High DPI Guide
This guide explains how to support High-DPI displays in your VCL applications and RAD Studio IDE’s support for High-DPI.
This guide does not cover FireMonkey.
Modern displays are high resolution, and Windows supports scaling so that windows appear approximately the same physical size on these new screens while using a higher density of pixels to draw. This means that applications that run on modern displays must be High-DPI aware: able to scale correctly regardless of the display on which they are located. A computer may have multiple displays, each with a different scale due to its varying resolution and physical size, and your app’s windows need to be able to transition seamlessly from one screen (and scale) to another.
Your apps can support high DPI independently of the RAD Studio IDE itself, which supports high DPI. However, RAD Studio has features that help considerably. This page explains how to ensure your applications scale correctly and look great on any display, as well as RAD Studio’s high-DPI support.
Typical problems for your application include enabling high-DPI support at all, handling images such as toolbar and menu image lists, changing the drawing code to adjust to different canvas sizes, or similarly changing manual control size or position placement code, and more.
In RAD Studio Florence, the Delphi, C++Builder, and RAD Studio IDEs are now high-DPI enabled. All windows in the IDE now support high DPI. This includes behavior such as changing scale when being dragged from one monitor to another. IDE functionality refers to:
- The main window.
- All dockable windows, like the Object Inspector, Projects, Structure, Messages, etc. As well as all Object Inspector property editors.
- All dialogs like New Items, IDE Options, Project Options, About, etc.
- All content windows like the editor, form designers, history, etc.
The IDE styles (Light, Dark, and Mountain Mist) have also been updated to support high DPI.
Contents
Why High-DPI
There are two answers here: the idealistic and the practical.
Idealistic
High-DPI benefits users because using more pixels for the same size onscreen means what you see and display is sharper. For example, previously you could see the pixels that made up a letter, and now it can be high-resolution so that your eye sees the letter as its actual shape. As a result, images can contain more detail. For many years, phones and tablets have supported high-DPI displays, often referred to as Retina displays.
Practical
In practice, you need to support it in your applications because your users have high-resolution displays. If you don’t, your low-resolution app will be stretched by Windows, appearing blurry and challenging to read. That is, if your apps don’t support high DPI, they’ll actively look worse than they used to. Fixing this is essential for usability and accessibility.
Concepts
The following is an overview of the fundamentals.
DPI or PPI
Displays have a physical size and a resolution. The DPI (dots per inch) or PPI (pixels per inch) mean the same thing. They refer to the number of pixels a display has for its physical size, known as pixel density. For example, a 1024x768 13” display has a higher pixel density, that is, a higher PPI (number of pixels on the physical screen space) than a 1024x768 17” display (which has the same number of pixels total but a lower PPI because the same pixels are stretched out over a bigger physical distance). This is why, when you use a modern 4K screen today, which has twice the pixel density of older displays, everything will draw really small: it’s using the same number of pixels as if the display had a lower PPI. 
This is the reason for requiring high DPI scaling, which uses more pixels for any display element to maintain the same physical size onscreen, despite the windows being resized. That means when you use your 4K display, everything is not drawn really small, but is a usable, readable size.
Scale
Windows used to assume that all displays, regardless of their resolution or size, had a 96 PPI (pixels per inch) density. This is the nominal “100%” scale. Scale refers to the Windows setting (in the Settings app, Display). The user sets this to ensure the windows on screen are the correct physical size. The scale should be based on the display’s PPI, and many displays communicate with Windows, allowing Windows to set an appropriate scale automatically. A scale of 150% means that a UI element, which was previously 16px wide, should now take up 24px on screen. A scale of 300% means the same UI element should now take 48px on screen. 
Note the “should”: enabling high-DPI support in your app is what ensures this happens.
It is recommended to think in terms of scale, not PPI. This simplifies sizes and positions (e.g., the width of a button):
1.5x for 150%, rather than attempting to perform the math for various PPIs and determining a scaling factor.High-DPI Support
Refers to your app scaling correctly on the screen. Importantly, this means crisp drawing. That is, in the example above, your button really does have a bigger Width than it used to. This is important to note because Windows checks if an app supports high DPI via a setting in its manifest, and if it does not, it will stretch the app onscreen to match the display scale. This means your app is the right size onscreen (good), but because it’s stretching a lower-resolution image (the image of your window) to a higher-resolution display, your app will be blurry. Supporting high DPI means Windows doesn’t do this and assumes your app will behave correctly on its own. For a RAD Studio app, VCL does all the heavy lifting here for you.
Other key Information
It’s essential to recognize that when an app is high-DPI-enabled and scales, the actual pixel height and width of the window or form, as well as each control, change accordingly. That is, the Left, Top, Width, and Height properties all change. If you have a button that’s 75 pixels wide, at 200% scaling, it will be 150 pixels wide. If your code sets the Width, Left, or similar property, you will need to adjust it by the current scaling. There are methods available to assist you with this.
Supporting High-DPI in Applications
VCL applications support the newest and best of Microsoft’s high-DPI features in Windows, known as perMonitorv2. This allows one application to have windows scaled differently on displays with different scales.
Scaling is performed per window (per form) because there is a window-display relationship: a window is only ever displayed on one screen at a time. However, support is enabled on an application-wide level. The VCL then handles adjusting each form and its controls when the app is run, as well as when scaling changes occur (such as when the user changes Windows display settings or drags a window from one display to another).
Enabling High-DPI on Apps
To enable High-DPI support, navigate to the Project > Application > Manifest page and select the 'perMonitorv2' option in the DPI Awareness field.
 
