Carlo Barazzetta 116 Posted April 9, 2021 I have encountered this problem affecting High-DPI support with VCL-Styles Enabled. Scenario: Main monitor: 96 dpi Secondary monitor: 192 dpi Per monitor V2 enabled "Empty" application with main form with: Constraints.MinHeight = 400 Constraints.MinWidth = 300 WindowState = wsMaximized VCLStyle active. 1) Run (the form is full screen on the main monitor) 2) Minimize form. 3) Maximize form: BUG! appears in full screen on the main monitor but with a dpi of 192 !!! 4) Re-Minimize form. 5) Re-Maximize: appears in full screen on the main monitor but with a dpi of 96 (returns to correct scale) The three factors that determine the problem are: 1) presence of MinHeight and MinWidth Constraint 2) active VCL style 3) a secondary monitor with different dpi (on which the application never runs) Tested with D10.4.2 and Win10: anyone can confirm this problem or try it on other versions of windows? Using the AfterMonitorDpiChanged event, on the first call after "maximize" the NewDPI value is 192, on the second call returns to 92. the project: program StyleTest; uses Vcl.Forms, UstyleTest in 'UstyleTest.pas' {Form5}, Vcl.Themes, Vcl.Styles; {$R *.res} begin Application.Initialize; Application.MainFormOnTaskbar := True; TStyleManager.TrySetStyle('Windows10 SlateGray'); Application.CreateForm(TForm5, Form5); Application.Run; end. the main form dfm: object Form5: TForm5 Left = 0 Top = 0 Caption = 'Form5' ClientHeight = 500 ClientWidth = 500 Color = clBtnFace Constraints.MinHeight = 400 Constraints.MinWidth = 300 DoubleBuffered = True Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'Tahoma' Font.Style = [] OldCreateOrder = False WindowState = wsMaximized OnAfterMonitorDpiChanged = FormAfterMonitorDpiChanged PixelsPerInch = 96 TextHeight = 13 end the main form pas unit UstyleTest; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TForm5 = class(TForm) procedure FormAfterMonitorDpiChanged(Sender: TObject; OldDPI, NewDPI: Integer); private { Private declarations } public { Public declarations } end; var Form5: TForm5; implementation {$R *.dfm} procedure TForm5.FormAfterMonitorDpiChanged(Sender: TObject; OldDPI, NewDPI: Integer); begin ; end; end. Share this post Link to post
Vandrovnik 215 Posted April 9, 2021 Does not happen on my PC (100 % and 150 % monitors). Windows 10 Pro Czech, 20H2, build 19042.906. Share this post Link to post
Nicola Tambascia 0 Posted April 9, 2021 I have the same problem. Windows 10 Home, 20h2, Build 19042.867 Share this post Link to post
Bill Meyer 337 Posted April 9, 2021 I was not involved, but I know we had some issues which were resolved by the info here: https://stackoverflow.com/questions/23551112/how-can-i-set-the-dpiaware-property-in-a-windows-application-manifest-to-per-mo#44009779 Share this post Link to post
Carlo Barazzetta 116 Posted April 9, 2021 12 minutes ago, Bill Meyer said: I was not involved, but I know we had some issues which were resolved by the info here: https://stackoverflow.com/questions/23551112/how-can-i-set-the-dpiaware-property-in-a-windows-application-manifest-to-per-mo#44009779 It's not a configuration problem because the application starts with correct DPI. At the first Minimize/Maximize receive a wrong DPI (the DPI of the secondary monitor), at the second Minimize/Maximize receive the correct DPI, and so on. But this appens ONLY if I'm using a VCLStyle and I have defined min constraint for the form and the current WindowsState is wsMaximized. Share this post Link to post
Bill Meyer 337 Posted April 9, 2021 22 minutes ago, Carlo Barazzetta said: It's not a configuration problem because the application starts with correct DPI. At the first Minimize/Maximize receive a wrong DPI (the DPI of the secondary monitor), at the second Minimize/Maximize receive the correct DPI, and so on. But this appens ONLY if I'm using a VCLStyle and I have defined min constraint for the form and the current WindowsState is wsMaximized. Understood. My recollection is that the misbehavior observed here was inconsistent. Sorry I don't have better details. What I do know is that after the fix was applied to the manifest, the system behaved as expected. Worth a try, I think. Share this post Link to post
Vincent Parrett 763 Posted April 10, 2021 I couldn't reproduce this here, although my second monitor shows as 144 dpi (4k at 150%). Share this post Link to post
Carlo Barazzetta 116 Posted April 10, 2021 7 hours ago, Vincent Parrett said: I couldn't reproduce this here, although my second monitor shows as 144 dpi (4k at 150%). Thanks for the test, I've changed my secondo monitor to 144dpi but the problem is the same... I've upgraded my Windows10 version to 20H2 build but the problem still persist... Share this post Link to post
Vandrovnik 215 Posted April 10, 2021 Can you upload whole project with all the settings you use? Share this post Link to post
Carlo Barazzetta 116 Posted April 10, 2021 Zip of Project and video demo. HighDPI_test.mp4 HighDPI-test.7z Share this post Link to post
Vandrovnik 215 Posted April 10, 2021 Confirmed, problem appears 🙂 1) Monitor 1 @ 150 % Monitor 2 @ 100 % Monitor 2 is my primary monitor When app is on monitor 2 (primary, 100 %), it works fine. When app is on monitor 1 (not primary, 150 %), problem appears - app alternates between 150 % and 100 % zoom on this monitor 2) Monitor 1 @ 100 % Monitor 2 @ 150 % Monitor 2 is my primary monitor When app is on monitor 2 (primary, 150 %), it works fine. When app is on monitor 1 (not primary, 100 %), problem appears - app alternates between 100 % and 150 % zoom on this monitor Share this post Link to post
Carlo Barazzetta 116 Posted April 10, 2021 (edited) 1 hour ago, Vandrovnik said: Confirmed, problem appears 🙂 1) Monitor 1 @ 150 % Monitor 2 @ 100 % Monitor 2 is my primary monitor When app is on monitor 2 (primary, 100 %), it works fine. When app is on monitor 1 (not primary, 150 %), problem appears - app alternates between 150 % and 100 % zoom on this monitor 2) Monitor 1 @ 100 % Monitor 2 @ 150 % Monitor 2 is my primary monitor When app is on monitor 2 (primary, 150 %), it works fine. When app is on monitor 1 (not primary, 100 %), problem appears - app alternates between 100 % and 150 % zoom on this monitor But in my configuration the primary monitor is monitor 1 with 96 DPI, the application is on monitor 1 (the primary) and the bug is present also on primary monitor. Edited April 10, 2021 by Carlo Barazzetta Share this post Link to post
Carlo Barazzetta 116 Posted April 11, 2021 At the moment I've resolved rewriting WMGetMinMaxInfo handler using other variables (MinFormWidth, MinFormHeight, MaxFormWidth, MaxFormHeight: Integer;) into my base form of the application. procedure TBaseForm.WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo); var LMinMaxInfo: PMinMaxInfo; begin if not (csReading in ComponentState) then begin LMinMaxInfo := Message.MinMaxInfo; with LMinMaxInfo^ do begin with ptMinTrackSize do begin if MinFormWidth > 0 then X := MinFormWidth; if MinFormHeight > 0 then Y := MinFormHeight; end; with ptMaxTrackSize do begin if MaxFormWidth > 0 then X := MaxFormWidth; if MaxFormHeight > 0 then Y := MaxFormHeight; end; ConstrainedResize(ptMinTrackSize.X, ptMinTrackSize.Y, ptMaxTrackSize.X, ptMaxTrackSize.Y); end; end; inherited; end; Share this post Link to post
balabuev 102 Posted April 13, 2021 (edited) I can confirm the issue with the configuration similar to yours: Monitor 1 - 100% (primary) Monitor 2 - 125% App's main form is shown on the primary monitor (1). The bug happens if monitors are related like this (monitor 2 has negative X coordinates in its area) The bug does not happen if monitors are related like this: Edited April 13, 2021 by balabuev Share this post Link to post
Vincent Parrett 763 Posted April 13, 2021 Ok I can now reproduce this here after reading the above, when I tried it before I didn't move it to monitor 1 (2 is my primary). Even worse, when minimizing it the second or third time, it jumps over to the other monitors task bar, but restores to the correct monitor. Something is very broken with monitor detection. I'm seeing a similar issue with menus and vcl styles when moving between monitors - depending on which monitor a menu is first used on it will either be displayed too small or too large - it seems like it's finding the wrong monitor to get the dpi. I've been trying to debug this for days but it's very difficult to debug since that seems to impact on the behaviour. Share this post Link to post
Vincent Parrett 763 Posted April 14, 2021 So I added some debugging info to see what is going on, iterating the screen.monitors collection procedure TForm3.ListMonitors; var i : integer; monitor : TMonitor; begin Memo1.Lines.Clear; for i := 0 to Screen.MonitorCount -1 do begin monitor := Screen.Monitors[i]; Memo1.Lines.Add('Monitor idx : ' + IntTostr(i)); Memo1.Lines.Add('Monitor num : ' + IntTostr(monitor.MonitorNum)); Memo1.Lines.Add('Monitor primary : ' + BoolToStr(monitor.Primary,true)); Memo1.Lines.Add('Monitor ppi : ' + IntToStr(monitor.PixelsPerInch)); Memo1.Lines.Add('Monitor size (Top,Left,Width,Height) : ' + IntToStr(monitor.Top) + ',' + IntToStr(monitor.Left) + ',' + IntToStr(monitor.Width) + ',' + IntToStr(monitor.Height)); end; Memo1.Lines.Add(''); monitor := Screen.MonitorFromWindow(Self.Handle); Memo1.Lines.Add('Current Monitor : ' + IntToStr(monitor.MonitorNum)); end; My primary monitor is 1 (96dpi) which is to the left of 0 (144 dpi) - this is after moving the window to monitor 0 (the one on the right). Monitor idx : 0 Monitor num : 0 Monitor primary : False Monitor ppi : 144 Monitor size (Top,Left,Width,Height) : 0,2560,3840,2160 Monitor idx : 1 Monitor num : 1 Monitor primary : True Monitor ppi : 96 Monitor size (Top,Left,Width,Height) : 0,0,2560,1440 Current Monitor : 0 After minimizing and restoring (so still on monitor 0) Monitor idx : 0 Monitor num : 0 Monitor primary : False Monitor ppi : 144 Monitor size (Top,Left,Width,Height) : 0,2560,3840,2160 Monitor idx : 1 Monitor num : 1 Monitor primary : True Monitor ppi : 96 Monitor size (Top,Left,Width,Height) : 0,0,2560,1440 Current Monitor : 1 Clearly Screen.MonitorFromHandle is returning the incorrect monitor - I suspect this is the cause of the issues I'm seeing with menus too. Looking at the code behind MonitorFromHandle I can't see what the problem is, I suspect this may be a windows bug? Share this post Link to post
Vincent Parrett 763 Posted April 14, 2021 More info If I change my debug output to do this monitor := Screen.MonitorFromPoint(Self.BoundsRect.CenterPoint);// MonitorFromWindow(Self.Handle); Memo1.Lines.Add('Current Monitor : ' + IntToStr(monitor.MonitorNum)); It always give the correct monitor. Current Monitor : 1 Windows Bounds (Top,Left,Bottom,Right) : -8,-8,1408,2568 Current Monitor : 0 Windows Bounds (Top,Left,Bottom,Right) : -11,2549,2111,6411 Those top/left values on monitor 0 looked odd, so I turned off themes Current Monitor : 1 Windows Bounds (Top,Left,Bottom,Right) : -8,-8,1408,2568 Current Monitor : 0 Windows Bounds (Top,Left,Bottom,Right) : -11,2549,2111,6411 So that's not it, some googling found this is explained here - https://devblogs.microsoft.com/oldnewthing/20120326-00/?p=8003 So I'm no closer to figuring this out. 1 Share this post Link to post
Vincent Parrett 763 Posted April 14, 2021 I'm getting nowhere with this - something is causing windows to send WM_DPICHANGED when restoring the window - according to the documentation for WM_DPICHANGED the message is sent when The window is moved to a new monitor that has a different DPI. The DPI of the monitor hosting the window changes. We are definitely not changing the dpi of any monitors, so that suggests that windows thinks the window has changed monitors. I've been looking though the vcl source but cannot see where that is happening. Since this only happens with vcl themes enabled I think it's safe to say this is probably not a windows bug. @Carlo Barazzetta did you report this yet? If so post the number here so we can vote for it. Hopefully we can find a workaround for this because I guarantee I will have customers reporting this as an issue with my software otherwise. Share this post Link to post
Vincent Parrett 763 Posted April 14, 2021 Last one before I give up on this for today. It seems to be caused by TFormStyleHook.WMWindowPosChanging calling Form.Constraints := FRestoringConstraints; - this causes a flurry of calls to SetBounds - proving difficult to debug, but at some point the incorrect bounds are set and windows then fires off the dpi changed message -while debugging I saw the window jump and then the dpi change message fired. Share this post Link to post
Lars Fosdal 1793 Posted April 14, 2021 The BDS IDE itself also goes catatonic for a long time when changing DPI. The style management code in VCL SUCKS! Share this post Link to post
Carlo Barazzetta 116 Posted April 14, 2021 2 hours ago, Vincent Parrett said: I'm getting nowhere with this - something is causing windows to send WM_DPICHANGED when restoring the window - according to the documentation for WM_DPICHANGED the message is sent when The window is moved to a new monitor that has a different DPI. The DPI of the monitor hosting the window changes. We are definitely not changing the dpi of any monitors, so that suggests that windows thinks the window has changed monitors. I've been looking though the vcl source but cannot see where that is happening. Since this only happens with vcl themes enabled I think it's safe to say this is probably not a windows bug. @Carlo Barazzetta did you report this yet? If so post the number here so we can vote for it. Hopefully we can find a workaround for this because I guarantee I will have customers reporting this as an issue with my software otherwise. No, I haven't reported the BUG yet, I was hoping to be able to find the problem and also provide a possible solution ... Share this post Link to post
balabuev 102 Posted April 14, 2021 Handling of maximized form state is bugfull even without multi-monitor setup, and even without VCL Styles. Use the following code with a simple memo on a form: procedure TForm1.FormResize(Sender: TObject); begin Memo1.Lines.Add(Width.ToString + ', ' + Height.ToString + ', ' + IsIconic(Handle).ToString); end; Run the project and maximize the form. Minimize the form using taskbar app button. Maximize it back using taskbar app button. You will see that during minimize/maximize steps two OnResize events are fired with different Width/Height values. This was not the case in early Delphi versions, like Delphi 7; this was introduced later along with the Application.MainFormOnTaskbar feature. I'm sure that all such issues are related, and the problem is that they forget to check for IsIconic() whenever appropriate. Share this post Link to post
Vincent Parrett 763 Posted April 15, 2021 So today I took a copy of the TFormStyleHook (a bit of work due to use of protected methods on TCustomForm) and registered for my form class and started adding some outputdebug strings. (BTW, it's staggering how much code there is (with many corner cases) in the stylehook! This is what happens when restoring after minimizing on monitor 0 (the non primary monitor) TVSoftFormStyleHook.WMWindowPosChanging x : 0 y 0 w : 0 h : 0 flags[ SWP_NOMOVE + SWP_NOSIZE ] Form Bounds: Left: 3803 Top: 499 Width: 1302 Height: 675 TVSoftFormStyleHook.WMWindowPosChanging x : 2549 y -11 w : 3862 h : 2182 flags[ SWP_NOCOPYBITS + SWP_FRAMECHANGED ] Form Bounds: Left: 3803 Top: 499 Width: 1302 Height: 675 TVSoftFormStyleHook.WMWindowPosChanging x : 0 y 0 w : 0 h : 0 flags[ SWP_FRAMECHANGED + SWP_NOACTIVATE + SWP_NOZORDER + SWP_NOMOVE + SWP_NOSIZE ] Form Bounds: Left: 3803 Top: 499 Width: 1302 Height: 675 TVSoftFormStyleHook.WMWindowPosChanging : Applying FRestoringContraints TVSoftFormStyleHook.WMWindowPosChanging x : 0 y 0 w : 1302 h : 675 flags[ SWP_NOACTIVATE + SWP_NOZORDER + SWP_NOMOVE ] Form Bounds: Left: 3803 Top: 499 Width: 1302 Height: 675 SetBounds: Left: -32000 Top: -32000 Width: 1302 Height: 675 TVSoftFormStyleHook.WMWindowPosChanging x : -29440 y -32000 w : 0 h : 0 flags[ SWP_NOACTIVATE + SWP_NOZORDER + SWP_NOSIZE ] Form Bounds: Left: -32000 Top: -32000 Width: 1302 Height: 675 FormAfterMonitorDpiChanged : OldDpi : 144 NewDPI : 96 TVSoftFormStyleHook.WMWindowPosChanging x : 0 y 0 w : 0 h : 0 flags[ SWP_FRAMECHANGED + SWP_NOACTIVATE + SWP_NOZORDER + SWP_NOMOVE + SWP_NOSIZE ] Form Bounds: Left: -29440 Top: -32000 Width: 1302 Height: 675 TVSoftFormStyleHook.WMWindowPosChanging x : 0 y 0 w : 0 h : 0 flags[ SWP_NOMOVE + SWP_NOSIZE ] Form Bounds: Left: 2552 Top: -8 Width: 3856 Height: 2116 As you can see it is called at least once with co-ordinates that are on the other monitor (monitor 1) - which is why the errant WM_DPICHANGED is fired!! I tried to decode the flags as well I commented out this line in TVSoftFormStyleHook.WMWindowPosChanging and that seems to solve it, however we then no longer have any constraints. Form.Constraints := FRestoringConstraints; So while I can see what is happening, I haven't quite figured out why - it seems like the constraints are somehow lost when minimizing, however resetting them in WMWindowPosChanging is causing windows to believe the window has changed monitors. What a mess. Share this post Link to post
balabuev 102 Posted April 15, 2021 (edited) 9 hours ago, Vincent Parrett said: SetBounds: Left: -32000 Top: -32000 Width: 1302 Height: 675 This is the key line. Now let's think, where this strange "-32000" numbers may araise at all. I'm sure 99.9% that the numbers are received from a Windows API function call, such as GetWindowRect (or similar) in minimized window state (IsIconic(Handle) = True). Simple experiment proves it: procedure TForm1.Timer1Timer(Sender: TObject); var wr: TRect; begin if WindowState = TWindowState.wsMinimized then begin GetWindowRect(Handle, wr); OutputDebugString(PChar(wr.Left.ToString + ', ' + wr.Top.ToString)); end; end; So: 9 hours ago, Vincent Parrett said: As you can see it is called at least once with co-ordinates that are on the other monitor (monitor 1) With "-32000" left and top values the window is not, of course, within the area of your monitor 1, but this monitor becomes the closest one to the window. Edited April 15, 2021 by balabuev 1 Share this post Link to post
Carlo Barazzetta 116 Posted April 21, 2021 I've created the BUG report: https://quality.embarcadero.com/browse/RSP-33760 Please vote for it. bye Carlo 3 Share this post Link to post