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

The Problem:

  • in Xojo 2019R1.1 and other versions
  • when Application.SupportsHiDPI = True
  • and the system has multiple monitors with non 100% scaling
  • Setting a window’s Top,Left values gives the wrong location in some cases. It is noticeably bad on a 3 monitor system when the third monitor is separated from the Main monitor by a second monitor, e.g. [ Screen 0][ Screen 1][Screen 2] horizontally or
    [Screen 2]
    [Screen 1]
    [Screen 0]
    Vertically.

When the 3 screens are both touching the main monitor, you may not see the bug, e,g [Screen 2][Screen 0][Screen 1]

This thread https://forum.xojo.com/49753-screen-behavior-in-windows-hidpi-builds-with-multiple-displays-/16 has a few solutions, but they require Declares and/or Plugins, and unforunately the example projects which were hosted on DropBox are now missing.

I have found a solution which seems to work, and doesn’t require Declares or Plugins.

The observation I made was that when you see the bug, the value returned by Window.Left or Window.Top is not what you set them to.

Therfore, it’s possible to do an iterative search solution which basically keeps adjusting the Window.Top and Window.Left values until they return the values you set them to.

do you have 3 monitors and each has different scaling using Windows 10 ?
that seems to be particularly troublesome

Edit - found a bug.

That’s relocated to here: Monitors

Here’s an updated solution which seems more robust. The problem is that in some cases, the adjustment value overshoots and so you get an oscillation that never converges on the solution. I added extra logic so that as you get closer to the right location, the adjustment values get smaller, dropping to 1 pixel increments to avoid overshoot.

Public Function WindowSetLocationHiDPI(extends w as Window, x as integer, y as integer) as Boolean
  #Pragma DisableBackgroundTasks
  
  
  ' See https://forum.xojo.com/60697-screen-behavior-in-windows-hidpi-builds-with-multiple-displays-
  ' The Problem:  
  ' * in Xojo 2019R1.1 and other versions
  ' * when Application.SupportsHiDPI = True
  ' * On Windows 10
  ' * and the system has multiple monitors with non 100% scaling
  ' * Setting a window's Top,Left values gives the wrong location in some cases.   It is noticeably bad on a 3 monitor system when the third monitor is separated from the Main monitor by a second monitor, e.g. [ Screen 0][ Screen 1][Screen 2]  horizontally or 
  ' [Screen 2]
  ' [Screen 1]
  ' [Screen 0]
  ' Vertically.
  ' 
  ' When the 3 screens are both touching the main monitor, you may not see the bug, e,g [Screen 2][Screen 0][Screen 1]
  ' 
  ' This thread https://forum.xojo.com/49753-screen-behavior-in-windows-hidpi-builds-with-multiple-displays-/16 has a few solutions, 
  ' but they require Declares and/or Plugins, and unforunately the example projects which were hosted on DropBox are now missing.
  ' 
  ' I have found a solution which seems to work, and doesn't require Declares or Plugins.
  ' 
  ' The observation I made was that when you see the bug, the value returned by Window.Left or Window.Top is not what you set them to.
  ' 
  ' Therfore, it's possible to do an iterative search solution which basically keeps adjusting the Window.Top and Window.Left values until they return the values you set them to.
  
  
    w.left = x
    w.top = y
  
  
  if TargetMacOS or not app.SupportsHiDPI then
    return true // no need to adjust
  end if
  
  
  #if TargetWin32
    // Try to relocate window by homing in on the right values
    // given the weird cross-screen scaling issues we can't use the window left or top as the source of the new coordiantes
    // instead we keep them separately in lastX, lastY
    
    dim lastX as integer = X
    dim lastY as integer = Y
    
    
    
    
    dim nAdjustments as integer = 0
    while true
      dim dx as integer = w.left - x
      dim dy as integer = w.top - y
      
      if dx = 0 and dy = 0 then
        system.debugLog " Success! setting top,left to " +  " x,y = " + str(lastX) + "," + str(lastY)  + " in order to reach " +  " x,y = " + str(x) + "," + str(y) 
        return True
      end if
      
      
      
      nAdjustments = nAdjustments + 1
      system.debugLog "    Window is off by  " +  " dx,dy = " + str(dx) + "," + str(dy)  + " pixels, adjusting"
      
      
      
      // There are some screen configurations where we can get into an oscillation, so that the dX and dY values start increasing
      // This works around that problem by slowing down the search as we get closer, so that we go in 1 pixel increments:
      if dx > 32 and dx < 64 then 
        dx = 4
      elseif dx < -32 and dx > -64 then
        dx = -4
      elseif dx > 0 and dx < 32 then 
        dx = 1
      elseif dx < 0 and dx > -32 then
        dx = -1
      end if
      
      
      lastX = lastX -dX
      lastY = lastY -dY
      
      w.left = lastX
      w.top = lastY
      
      
      
      if nAdjustments > 100 then
        system.debugLog "     Failed after " +str(nAdjustments) + " attempts"
        return false // failed
      end if
    wend
  #endif
  
End Function

[quote=491915:@Norman Palardy]do you have 3 monitors and each has different scaling using Windows 10 ?
that seems to be particularly troublesome[/quote]

Yes - my first solution (now deleted) was failing with some really weird monitor size/locations (e.g. 175% where each monitor was down and to the left of the prior one). The newer version solves that issue, but there may be more edge cases that I haven’t caught.

I’d love if you can do some testing and see if you can make it fail.

Edit - still failing, will try another version.

After more tries, I finally realized that this is not going to work. The problem is that with some screen arrangements, there seems to be a discontinuity in coordinates at the level of the 0,0 point for the main monitor. At that point, the coordinates get really wacky, as shown here. Notice how both the X and Y values are jumping around.

So I’m pretty sure my idea of searching by iteration is simply not going to work.

Did you try @Jürg Otter s code ?
He and @JulianS (I think) spent a pile of time digging into windows declares & such as far as I recall

Thanks, Jürg - I finally looked at your code and it looks like we came up with similar solutions.

What I ended up doing was creating custom classes, cWindow and cScreen which are subclasses of the Xojo classes.

  • cScreen starts with the Xojo Screen class, but then uses declares to EnumDisplayMonitors() to get the actual sizes.
  • cWindow uses declares to SetWindowPos() when changing the window’s top or left location, and declares to GetWindowRect() upon .moved, resized or .resizing events to update the .top or .left values.

I also tried something “clever” by overriding window.width and window.height in my window subclass to use the actual pixel values. Do not recommend - this was a disaster - it turns out that the Xojo framework calls .width and .height in many places, and by overriding them, it caused many weird problems - in particular, control nesting and auto-sizing was broken in weird ways.

So my final solution was this:

  • for Window classes, always use the “real” .left and .top values calculated from the declares
  • for Window classes, always use the virtual .width and .height values that Xojo uses.
  • for Screen classes, always use the “real” physical .left, .top, .width and .height values
  • The only “gotcha” therefore is when doing window bounds calculations against Screens, you have to pay attention to the fact that window is using virtual width,height and the screen is using physical.