RAD Studio provides a setting option for all possibilities, but do not use others: the VCL supports only version 2, the latest and best, and using other settings will give unsupported results.
Second, for each form in your application, ensure that the Scaled property is set to True. This is the default value and will only be False if it was manually changed in the past. This property is required to let a form and a firm’s controls respond to a scale change.
At this point, your app can run, and all forms, buttons, and other controls should change size when you run the app and when you drag windows between different displays. However, you may have issues with images, manual positioning, and drawing code.
Handling Images
Toolbar, menu, button, action, and other components get their images from an image list. Your software is likely using a TImageList control. This stores images at one size only, usually 16x16. This means that now your application uses more pixels for a toolbar button or menu. The images are too low-resolution and do not look correct.
To resolve this, the VCL features a new image list component, TVirtualImageList, which is used in conjunction with an image storage component called TImageCollection. An image collection resides in a single location within your app, such as a data module, and stores the images your app requires. You can have multiple image collections if you want. Still, there’s no point to it: one collection can store anything, including images of different aspect ratios, i.e., hold both images intended for a toolbar as well as images intended for a splash screen. Images are named and in categories.
A TVirtualImageList connects to an image collection and presents some or all of the images from the collection, automatically adjusted for scale. You can think of this as the collection being the original core data (images), and the image list being a view or presentation of that data (images) presented at the right scale for your form to use.
The virtual image list has a nominal Width and Height, just like a traditional TImageList. The default value is 16x16, typical for menu and toolbar images. However, these image list properties are not the width and height of the images, but the width and height at 100% scaling. The actual images it presents and makes available to draw will be the right size automatically.
This makes virtual image list controls unique because all other controls have Width and Height set to the actual in-use width and height, that is, these values change when the control is scaled.
For the virtual image list only, these really mean ‘nominal width/height’ or ‘width/height at 100%’. RAD Studio retains the names of the properties rather than renaming them, e.g., to NominalWidth, to make it easier to replace old image lists with new ones without requiring code changes.
How does it know the right size? It scales based on the scale of the form the image list is placed on, or more accurately, of the display the image list’s form is on. Scale is per display, meaning per window.
This means the following:
- Store all your images in an image collection.
- Controls must always use an image list on the same form they are on. This is because an app may have forms with different scales applied, so referring to an image list on another form does not guarantee it will have the right-sized images. An image list on a data module is meaningless because a data module has no scale. So, controls must use an image list on their form, always and only.
- If your controls previously referred to a central image list, change this. Conceptually, you would replace it with a central image collection, then have virtual image lists connected to that collection on each of your forms. A central image list will no longer work for a high-DPI app because Windows may scale differently.
- Remember, a virtual image list is lightweight: it does not actually store any images, just presents them, so you can have multiple virtual image lists referring to the central collection without significant memory or other cost.
See our Supporting high-DPI images with the Image Collection and Virtual ImageList components documentation page for a full, detailed write-up on using the image collection and image list components.
This includes helpful practical steps such as:
- Load existing TImageList into TImageCollection.
- Converting old TImageLists.
- Best Practices, including advice on image formats, what size images to use, and more.
It is possible to convert traditional TImageLists into modern image collections, which makes upgrading image technology easier.
When creating your image collection and image lists, remember that the VCL now supports using names to refer to images, not just indices. This allows setting a menu, button, action, etc., image by name, including category and name.
Manual Positioning Code or Drawing
When scaling, the actual pixel coordinates of controls, including their size, change. Above, we gave the example of a 75-pixel-wide button that, at 200%, is now 150 pixels wide. The VCL handles this scaling for you, ensuring that when the DPI for a form changes, all controls have their positions and sizes updated.
However, if you are working with positions on-screen in your code, you will need to make some manual adjustments. Examples include:
- Code setting a control position, e.g., {{{1}}}.
- Code changing control size.
- Any code dealing with custom spacing. It’s common to have, for example:
MyOkButton.Left := MyCancelButton.Left + MyCancelButton.Width + 4;
- Note:
 The ‘4’ needs to be updated.
- Drawing on a canvas, again with coordinates.
In the past, it was common to adjust control positions or sizes through a complex call to MulDiv. This is no longer necessary. Today, it is recommended to use the following properties:
- TControl.ScaleFactor: always contains the scale for the display the control is on (e.g., 1.5 for 150%).
- TControl.ScaleValue(): gets the scaled version of values, points, rectangles, etc. That is, you can input an unscaled TPoint (perhaps used for drawing coordinates), and the result will be the scaled point you should use: the location of that original point on the scaled control.
In fact, ScaleValue allows you to scale integers, floating-point values, sizes, points, and rectangles. All inputs are the unscaled value: the old, pre-high-DPI value you were using, such as ‘4’ in the button positioning example above. The output is the correct value to use for the currently applied scaling. The above example would be adjusted today to:
MyOkButton.Left := MyCancelButton.Left + MyCancelButton.Width + ScaleValue(4);
ScaleValue is the magic solution for all custom layouts, drawings, and more. Remember that when inputting the unscaled value (the one you would have always used in the past), the result is the correct value to use now.
As a member of TControl, each control has a scale factor (and the ScaleValue method), but the scale factor is identical to the scale of the form on which the control is placed.