Writing High-DPI Aware Windows Apps
Posted by William Yu
Today's laptops and monitors support high resolution displays which allow you to pack more information and content on the screen. Although one common complaint is that people find the text to be too small at the maximum resolution, Windows' solution to this is the ability for the user to adjust the DPI setting.
At 150% resolution things get scaled 1.5 times which should make text more readable. However, if your app is not DPI-aware then Windows will scale your content for you, which results in a very blurry looking window.
This DPI virtualization was introduced in Windows Vista, although they still support Windows XP style DPI scaling. The difference is that XP style DPI scaling doesn't scale your content automatically, but does report the correct DPI resolution to your app (which we use to render text appropriately at the right size). With DPI virtualization your app does not receive the correct DPI resolution information (Windows lies about it) so we render text at the lower resolution and Windows takes over from there and scales your content automatically. The result is a blurry looking window.
Enabling DPI-aware mode
As more computer makers start shipping laptops with high-DPI settings preconfigured, being DPI-aware is increasingly important. We're constantly trying to improve our framework to support things natively, and I'm confident at some point that you will not even have to think twice about whether or not your Xojo app is DPI-aware, but the one big challenge with high-DPI apps is resizing the controls correctly. However, this does not mean your Xojo app today can't be DPI-aware with a little work on your part. There are two ways to tell Windows that your app is DPI-aware.
- Declare and call the Win32 SetProcessDPIAware API
- Modify the embedded manifest in your Xojo .exe app
Let's take a look at the simplist approach to enable your app to be DPI-aware. You can read up on this API here. You'll notice that Microsoft doesn't recommend this approach in the case where a DLL would cache the DPI setting before SetProcessDPIAware is called. None of our DLLs currently cache the DPI so you're fine as long as you don't rely on other DLLs that might. You can setup this code in the App.Open event like so:
Declare Function SetProcessDPIAware Lib "user32" () As Boolean If Not SetProcessDPIAware Then MsgBox "Error enabling DPI aware mode." End If
Modifying the application manifest
Modifying the application manifest is the suggested approach by Microsoft, but it requires the use of a resource editor like PE Explorer. A manifest is embedded in each Xojo app which describes some of the elements and attributes of your application. One such attribute is whether or not your app is dpi-aware. You can copy and paste this into the manifest resource in your Xojo app:
So what exactly does this get you? In normal DPI mode nothing really, now switch to high-DPI mode (say 150%). Now that you've told Windows that your app is DPI-aware it no longer virtualizes your interface (by scaling automatically). Your new interface should look something like this:
Notice that your text inherits the default system size, unless of course you've specified otherwise. The controls however, are not automatically scaled. Until we come out with an autolayout manager you'll have to adjust the controls yourself. First, you must find the scale factor. A normal DPI resolution on Windows is 96, while at 150% it would be 144. Here's some code to find this scale factor:
Declare Function GetDC Lib "user32" (hWnd As Ptr) As Ptr
Declare Function GetDeviceCaps Lib "gdi32" _
(hdc As Ptr, nIndex As Integer) As Integer
Declare Sub ReleaseDC Lib "user32" (hWnd As Ptr, hdc As Ptr)
Const LOGPIXELSX = 88
Const LOGPIXELSY = 90
Dim hdc As Ptr = GetDC(Nil)
Dim dpiX As Integer = GetDeviceCaps(hdc, LOGPIXELSX)
Dim dpiY As Integer = GetDeviceCaps(hdc, LOGPIXELSY)
Dim scaleFactorX As Double = dpiX / 96
Dim scaleFactorY As Double = dpiY / 96
Note how we're finding both the X and Y DPI resolution. They are usually, but not always, the same. Knowing the scale factor you can now adjust your UI appropriately.
If your controls rely on a fixed sized text, no matter what the resolution, you can always specify the TextUnit to be Pixel instead of Point and enter the size in pixels. This would allow you to ignore any scaling, but note how "off" your UI may feel.
Note that for pictures that you display on screen you may also want to create multiple resolutions instead of scaling them.
Being a DPI-aware app on Windows is becoming increasingly important as resolutions grow larger but how you choose, or not choose, to support it is of course up to you. You may decide that it's too much effort at this time to update your app, and continue to rely on Windows' inherent DPI virtualization to scale your app for you, or wait until we support it natively. You can choose to be somewhat DPI-aware and set the mode up (with the declare or manifest change mentioned) but leave all UI unscaled (in which case you would change all the TextUnits to Pixel), or you can be fully dpi-aware and scale/reposition all your controls as necessary.