Delphi Firemonkey Scale for High DPI Windows - delphi

I thought scaling an application in FireMonkey should be easy as it is supposed to work an a Retina-Mac too. It seems there is some background magic which chooses another style than, but this doesn't seem to be the case in Windows.
In VCL there was TForm.Scaled which does the job (more or less).
For FireMonkey I found this Article by Embarcadero but it seems Embarcadero is not quite sure if this approach is useful as in XE5 the sampleproject "ControlsDemo" doesn't contain the scale-trackbar anymore. It also requires to have a root-TLayout present in all forms for which the scale factor can be set. I don't have such a element in my forms (and I'm afraid to add one as I cannot be sure if I run in another firemonkey bug than).
So how do I account for different DPI-Settings using FireMonkey?
Edit: I tried hacking TPlatFormWin and set CurrentScreenScale to 2 but it didn't work: I got black toolbars, but no scaling, but at least it used the HighRes alternative from the provided TImage.MultiResBitmap.

If anyone reads this... my experience with Windows desktop Firemonkey is that you have to take care of OS DPI setting manually and it's OK to put your controls in a top container (a TLayout) that's Scale is set according to the OS screen DPI settings (determined with some low level code).
However there are cases when you need to reverse this scaling - for example a Viewport3D must be scaled back 1/X to correctly show inside the scaled up container. Otherwise pixel level artifacts will show, image quality will be awful.
This scale up/scale back technique works nicely.

U can do something like this to change the root TLayout:
with 100 being the default
if windowsscale>0 then begin
LayoutScale.height:=ClientHeight*100/windowsscale;
LayoutScale.Width :=Clientwidth*100/windowsscale;
LayoutScale.Scale.X:=windowsscale/100;
LayoutScale.Scale.Y:=windowsscale/100;
end;

Related

Visual bug in Windows title bar with VCL Styles enabled application and display scaling

Currently I am testing various aspects of VCL styles enabled applications.
I noticed, that with Windows scaling higher than the default 96 dpi/100%,
the icon and the title bar text of the VCL Form are too big in size - and both are to close together -, please see attached screenshots. This is especially true with higher scalings like 200% or 250% (e.g. used on 4K displays and Windows 10), but even with a scaling of 144 dpi/150%, the problem is already visible.
This is true for all styles delivered with RAD Studio. High Dpi awareness for manifest is enabled via project settings.
If I disable VCL Styles in the App, the icon and title bar text is correct in size.
Am I missing something here? Shouldn't the delivered styles work without such display errors out of the box with display scaling enabled? Or is there some setting somewhere I can adjust to fix this.
Thanks,
VCL Styles do not properly support high DPI scaling.
If you use VCL Styles, then you should remove high DPI awareness from your application manifest.
QP report requesting general high DPI support for VCL Styles:
VCL styles don't scale properly under high DPI configurations
Related QC Report for NC area: Styled form's non-client area incorrectly scaled under High DPI
Ok, here is my solution for the visual bugs, please see the attached screenshots. I did fixes in 3 places in Vcl.Forms.pas.
The first fix, commented with // Title bar fix 1, addresses the problem that the icon is not correctly drawn, even without scaling, on a default 96dpi Windows with a VCL styled application. I could fix this based on the findings about WM_GETICON, ICON_SMALL2, from James Johnston,
https://stackoverflow.com/a/35067909
thanks, James, for that!
The other two fixes address the problem that the icon is drawn too big with display scaling enabled, and that the distance between the icon and the title bar text is too small. These are the fixes commented with // Title bar fix 2 and 3 in the code.
GetDpi is just a getter for the current dpi value, which I get from my C sources within the application.
The result is in no way perfect, but it will do for now, with this the VCL styled application is at least acceptable under scaled circumstances.
Thanks to all for your input.
Here is a unit that allows VCL styles in DPI-aware applications.
VCL.Styles.DPIAware.pas
To use the unit just add it to the implementation uses statement of the main form and add the following code to the FormCreate handler.
procedure TFrmMain.FormCreate(Sender: TObject);
Var
StyleDPIAwareness : TStyleDPIAwareness;
begin
StyleDPIAwareness := TStyleDPIAwareness.Create(Self);
StyleDPIAwareness.Parent := Self;
By default the component scales the styles at multiples of 100%. You can change that, by adding the line:
StyleDPIAwareness.RoundScalingFactor := False;
With this statement styles are scaled to whatever scaling factor results for Screen.PixelsPerInch. Most of the styles would work fine, but a few may show some visual defects.

Painting issues with TScaledLayout & custom styles

I'm experiencing painting issues when combining TScaledLayout and custom styles created from the bitmap style designer in fmx.
To demonstrate, I loaded the default custom style created by chosing "New style for VCL / FMX" -> "save as .style" in the bitmap style designer. I dropped several standard controls on some colored rectangles: The red & green ones on a TScaledLayout, the blue one directly on the form. As I stretch the form, colored lines appear on the controls on the ScaledLayout; the background is partially visible:
If I size the form to exactly match the design-time dimensions, the lines disappear. That seems like a pretty significant issue, I certainly can't use those two together like that. Does anybody have an idea for a possible fix or workaround?
Looks like this is a known issue with scaling and bitmaps. See the Google+ discussion here - https://plus.google.com/+PaulThornton/posts/ACAHkJD3a84. I'll quote Marco Cantu's thoughts:
I've found an internally reported issue of a similar case, but haven't
found one that matches this scenario. Certainly worth adding to quality
portal. Having said this, I fear that bitmap-based operations and
scaling don't really fit together very well, and it might be difficult
to have an all encompassing solution.
Let me explain with an example. Take a button. This is painted by FMX
with 9 sections (borders, corners, central part) so that regardless of
the size the bitmap elements are stretched in one direction at most,
often just draw. Stretching a single bitmap for the button to the
target size would break anti-aliasing and create a blurred image when
using colors.
This is example what happens with a ScaledLayout, given it takes the
complete final image and transforms it. ScaledLayout was originally
introduced with vector styles, and worked very well in that scenario.
With todays's bitmap styles things get a bit more complex.
Regardless of this explanation of there the issue lies, I'd recommend
reporting it on QC, and I'll make sure it doesn't get closed as design
(it could naturally happen, this is how the system works) but that we
do some investigation to address the issue -- turning this into a
feature request.

For what design reason TCanvas.StretchDraw is not working as expected for the TIcon?

Recently I've discovered that TCanvas.StretchDraw will not work as expected for object which is a TIcon instance (quick look at TIcon.Draw and DrawIconEx method tells why). Delphi help acknowledges that fact. I know the workaround but I do not know the reason behind such design decision in VCL. Does anyone know why they decided to left TIcon untouched in this matter?
Icons are not regular bitmaps. This is mostly due to historical design and technical reasons.
It did make sense at time when icons were small 32x32 pixels large and 16 colors (good old days!) that icons were never to be stretched on screen.
But there is also a "common sense" technical reason. Such small bitmaps are usually very hard to be re-sized by an algorithm (and default GDI strech algoritm is very fast but produces also very bad result in respect to other interpolation modes, e.g. available with GDI+), so it was decided to embed a set of icons within the executable, as resources: one icon per size. The strech process benefits of being done at design time, at pixel level, by an icon designer. And - back those days - it was also much less resource consuming to use dedicated icons, with a reduced color palette.
Since you are supposed to have a set of icons with a pre-defined size for each, you do not need to use StrechDraw, but just select the right icon to display.
So if you want to display an icon with a given size, ensure you pickup the right size, or get the biggest icon and upsize it, using a temporary bitmap - or DrawIconEx(). Or, even better, do not use icons, but a bitmap, or a vectorial drawing if you expect huge picture size.

Troubleshooting DPI Virtualization and DPI-aware applications, in Windows Vista and Windows 7

I have a problem where an application (written in Delphi) is behaving properly at default 96 DPI settings on all systems, but is behaving inconsistently at the "150% text size" (internally 144 dpi) setting, on different systems. It seems that on some systems, that certain text/font parts of my application are being stretched and on other systems, they aren't. I would have thought that my application, on a certain version of Windows (Win7), at a certain DPI, should behave the same way.
Either my application will make it known to Windows that it doesn't need the DPI Virtualization feature, or it will not. That much I understand. What I don't understand is how DPI changes can cause different appearance on two machines, both running Windows 7, both at 144 dpi, displaying the same fonts and forms at the same fixed sizes.
Is there some configuration-dependant elements involved in DPI Virtualization that I need to inspect in windows (registry etc)? Otherwise, how do you troubleshoot and know whether DPI virtualization is being done on your client window?
In Delphi, one has to set the TForm.Scaled property to false, if one doesn't want scaling. But what I don't understand is that when the main form's Scaled property is true, I cannot always predict the outcome.
What is most perplexing to me in my application, is that I have a control that only misbehaves in my large real application, but which does not misbehave in a standalone app where I am trying to debug just the control. To understand the control behaviour in a standalone app I was forced to make a demo app where I force the DPI awareness in via the manifest file. Then I can reproduce the control drawing glitch, although in a different form.
Here is the manifest file I use in my demo app, which exposes the problems that my controls have with dealing with high-dpi settings in windows. However, one strange thing I have found is that it is possible for an application
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings
xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
<assemblyIdentity version="14.0.3615.26342" processorArchitecture="*"
name="TestProject" type="win32"></assemblyIdentity>
<description>High DPI Controls Test App</description>
</assembly>
here's an example of one of about 30 places where the controls in my application are messed up when I disable DPI virtualization in my app. This particular glitch was solved by turning off the Scaled property in my form. But in other places, having TForm.Scaled=false causes the problem, whereas in some forms, it fixes it:
Update: It turns out that some of my controls use GDI+ and that font behaviour in GDI+ contexts is different than font behaviour in normal GDI contexts, at least for certain third-party controls that use GDI+. That's a major source of headaches. Secondly, there is the spotty test coverage and poorly-defined requirements, for DPI awareness, in the VCL. Some VCL controls are based on MS Common Controls, and while it's fair to say that the underlying common controls probably work fine in high-DPI situations, not all the VCL control wrappers can be guaranteed to work correctly. So, reviewing an application for high-DPI-awareness in all its controls, as well as correct behaviour in all available windows 7 themes:
aero glass on, at 96dpi (Default Win7 appearance on most modern hardware)
basic theme (aero glass off) but xp themes enabled
classic win2000 look where glass is off, as well as xp level themes,
high contrast white
high contrast black
Various Other-than-96-DPI settings
..and the list goes on., and you have a pretty heavy burden, as an application developer. Whether you are a delphi user and use the VCL, or you are an MFC/ATL C++ developer, it seems to me, that supporting all of the various quirky windows modes is a burden almost too heavy to bear. So most people don't bother. Am I right?
You need to manifest that your app is DPI aware with a section like this:
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
If you do this then you won't get DPI virtualization.
You aren't meant to use DPI virtualization so I think there's little point trying to work out how it works. It could easily be dependent on graphics card drivers. It's almost impossible for us to explain why virtualization is behaving this way: you haven't even given any screenshots, hardware details etc. However, you simply shouldn't bother trying to diagnose this. Manifest as dpiaware and it's a non-problem.
For reference I offer you:
Writing High-DPI Win32 Applications
http://www.rw-designer.com/DPI-aware
It's actually a different question than that.
Your forms should not be getting larger with the user's DPI, they should be getting larger with font size.
Delphi's default form font is 8pt Tahoma.
The average 8pt Tahoma character is: 6.08px * 13px.
Starting with Windows Vista, the default font is 9pt Segoe UI.
The average 9pt Segoe UI character is: 6.81px * 15px.
Your Windows applications should be honoring the user's font preference (i.e. IconTitleFont).
My Windows font preference is 12pt Segoe UI, whose average character size is: 8.98px * 21px:
This means that if you designed your form at Tahoma 8pt (13px high), you need to scale the form, and all the child controls, by 162%:
scaleFactor = (userFontSize / designedFontSize)
= 21px / 13px
= 1.615
If you're careful you'll notice that changing DPI is just a special case of changing the font size. Your 8pt font is still 8pt, but 8pt translates into more pixels. If you run 131dpi (136% zoom setting in Windows 7) then:
9pt Segoe UI, 96dpi = 6.81px x 15px
9pt Segoe UI, 131dpi = 8.98px x 21px
Note: It's not a coincidence that i chose 131dpi and 12pt as my examples. At work i run 96dpi but 12pt. At home i run 9pt but 131dpi. Both have the same pixel font height, 21px.
In the end you need to call ScaleControl by the size difference:
procedure StandardizeFormFont(Form: TForm);
var
iconTitleFontName: string;
iconTitleFontSizePixels: Integer;
currentFontSizePixels: Integer;
begin
GetIconTitleFont(iconTitleFontName, iconTitleFontSizePixels);
//Change font face first
//Some fonts are inheriently taller than others
//(even at the same point size)
Form.Font.Name := iconTitleFontName;
//Get the form's current font height (in pixels)
currentFontSizePixels := Form.Font.Height; //negative number, but so is iconTitleFontSizePixels
Form.ScaleBy(iconTitleFontSizePixels, currentFontSizePixels);
end;
In reality this code is very simplistic. Many child controls need to be updated manually:
listview columns need to get wider
controls with ParentFont = false need to have their font's adjusted
TImage controls need to stretch their image to the new size
toolbar's need to use larger images
In reality we use a super-fancy version of StandardizeFormFont that recursively goes through all controls on the form and does it's best to adjust each control based on what it is.
Every control is Delphi is supposed to override it's ScaleControl method, and make the adjustments it needs to:
protected
procedure ChangeScale(M, D: Integer); override;
The "Custom DPI Setting" window has a "Use Windows XP style DPI scaling". That might explain the different behaviour.
It turns out that the quirks in my Application when the system DPI changed away from the default 96 dpi value, are in three general camps:
Some of the application's controls use GDI and some controls use GDI+. There are some differences in how a GDI and GDI+ font renders at different DPI, at least in the controls I am using.
I use a framework called VCL in delphi. In this Delphi VCL framework, some forms have TForm.Scaled=true, and some have TForm.Scaled=false. Because it requires you to think about each control in a scaled form, it is very common to have things happen that you as a UI designer will find "ugly" or unacceptable in a Scaled form. Turning Scaled off, you are left with forms that are either stretched by Windows 7 itself, in high DPI settings (DPI Virtualization mode) or which appear small and therefore, ignore the user's "request" if you like, for a 144 dpi version of your 96 dpi UI. Other people might be using other framworks in other languages, or might even be using something really old fashioned, like the Dialog Box Editor for Visual C++, where you design dialogs in "Dialog Units", which is another way of trying to separate general dialog layout, from a 1:1 correspondence to pixels. Scaling, stretching, and layout controls are a general part of UI design that must be solved in a way that matches platform requirements. In my case, the VCL does a great job of letting me design a 96 DPI glass-enabled aero app, and works great at other DPI ratings, but most of my custom controls don't. So it's another reason to stick with the controls that come with the VCL: If you care about high DPI support, your job gets harder when you try to make high DPI support work.
DPI Virtualization in turn, is controlled by a manifest setting that you must expressly include in your application. Since my application already had a custom manifest (not the one that is included with your Delphi app when you click the Enable-windows-themes checkbox in the project settings), I was able to turn this DPI virtualization on and off again, and test my application in both cases. I found that my application was not ready to run without DPI Virtualization, and thus I left Windows to its default behaviour. Other people's applications might easily work with DPI Virtualization disabled, if they use 100% vcl controls, with forms that either use Form scaling, or some other technique, to appropriately size themselves (such as the VCL ExpressLayout control from DevExpress) at a variety of font sizes, and DPI pitches.). It seems to me, that in the end, the VCL is functional enough, but that for really industrial-strength solutions, a more advanced framework than the VCL is required, to comprehensively deal with issues like "high DPI environments" properly, and that third party controls are generally not designed to work even as well as the current VCL works, in these cases. Such framework concerns are very much in evidence in the WPF framework (Microsoft) and in Java (wing), but are not part of the classic "Win16/Win32 common control framework" of the VCL.
All in all, these changes are not that different (complex) now, than in the old days, when Windows XP and other versions of Windows offered you a choice of "font sizes", whereas now, the Windows 7 UI experience tries to bury the font point size options pretty deeply, and instead offers you a "text size" changing user interface that modifies the system DPI below the surface. Both of these ways of changing your user experience result in almost every user having problems with at least one major commercial application that doesn't look, or work correctly with the resulting changes. As ultra-high dot-pitch displays become more common in the consumer PC landscape, this problem will probably get worse and worse, and UI design around more suitable frameworks will be required.

Do you test on high-res screens and with non-standard/high contrast colour schemes?

I use a non-default Windows colour scheme on most of my machines, and have a laptop with a 124 DPI screen, which Windows is set to.
A lot of programs I tested or even use daily seem to have problems with that, showing for example non-standard sizes of controls, cut-off UI elements, unreadable text and so on. There is the whole range from slightly annoying to (nearly) unusable.
Now I feel that a lot of these issues are unnecessary. A simple test run on a high-resolution screen in a few colour schemes would show them, some of them are even very easy to correct (like always using clWindow, clWindowText and clBtnFace instead of clWhite, clBlack and clSilver). Some of them are harder, like proper control sizing.
So my question is: Do you try to follow the recommendations in the UI guidelines regarding system colours, sizing and spacing of UI elements, and font sizes and faces? Is testing for compliance to them part of your QA process? Do you even try to lay out your forms in dialog units instead of pixels, even though most of the IDEs (Delphi in my case) have pixel-oriented designers?
[EDIT]: On re-reading this after sleeping I notice that this question may look like an invitation for fruitless discussion. It is not meant that way, I'd definitely be interested in tools to help me create applications that conform to the UI interface guidelines, an area where I feel Delphi is letting me down a little. See also my own answer.
I definitely don't. It costs time that I prefer spending on improving the experience of many rather than the few who use non-standard windows settings. A few things I usually do, which should still fix some of these issues:
use clWindows etc. because that's the standard for Delphi controls anyway, so why change it?
place labels above entry fields rather than to the left, which should solve many size problems
make sure the forms resize properly, by setting the anchors
make sure the tab order is correct (which can become a major annoyance if not done)
But I certainly don't take the time to set up test computers with odd resolutions and colors, or even worse, change my development box to use them (which will screw lots of things that again take time to reset properly).
If a paying customer reports problems with non-standard settings, it depends on the customer whether they will be addressed. If he orders 100 licenses, his chances are good. If he uses these settings because he is visually impaired, his chances are good. If he makes it part of the requirement, I will do it, but charge for the additional work.
Today, so much software doesn't work properly at a non-standard DPI that I don't think it's worth trying to fix it. The trouble-shooting FAQs for many applications just instruct users to switch to a normal font size for related problems. Microsoft acknowledged lack of proper DPI support in 3rd-party software and redesigned the display scaling methods in Windows Vista, where all GDI operations are scaled on a low level instead of relying on applications being aware of the DPI setting.
Final answer: it depends on your software's audience. If your software is likely to be used by disabled users, it might be worth the effort.
Apart from using the proper colour constants for standard colours I invest some extra effort for applications that we need to use internally on high DPI screens, or where customers may need this.
I have a unit with helper functions for determining proper sizes and placement margins, which compute these from the default GUI font and the standard values in dialog units as given in the UI guidelines, and with helper functions to compute the maximum width / height of an array of controls, place controls, things like that. For fixed size forms and dialogs I calculate the placement of controls once after translating their text with GNU gettext, for resizable forms I do this in an OnResize handler.
This gives good results, is however time-consuming. I would like to have something like the wxWidgets sizer functionality, which automates resizing once the minimum size of a control is set. I have never seen something similar for Delphi, though.
I occasionally test it myself for large fonts, because my Vista laptop is set to Large Fonts. Colors, not so much, but I rarely specify colors on controls.
However, proper resizing is pretty hard. I usually set Forms scale to false, so as that they won't resize wrong.
There are a few tools for auto-resizing forms. I did look into them, but never got around to testing them properly:
TFormResizer
ElasticForm - ironically (given the component's area) most of the text in this page won't show up in Chrome...
JVAutoFormSize (in JVCL - doesn't seem to be very useful from what I read)

Resources