Microsoft had introduced a mechanism for dealing with high pixel densities (dots per inch, DPI) in Windows XP, and added another one in Windows Vista. Both are described in High DPI Settings in Windows. Vista’s new DPI virtualization is now only beginning to look acceptable on the latest monitors, so for the foreseeable future developers will need to support the older XP style scaling.
On this page I’ve collected samples from four GUI frameworks based on .NET and Java to demonstrate how they deal with XP style DPI scaling – or not.
Windows XP style DPI scaling enlarges the default system font and other system UI elements in response to a higher DPI setting, but always maps drawing coordinates to physical screen pixels. That leaves GUI frameworks three methods to deal with varying DPI settings.
- Adopt the scaled system font size, and internally scale all drawing coordinates as well. This approach completely hides the fact that any scaling takes place at all. Applications automatically display correctly at any DPI setting. Currently, only WPF and JavaFX (since Java SE 8u60) fully support this method, and to a lesser degree Windows Forms.
- Adopt the scaled system font size, but do not scale drawing coordinates. This approach requires either manually scaling all drawing calls, or else relying entirely on UI components that automatically adapt to the current font size. JavaFX (prior to Java SE 8u60) quite successfully implemented the latter option, and to a lesser degree Java Swing’s System Look & Feel.
- Adopt nothing, scale nothing. Use hard-coded pixel sizes for all default fonts. This is what other Java Swing themes do. The advantage is that the application layout is always correct. The disadvantage is that it’s unusably small at high DPI settings.
If you’re only targeting desktop Windows, your best option to handle diverse DPI settings is the Windows Presentation Foundation (WPF). For those cases where this isn’t possible, the rest of this page examines how other popular options compare to the WPF gold standard.
Note: The two enlarged modes showcased below are 120 DPI (125%) and 144 DPI (150%). 120 DPI defaults to XP style scaling while 144 DPI defaults to DPI virtualization. However, all tested GUI frameworks (surprisingly even Java Swing) declare themselves DPI-aware and so use XP style scaling anyway. Windows Forms requires an extra manifest for DPI awareness but that’s typically present in real-world programs, so I added it to my test suite as well.
A suite of small test programs demonstrates the scaling behavior of the four standard .NET and Java GUI frameworks. The download package GuiDpiTest.zip (22 KB, ZIP archive) comprises the precompiled executables and their complete source code. Please refer to the enclosed
ReadMe.txt file and the various batch files for the required development tools and expected file paths.
The sample screenshots on this page are quite large and appear scaled down to your viewport size. Click or tap each image to see a full-size version in a new browser window.
Each application window is set to size itself automatically to its content. That content is a 3×3 grid using the framework’s standard grid container. The two top rows hold text entry fields, and the bottom row holds three other commonly used controls. One text field shows the name and size of the window’s default font, the other any active display options.
Each application window is positioned at the exact same location for all DPI settings, allowing an easy comparison of relative sizes. Batch files included in the download package set all window positions and any display options. Run the batch files to recreate these arrangements on your system.
WPF, JavaFX & Swing
First we’ll look at WPF and the two standard Java GUI frameworks, Swing and the new JavaFX – see JavaFX Overview below for more information about this framework. The tested versions are .NET 4.6 and Java SE 8 Update 60 with JavaFX 8. Here’s a series of screenshots for Windows 8, respectively set to 96 DPI (100%), 120 DPI (125%), and 144 DPI (150%).
WPF & JavaFX
For WPF and JavaFX, the
autoGrid option indicates that all controls are automatically sized to their contents. Omitting this option sets a fixed width of 70 pixels for each column in the bottom row, forcing all controls to conform to that width. (JavaFX uses 80 pixels for the third row since its
ComboBox has a lot of internal padding.) The height of all rows is always set automatically by the frameworks.
WPF — As expected, WPF performs flawlessly on all DPI settings. The only criticism is somewhat insufficient button padding when controls are sized automatically (WPF …b). Fixed column widths scale perfectly, too, because WPF interprets the specified 70 pixels as device-independent units which are implicitly scaled with increasing DPI resolution (WPF …a).
JavaFX — JavaFX correctly adopts the default system font, reported as “System Regular” but it’s really Segoe UI – check the glyphs! The sizes are all correct as well. Fully automatic control sizing (JavaFX …b) looks even better than WPF, in my opinion. You’ll notice that JavaFX uses a spacious cross-platform styling rather than the standard Windows look. There are no plans for an official Windows style.
There’s a catch, though. 144 DPI and higher perform implicit coordinate scaling just like WPF, so explicitly and automatically sized columns both work fine, and the system font is reported with its 96 DPI size of 12 pixels. However, JavaFX currently makes an exception for 120 DPI. Here only the default font and built-in controls are scaled whereas explicitly specified coordinates remain unscaled. Therefore, fixed 70/80-pixel columns lead to cut-off labels (JavaFX 120a). For the same reason, the margins between controls (explicitly set to 8 pixels) are not widened at 120 DPI.
This exception was added as a stopgap because the comprehensive DPI scaling introduced in Java SE 8u60 unexpectedly led to blurry text at 120 DPI. It’s not clear whether this issue will be revisited and fixed in future JavaFX version. For the time being, you must remember to manually test JavaFX layouts at 120 DPI to ensure no functionality is lost, as in the sample window with its cut-off labels.
The remaining four samples illustrate three different Java Swing “Look & Feel” packages, or themes for short. Swing does not support automatic coordinate scaling but does a good job of automatic control sizing, so the
autoGrid option is always implied. Trying to use explicit sizing with the one Swing theme that features automatic font scaling (System) will again result in cut-off labels.
Swing System — System is a pseudo-theme that requests different concrete themes depending on the platform. On Windows, System is the only Swing theme whose font scales with DPI settings (Swing …a) but rather imperfectly. Its default font is Tahoma rather than the actual Windows system font, and its size is always ca. 10% smaller than the actual system font size! This strange defect makes the otherwise promising System rather useless for the purpose of enlarging the GUI at higher DPI settings. You would have to explicitly request a larger font, and hope that everything scales correctly to the greater size.
Swing Default — Simply labeled “Swing” in the screenshots (Swing …b), this is the theme informally known as “Metal.” That’s the old default appearance that gave Java desktop applications a bad name: chubby, ugly, non-standard in every conceivable way, not scaling to anything at all. Useless, unless perhaps you want to write a convincing Windows 3.1 emulator…
Swing Nimbus — This promising attempt at a scalable cross-platform Swing theme was abandoned halfway through, for unclear reasons. Nimbus is vector-based and could theoretically scale to any DPI resolution, but is in practice hard-coded to only four different sizes – two of which are smaller than the default 96 DPI size (Swing …d)! The “Large” variant (Swing …c) illustrates the largest available size. You must manually request this size for every single control, as it’s neither automatically selected on high DPI systems nor toggled with a global option. Nimbus strikes me as useful only if you must build a cross-platform Java application with a consistent look that cannot use JavaFX.
I had originally intended to add two or three Windows Forms samples to the previous section’s overview. However, the framework’s various scaling options produced such a bizarre menagerie of layout failures that I decided to showcase them in a dedicated section.
The default settings are as follows: Automatic DPI scaling is disabled, row heights are always automatically sized, column widths in the bottom row are fixed at 70 pixels, and
AutoSize is disabled for controls in the bottom row. Windows Forms always automatically adjusts control sizes, so it’s best to think of
AutoSize as enabling an alternative sizing mode. The following settings are selectively overridden:
AutoScale— Automatic DPI scaling is enabled:
AutoScaleMode = AutoScaleMode.Dpi
AutoGrid— Column widths in the bottom row are automatically sized:
AutoSize— Controls in the bottom row are automatically sized:
AutoSize = true
Here’s the resulting series of screenshots for Windows 8, again respectively set to 96 DPI (100%), 120 DPI (125%), and 144 DPI (150%). The window positioning at 96 DPI looks random, but that’s because it matches the seemingly random 144 DPI scaling of Windows Forms!
No Automatic Sizing — Windows Forms looks best when not using automatic sizing for anything but grid row height. A fixed column width looks good at 96 DPI (WinForms 96a/c). Labels normally get cut off at higher DPI (WinForms 120/144c) but automatic DPI scaling prevents this (WinForms 120/144a). Indeed, this combination of fixed widths and automatic DPI scaling produces results comparable to WPF.
Automatic Sizing Without Automatic DPI Scaling — Enabling both automatic grid widths (
autoGrid) and automatic control sizing (
autoSize) but not automatic DPI scaling (
autoScale) is the one other combination that works reasonably well (WinForms …e). The auto-sized combo box is rather too wide, but otherwise the only defect are the unscaled control margins, as in JavaFX at 120 DPI.
Any Other Combination – Surprisingly, any other combination of automatic scaling and sizing destroys the layout!
autoGrid on its own has very strange ideas of appropriate column widths (WinForms …b).
autoSize on its own ignores DPI scaling and cuts off labels (WinForms …d).
autoScale produces bizarrely bloated buttons with
autoSize (WinForms …f) and bizarrely widened columns with
autoGrid (WinForms …g). Enable all three options, and you get both defects at once (WinForms …h).
You may have noticed one other pervasive defect: the default font is always the correct size but the wrong family. Windows Forms always chooses the ancient Microsoft Sans Serif which hasn’t been the default system font since Windows XP. The usual workaround is to explicitly request
SystemFonts.MessageBoxFont. I haven’t bothered to do this here because I didn’t want to give Windows Forms too much of a leg up with manual corrections. The test program already contains an explicit call to
Application.EnableVisualStyles, so as to at least show the correct control styling.
JavaFX is broadly comparable to WPF in its capabilities. For example, JavaFX offers nestable containers with dynamic content-sized layout, data binding through properties, and optional declarative UI specifications. Since Jave SE 8 Update 60, JavaFX also features the same automatic DPI coordinate scaling as WPF. (Older versions only scaled the default font and built-in controls, as documented here.)
JavaFX applications can be written entirely in code, like my test program. However, JavaFX also supports two kinds of declarative files: CSS style sheets and FXML object graphs. Whereas WPF packs both styling and structure into the XAML format, JavaFX uses separate formats for each task.
CSS defines the look of JavaFX controls. All standard controls come with predefined style sheets. Custom styling can be applied with your own external style sheets, or directly with the Node.setStyle method. While the syntax was adopted from HTML CSS, you cannot use regular CSS properties with JavaFX. Instead, the framework defines its own set of properties prefixed by
- FXML is a (purely optional) way to define the structure of a JavaFX layout. Just as in XAML, objects correspond to elements and properties are set with attributes or nested elements. You can also embed code or link to external code, typically event handlers.
While FXML is overall comparable to XAML, it turns out to be less important in practice because programmatic UI building is much easier in JavaFX than in WPF. JavaFX provides a wealth of builder classes and convenience constructors that are almost totally absent from WPF. So far I prefer to create JavaFX UIs in code, with the occasional CSS modification, even though I usually write WPF UIs in XAML.