Resize a window in WinUI (part 0: what is DPI)
I’ve been working on a WinUI 3 app, and I’ve been trying to use the WinAppSDK resizing APIs, like AppWindow.MoveAndResize
. Simple APIs!
Unfortunately, on my high-DPI monitor, these APIs make my window smaller than I expect — 400px in XAML seems to be larger than 400px in these AppWindow
APIs.
It turns out these APIs use physical pixels (raw pixels), whereas most WinUI & UWP sizes are in something called device-independent pixels (which scale based on screen size & DPI). So on high-DPI monitors, windows will be smaller than expected:
public sealed partial class MainWindow : Window {
public MainWindow() {
InitializeComponent(); // ...
// These numbers are physical pixels, whereas most other
// pixels in WinAppSDK are device-independent pixels.
this.AppWindow.MoveAndResize(new RectInt32() {
Height = 800,
Width = 600,
X = 0,
Y = 0
});
}
}
To handle this, we’ll need to adjust these numbers based on the current monitor’s DPI. But to do that, we need to understand DPI.
A brief history of DPI in Windows
First, let’s quickly explain DPI. The story begins on MSDN, DPI and device-independent pixels:
For years, monitors almost always had ~96 physical pixels per inch (PPI), or, using the printing term, “dot”, 96 dots per inch (DPI). Analagously, typesetters divide inches into 72 points per inch; so, excluding small ascenders & descenders which exceed this space, Windows could assume a 12pt font gets 16px:
12pt ×1 inch over 72 pt× 96px over inch= 16px
This worked well — until high-DPI monitors became common. The “96 DPI” assumption became wildly wrong (for example: a 27″ 4K monitor is ~160 PPI, and an iPhone 16 is 460 PPI). If you don’t scale your UI appropriately, everything becomes tiny!


To remedy this, Microsoft made Windows’ DPI adjustable. For example, you could set it to 192 DPI:
12pt ×1 inch over 72 pt× 192px over inch= 32px
(And, since you can adjust “DPI” to anything, unrelated to actual physical pixels per inch, Windows calls this logical DPI, or dots per logical inch. For the rest of this article, “DPI” and “logical DPI” refer to this adjustable DPI setting, and “PPI” refers to physical pixels per inch).


For another writeup, Colin Finck has a great overview of DPI on Windows in Writing Win32 apps like it’s 2020.
DPI-awareness: how apps handle DPI
This change on its own does nothing — apps must handle the new DPI by scaling their UI. While this has evolved across Windows 7, 8.1, 10, and 11 (see Colin’s blog above and High DPI Desktop Application Development on Windows and Mixed-Mode DPI Scaling and DPI-aware APIs), basically every window runs in one of 2 modes:
-
DPI-aware: do it yourself.
Your window sees physical pixels. Windows gives you those real pixels, a few APIs to get the DPI, and says “figure it out” (usually; see the sidenote below).
-
DPI-unaware: Windows does it for you.
Your window sees virtualized pixels (sometimes called logical pixels). Your app thinks Windows is running at its original 96 DPI (defined as
USER_DEFAULT_SCREEN_DPI
), and Windows stretches everything for you, by a simple scale factor. For example:scale factor =144 logical DPI over 96 default DPI= 150%This stretching is why some older apps, like Group Policy Editor and Device Manager, look blurry on modern devices (although Windows actually does some cool hackery to make text render crisper).
Blurry icons & less-blurry text in the Group Policy app
All apps are DPI-unaware by default and need to opt-in to DPI-awareness, but the templates for UWP & WinUI apps do this by default. If you’re writing an app from scratch, you’ll want to opt-in yourself.
Device-Independent Pixels (DIPs): frameworks add another layer
But we’re not done yet.
The Win32 APIs might provide physical pixels in your WinUI app, but the WinUI APIs typically won’t.
Newer frameworks (like UWP, WinUI, Win2D) are designed to be device-independent — and handle DPI scaling automatically. Within the frameworks, you work with device-independent pixels (DIPs, a.k.a effective pixels or view pixels), and the frameworks automatically scale them to physical pixel units.
These DIPs behave similarly to virtualized pixels in DPI-unaware apps: your app sees the world as though there are 96 DIPs per logical inch and the framework applies the scale factor for you. Remember, scale factor is:
scale factor =logical DPI over default DPI=logical DPI over 96
So (as described in DPI and DIPs):
physical pixels = DIPs × scale factor = DIPs ×logical DPI over 96
But, since the framework does this math before rendering (instead of stretching it afterward, like Windows would), it’s able to do so without making anything blurry.


After Windows 10, that’s more important than ever: Windows’ logical DPI is much less related to physical PPI. Instead, Windows scales logical DPI based on viewing distance to make UI text equally legible on all devices.


For example, it might attempt to render “1 inch” (72pt) text very differently on a distant TV versus a nearby phone:
Actual PPI | Viewing dist. | ⇒ Potential height | Scale factor | Logical DPI | |
---|---|---|---|---|---|
1080p 40″ TV | 55 PPI | 10 feet | 4.50″ | 450% | 432 DPI |
1080p 4″ phone | 550 PPI | 10 inches | 0.75″ | 75% | 72 DPI |
An archived blog post, Display Scaling in Windows 10, explains this philosophy and compares these scaling approaches.
Conclusion: back to our problem
Although AppWindow is a WinAppSDK API, not a Win2D one, the DIP docs state (emphasis added):
In Win2D, whenever you see an API that specifies a position or size using integer data types (or a struct such as BitmapSize that contains integers), this means the API is operating in pixel units.
[…]
In Win2D, whenever you see an API that specifies a position or size using floating point data types (or structs such as Vector2 or Size that contain floating point values), this means the API is operating in DIPs.
And thus, our issue:
AppWindow.MoveAndResize
uses integer coordinates. Since WinUI apps are DPI-aware, those are in physical pixel units.- The rest of our app uses device-independent pixel units.
So if we want our app to be the right size, we need to DPI-scale the pixels manually. Part 1 coming soon!
Thanks to Ari Krumbein, Raymond Chen, & Atherai Maran for editing.