Screen() behavior in Windows HiDPI builds with multiple displays/scaling factors

Just thought I’d post a note here about case #53050 that I filed a couple of weeks ago, and hoping their might be some relief from this issue in R3.

Screen(), as well as the Window properties, for left/top/width/height, don’t behave properly on Windows systems, when the application is built as HiDPI and when the user has a multi-display configuration with any difference in scaling factors between them. This isn’t a new problem.

It boils down to, it’s difficult, if not impossible, to programmatically move a window from one display to another; and do things like, center it, or expand it to fill.

Building the Windows version as non-HiDPI defeats the purpose of all the work that went into supporting Retina/HiDPI on both platforms. Telling users to temporarily set all of their displays to the same scaling factor when using the application isn’t realistic. It just needs to be able to trust the behavior of Screen() and the Window properties and work correctly.

The solution should be in the framework. Screen() should be adjusted to provide a continuous “virtual” desktop at a scaling factor, I believe, that would be normalized to Screen(0), which the user has designated as “main”. The left/right/width/height values for all of the Screen()s should be virtualized and contiguous from left to right (and top to bottom, although it’s reasonable to assume that users aren’t stacking display space vertically). The equivalent properties for the usage of Window should be on the same scale and applicable to the same contiguous desktop space that the corrected Screen() function would provide.

Has anyone else been trying to use Screen() in these configurations and managed a solution? What I’ve been in the process of doing, as a way of trying to work around this, is undo the flaws in what Screen() is currently doing, then use some Windows platform info to reconstruct what Screen() should be doing, and to come up with a fixed MyScreen() function to use instead. With some more work this should be possible, but then there’s as an issue of how the Window properties are also incorrect in some situations, and I’m not sure I can work around both areas.

One thing that’s necessary is a way of knowing what the scaling factor is for each display. Right now, the Screen() function doesn’t provide this. There’s the scaleFactor property of the Window, but that’s also not behaving “correctly” in these configurations. One way I’ve found to get the ACTUAL scaleFactor for a Window, no matter what display it sits on, is to do this:

// GetDpiForMonitor is available only in Windows 8.1 or later; there ought to be some equivalent for Windows 7 support…
Soft Declare Function GetDpiForMonitor Lib “Shcore” Alias “GetDpiForMonitor” (hMonitor As UInteger, dpiType as Integer, byref dpiX as UInteger, byref dpiY as UInteger) as Integer

dim realScaleFactor As Single
realScaleFactor = me.ScaleFactor // The scaleFactor of the WINDOW, SHOULD be the same as the scaleFactor of the display it’s currently positioned on
dim dpiX, dpiY As UInteger
call GetDpiForMonitor(ourGetMonitorHandle(), 0, dpiX, dpiY) // We use another function of ours to get the display’s handle
realScaleFactor = dpiX/96.0 // This is the actual scaleFactor of the display this WINDOW is in. Will be different from me.ScaleFactor if the display’s SF <> 100%

I haven’t looked at your case, but could you describe what the actual problem is?

P.S. - when posting Feedback reports, just select Edit > Copy in Feedback and paste the link into the forum. It will be automatically turned into a valid link.

I found there were a lot of problems on windows and the screen scaling. I found you must set top and left first then width and height otherwise you will be in the wrong position. This was on non hidpi. With hidpi and 2018r2 there was no way to get the window into the right position. Even the xojo ide started up in wrong location with multiple screens. I haven’t tested on r3 but we need this working as well before we can do a windows hidpi version.

Link to forum post I sent a few months ago

Exactly - the same problem.

