Migrating VCL Apps to Per‑Monitor DPI v2 in RAD Studio 10.4‑12
Enable Per‑Monitor DPI v2 in RAD Studio VCL apps, handle custom painting, multi‑resolution images, and third‑party components with a practical migration checklist.
22 Jul 2026, 20:57 UTC

The problem: blurry UI on mixed‑DPI monitors
When a VCL application runs on a system where one monitor is set to 100 % scaling and another to 175 %, the default DPI‑awareness mode (System‑Aware) forces the whole process to render at a single DPI. The result is either stretched, fuzzy controls on the high‑DPI screen or tiny, hard‑to‑read UI on the low‑DPI screen. Per‑Monitor DPI v2 (PMv2) solves this by letting each top‑level window receive its own DPI change notification (WM_DPICHANGED) and scale itself independently.
Thesis: PMv2 is opt‑in and works out‑of‑the‑box for new projects, but existing codebases need a staged migration
RAD Studio 10.4 Sydney and later generate a manifest with dpiAwareness = PerMonitorV2 and set Application.Scaled := True for every new VCL project. For an established codebase you must enable those two switches, then walk through custom drawing, image assets, and third‑party components in a controlled order. Expect 2–4 weeks for a medium‑sized project.
1. Enabling PMv2
- Manifest – Project → Options → Application → Manifest → set
dpiAwarenesstoPerMonitorV2. This adds the<dpiAwareness>PerMonitorV2</dpiAwareness>entry to the embedded manifest. - Application.Scaled – In the project source (
.dpr) ensureApplication.Scaled := True;runs beforeApplication.Initialize. The IDE adds this automatically for new projects; for older projects add it manually. - Runtime check – After enabling, a simple button handler can confirm the active DPI:
Run on a monitor set to 150 % and you should seeprocedure TForm1.Button1Click(Sender: TObject); begin ShowMessageFmt('CurrentPPI = %d, Monitor PPI = %d', [Self.CurrentPPI, Screen.Monitors[Monitor.MonitorNum].PixelsPerInch]); end;CurrentPPI = 144(96 × 1.5).
2. Custom painting and image assets
VCL controls handle WM_DPICHANGED automatically, but any OnPaint, Canvas drawing, or owner‑drawn code must scale manually. The control’s CurrentPPI field (or FCurrentPPI) gives the monitor’s DPI at that moment.
procedure TForm1.PaintBox1Paint(Sender: TObject);
var
Scale: Single;
begin
Scale := PaintBox1.CurrentPPI / 96; // 96 = design‑time PPI
PaintBox1.Canvas.Pen.Width := Round(2 * Scale);
PaintBox1.Canvas.Rectangle(0, 0,
Round(100 * Scale), Round(50 * Scale));
end;
High‑DPI glyphs
- TImageList multi‑resolution – Add separate bitmaps for 100 % (16×16), 125 % (20×20), 150 % (24×24), 200 % (32×32). At design time set
Width/Heightto the base size (16×16) and use the “Add images at multiple resolutions” dialog (improved in 12 Athens). - SVG via TSVGIconImageList – Available from RAD Studio 11 Alexandria. One vector file scales cleanly to any DPI without maintaining multiple PNG strips.
3. Third‑party components and known limitations
- Components that do not handle
WM_DPICHANGEDwill appear blurred or mis‑aligned. Vendors must recompile with a PMv2‑aware VCL or implement the message themselves. - MDI child windows and legacy common dialogs (
TOpenDialogbefore 11) do not fully respect PMv2. Work‑around: callSetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)around the dialog creation. - Non‑client area (title bar, system menu) respects PMv2 only on Windows 10 1809+. On older builds the frame stays at system DPI.
- FireMonkey (FMX) uses
Screen.Scale– do not mix VCL PMv2 assumptions with FMX forms in the same process.
Worked example: minimal PMv2 test project
- Create a new VCL Forms Application in RAD Studio 12 Athens. Verify the manifest shows
PerMonitorV2andApplication.Scaled := Truein the.dpr. - Drop a
TButton(Button1) and aTImageList(ImageList1) on the form. Add three PNG strips to ImageList1: 16×16, 24×24, 32×32. SetImageList1.Width := 16; ImageList1.Height := 16;. - Assign the ImageList to Button1.Images and set Button1.ImageIndex = 0.
- Add the
Button1Clickhandler shown above to displayCurrentPPI. - Run on a primary monitor at 100 %. Note the button glyph size and the reported PPI (96).
- Change Windows Display scaling to 175 %, sign out/in, move the window to that monitor. The form rescales automatically; the button glyph switches to the 24×24 strip (≈150 % – the nearest match) and the message shows
CurrentPPI = 168.
This confirms that the manifest, Application.Scaled, and multi‑resolution ImageList work together without code changes to standard controls.
Trade‑off: migration effort vs. runtime fidelity
Enabling PMv2 on a large legacy codebase without staged testing causes widespread layout breakage—controls overlap, fonts clip, and owner‑drawn surfaces render at the wrong size. The safe path is a feature branch, automated UI tests at 100 %, 150 %, 200 %, then fix custom drawing first, then third‑party controls, then layout constraints. The payoff is crisp UI on every monitor without a process restart.
Actionable checklist for your next sprint
- Add
dpiAwareness = PerMonitorV2to the manifest andApplication.Scaled := True. - Run the existing test suite on a 150 % scaled monitor; log any visual regressions.
- Audit all
OnPaint/Canvascode – replace hard‑coded pixel values withCurrentPPI / 96scaling. - Upgrade or replace third‑party VCL libraries that lack PMv2 support.
- Replace legacy icon strips with multi‑resolution
TImageListorTSVGIconImageList. - Validate MDI children and common dialogs; apply
SetThreadDpiAwarenessContextshims where needed. - Document the minimum Windows version (10 1809) for full non‑client scaling.
Follow the list, ship the branch behind a feature flag, and you’ll have a VCL application that looks sharp on every monitor configuration your users throw at it.