You “can” work around it with 2 displays; I haven’t gotten into 3-or-more; even for 2, it’s tricky. 2 displays: Main on the left (secondary on the right with positive left offset: or, Main on the right (secondary on the left with negative left offset).

To fix either of these two cases, you need to know the real scaleFactor of the display (see code snippet in my original post).

Case 1: Main on right, secondary on left.

The problem in this case is that Xojo is comes up with a negative offset for Screen(1) (secondary) which is wrong. For example: Main is 4096 wide (Retina) with sf = 200% to accommodate that; and secondary is 1920 wide (non-Retina) with sf = 100%. (I think I wrote this up in my Feedback case).

To “fix” Screen(1)'s left value (which Xojo says is -960 instead of -1920 as it should be), “fix” Xojo’s error by multiplying by Main’s real scaleFactor (2.0) and then complete the “fix” by dividing by the “right” scaleFactor for Screen(1) (100%, which does nothing, but if the secondary sf <> 100% then this division is important and necessary).

Window coordinates when placed on either Screen(0) or (1) are already virtualized and they’re correct.

So in general for this case (main = screen 0, secondary = screen 1): FixedScreen(1).Left = (Screen(1).Left * realScaleFactorScreen0) / realScaleFactorScreen1

This will work for any combination of scaleFactors and resolutions for the two screens.

Case 2: Main on the left, secondary on the right

This is weird because now, Screen(1).Left etc is correct, and it’s the Window coordinates that are incorrect when place onto Screen(1) - they’re not virtualized.

It’s possible to work around this by using the same patch as for Case 1, 'just because". This will multiply “up” Screen(1).Left (making it technically incorrect) BUT it bring it into the technically incorrect (absolute rather than virtual) coordinates that Window needs when placing it on Screen(1) and then that Window.Left will work correctly.

As you can see, neither of these patches is particularly comfortable, and they’re “good” only when you have only 2 displays.

With more than 2, then what happens - it’ll get more complicated. I think this has to be fixed in the framework.

My fix, which I’m still not done with (and hoping not to have to complete), is going to be, first: look at the entire Windows extended desktop and do what the framework should be doing. Start at Main; use its scaleFactor to normalize the others; and work to the left (if there are any screens to the left of it) and then to the right ("" “”). Come up with a fixed MyScreen replacement that will provide screen coordinates that can be used to set Window properties and position them on any screens in the extended desktop.

I think MyScreen will have to be weird in order for this to work. For one or more screens to the left of Main (if “any”), their values will need to be properly virtualized and normalized to Main’s scaleFactor (as in Case 1 above). For one or more screens to the right of Main (if “any”), their values will need to be “improperly” un-normalized to Main and scaled UP to absolute values to use for the Window properties that “just because” need to be in absolute coordinates since they’re to the right of Main. The behavior for screen(s) if any to the right of Main is particularly uncomfortable because now we’re making MyScreen do the “wrong” thing to adapt it to Window in those screens doing the “wrong” thing. (Two wrongs, in this case, are need to make a “right”)

While I am not affected by this issue (no HiDPI monitors). I do have 4 monitors in a grid configuration with the bottom right monitor being the main one. I also have many clients that add monitors to laptops where the main monitor is the external one and the laptop will either be on the right or left (usually running their mail client).

I can now purchase 32" High DPI monitors for $US200 or Quad DPI for $US233 so I’m expecting to run into this problem sooner rather than later.

It would be nice to get this fixed.

Greg O’Lone, if I boil it down to the essence:

  • Windows application, built with HiDPI turned on
  • User with multiple displays set up as an extended desktop (no mirroring!)
  • And, the user’s scaling factor is NOT the same for all displays

When this is all the case:

  • The Screen() function doesn’t provide the proper information for display(s) that are to the LEFT of whichever one the user has designated as “main” (that will be Screen(0) and its top, left corner is at (0,0). Top, left for those screens “will” be negative but will be incorrect. It looks to me like the framework is simply taking their absolute values and dividing them by the scaling factor of Main - which is wrong. For those displays, however, the properties of a Window that’s been placed on that a screen (or that an application would use to move and position the Window onto that screen) for left, top appear to be correct (i.e. the properties, rather than being “absolute”, are virtual coordinates that are normalized based on the scaling factor of Main as well as the display-to-the-left that the Window is associated with)

  • The properties of a Window that are on a display that’s to the RIGHT of Main are “incorrect” (absolute coordinates, rather than taking into account the pixel dimensions and scaling factor of display(s) that are to the LEFT of it. Curiously, the behavior of Screen() in this situation appears to be correct (at least, in a simple example where there are only 2 displays; I haven’t set up and tested this in 3 display configurations yet)

Easy way to see it going wrong:

  • Set up a 2 display Windows configuration with extended desktop

  • Make Main the rightmost of the two, and set its scaling factor to 200%. (Example: Retina display that’s 4096 wide, at 200%, is 2048 pixels wide in virtualized coordinate space).

  • Set the other display’s scaling factor to 100%. (Example: non-Retina display that’s 1920 wide, at 100%, is 1920 wide virtualized as well)

  • Look at Screen(1).Left. It’ll be wrong. (-960, but 1/2 the value it “should” be, -1920) To place any Window on that screen? If you take values in the Screen(1) range, it doesn’t work - the window will be shifted halfway across into Main. So this is a total fail.

  • To “correct” in this specific configuration, multiply by Screen(1).Left by Main’s scaling factor (2, or 1.75, or 1.5, or whatever the user has set it to) and then divide by its own (in this case 1.0, but otherwise, whatever it’s been set to) and the resulting value is then “correct”, and can be used in placing and positioning a Window. (The Window function in this specific case needs “correct” virtualized coordinates for Screen(1) for it to be properly “placeable” via its properties).

  • Next, swap the configuration. Make Main the leftmost of the two. Keep Main at 200% and Screen(1), which is to the right of it, at 100%.

  • Now, you’ll find that Screen(1).Left is… correct! It’s 2048 (virtualized), as it should be!

  • BUT, a Window placed on that screen? Aligned to the left edge (if you manually drag it there and look at it’s properties), it thinks “Left” is 4096. This absolute, rather than virtualized. So for a Windows that’s to the right of Main, it’s ignoring the scaling factor of any display(s) that are to the left of it, and to set/position it, you need to use values in the “absolute” rather than “virtual” range.

This is a big problem, for any use of Xojo windows in a Windows multi-display, HiDPI configuration. It only “works” if all of the displays have the same scaling factor.

The act of -physically dragging- Xojo windows between these displays works correctly. The problem is with Screen() and properties of the Xojo window, which are needed to be able to do these things programmatically.

The problem sounds relatively simple when there are only 2 displays. It worsens when they are more, because, there will be a chain “reaction” of all the display pixel dimensions, going left to right and top to bottom, that needs to be accounted for. You could have 4 displays left to right, and their scaling factors could be: 125%, 100%, 200%, 175%. Or any combination. Main could be any one of them - it could be leftmost, rightmost, or either of the two in the interior.

What should be happening, I think, is that Main should always the starting point (Screen(0)). All other Screen()s should have their coordinates normalized to Main, and all Xojo window properties should likewise be normalized, so that they line up with the normalized Screen() properties. (this normalization to Main is what happens indirectly when: all displays for a Xojo HiDPI build just so happen to have the same scaling factor; and also, when a Xojo application is built with HiDPI turned off)

All of the displays in the Windows extended desktop should have contiguous coordinates in normalized coordinate space so that their properties can be examined; and that Xojo windows can be placed/positioned/centered/resized/full screened etc. on any of them.

Feedback case 53050:

Does this happen on all Windows versions or just Windows 10?

It should also affect Windows 8 (I’ll test this in a VM later today and report back later). I’m not sure about Windows 7 but I’ll also test that in a VM later today. It’s possible Windows 7 might be “ok” but if so, probably only if HiDPI isn’t functional in Windows 7 regardless of how the application is built.

You’d think, but not necessarily. Each version of Windows seems to have its own idea about how HiDPI works.

Windows 8.1 “seems” to be OK. I can’t set up exactly the same configurations (Boot Camp, so “real” hardware) that I’ve been using for Windows 10 system, but i updated an 8.1 VM and took it to full screen, extended display mode in the latest Parallels 14.

Windows 8 doesn’t let the user choose “scaling factor” for displays; instead, there’s a popup with resolution choices, and a “recommended” (I’m assuming native) setting for each attached display. I made the main display rightmost in a 2 display setup (worked fine); then changed the resolution of main to a smaller value (produced larger icons and whatever internals Windows 8 is using to implement that change - which in Windows 10 would be, a scaling factor <> 100%); and having done that, locating the secondary (leftmost) display and centering a window in it worked correctly.

It “could be” that Windows 8 keeps all scaling factors the same and then “fakes” these changes in screen resolution (to non-recommended, non-native values) internally - so what Xojo would see in Windows 8 is what fixing the framework for Windows 10 would produce - an extended desktop that’s contiguous in virtualized coordinates, based on all of the user’s choices for screen resolution for all displays in the extended desktop. This seems to be what the behavior is, in which case, Xojo doesn’t have this issue in Windows 8.

So please: fix this in Windows 10…! It means fixing the framework so that Screen() and Window properties in Windows 10, with a mix of user specified scaling factors and native screen resolutions, will behave correctly, as it seems to be doing in Windows 8.

(“Working” in Windows 8, and “broken” in Windows 10, isn’t acceptable in the real world. Windows 10 is the primary active user base at this point - most if not all people will have abandoned 8 at this point for 10, and rightly so).

This is an old issue that I brought up a long time ago (don’t remember to who or where, and didn’t make a feedback report) and did, at the time, effect Windows 8/8.1. Under the circumstances David outlined above, you can easily see this by having your window open on the main (right-most 200% scaled) display with a Left value less than 50% of the display width and call Popup() on a MenuItem from a control in the window.

I just spent a while flipping “off” the HiDPI switch and retesting. As expected, everything “works properly” under Win10 this way, regardless of the screen arrangement and individual screen scaling factors.

What it means to me at the moment is: I can’t use HiDPI for a potential production release build without going down the route of making my own fixed Screen() function and getting it to “line up” correctly with how Window properties behave when a Window is placed and positioned on that screen - and even then, not feeling this will be solid enough to release. It’s a shame, because Retina support works fine on the Mac and there are many graphics in the UI that are nice and ready-to-go at upscaled resolutions. A fix in the framework - which is where it “should” be fixed - is the right thing to wait for.

I’m hopeful…! It’s easy for me to test: a fix in the framework, I can just switch HiDPI back on, rebuild, and run it. It’ll be obvious whether it starts doing the right thing.

In the meantime: what would be useful in the r3 alphas would be: separate, per-platform settings having to do with higher DPI builds. Right now, the HiDPI switch covers both OSX and Windows and you have to remember to set it one way or the other. How about adding a new “Retina” switch that applies only to OSX builds; to go along with the existing “HiDPI” switch (and have it only apply to Windows builds)? (Just like, there’s now a Dark Mode switch in the same part of the IDE, which only applies to OSX builds but which would be ignored for Windows builds - there’s room for some platform specificity there).

Moving this thread out of Pre-release as it is not specific to the current release cycle.

I have found that the design size of the window affects the correct placement on 2018r3.
Windows 10 with 2 monitors, main at 200% and second at 100% scaling placed to left with tops aligned.
Simple test app which creates a new modal window and places it at screen(1) left,top,width,height.
IF the design size of the modal window is 600x400 it works ok. if the design size is say 2000x2000 (which is larger than the res of the second screen) it will incorrectly position it 50% across the second screen. So the design size is being used to calculate which scaling to use, which sort of makes sense as it is probably done in the constructor.

Setting width and height first have no effect.

I am setting the design size to be large to fix a flicker issue on windows and opengl surface.

If the windows are not aligned along top then the window is ALWAYS placed as if it was, adjusting the screen alignments in the control panel, will cause the window to snap to correct position.

I’m trying to find a way to know on which Screen a Window currently is.
Since in a Multi Monitors (with different ScaleFactors) situation, it’s currently not possible to do that with Xojo’s Screen(x).Left/Top/Width/Height and Window.Left/Top/Width/Height (such as explained above), I wonder if there’s a way via Declares?

Getting the Handle of the current Window is quite easy:

Declare Function MonitorFromWindow Lib "User32" (hWnd As Integer, dwFlags as UInt32) As Integer Const MONITOR_DEFAULTTONULL As UInt32 = &H0 Const MONITOR_DEFAULTTOPRIMARY As UInt32 = &H1 Const MONITOR_DEFAULTTONEAREST As UInt32 = &H2 Dim hMonitor as Integer = MonitorFromWindow(self.Handle, MONITOR_DEFAULTTONEAREST)

That allows to read infos such as

Declare Sub GetScaleFactorForMonitor Lib "Shcore" (hMon As Integer, ByRef pScale as Integer) Dim pScale As Integer GetScaleFactorForMonitor(hMonitor, pScale)
(even though I don’t understand why I get 180 when the Monitor is set to have 200% Scale… anyway).

So: I have the Monitor’s Handle. But… how do I map this to the "Screen Number (i)", so that I know it’s on Screen(i)?
How can I query all the Monitor Handles, so that I know that: Screen(0) <-> HandleA, Screen(1) <-> HandleB, …?
I guess the answer lies in EnumDisplayMonitors. But I don’t know how to do that.
Maybe someone with more WindowsAPI/Declare experience can try?

My goal is to be sure that “Window -> MonitorHandle” is on what Xojo represents with Screen(x). Any help is appreciated…

MBS has WindowsDisplayMBS class which returns a lot of information, which is correct as well, if you have access to those plugins.

I know, it does… :wink:
But for this particular case I don’t want to use a Plugin… That’s why I’m asking here.

Hello Jurg,

Here is a program that confirms the 180% when you are at 200% scale, and also a way to calculate the scale with SetProcessDPIAware.

Some of the details of HiDPI are mentioned in this Xojo blog: Writing High-DPI Aware Windows Apps

It is presumed that the native primary native resolution is 96 DPI, and this can be retrieved with another API call called GetDeviceCaps. I just used the default 96 value. My system has 1-HiDPI screen and 2-non HiDPI screens. Moving the cursor through the different screens will then show the monitor number that the cursor is located, and also the HiDPI ratio using the default 96 dpi value. Global mouse coordinates are also shown so that you can quickly determine where the screen origin is located. The timer refreshes the information every 200 milliseconds. I added the SetProcessDPIAware function in the Window1 Open event.

This is all with Windows 10 API declares and it works on my machine with Xojo 2018 r3. LOL, don’t mind the spaghetti code, as I quickly put this together.

Let me know if this works on your computer. :slight_smile: