Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get a borderless child window to re-scale to current screen in multi-monitor setup?

Tags:

windows

qt

qml

My app has a main window which creates and opens an instance of a subclass of a QML Window {} using createObject(). This window has its flags: set to be a borderless window (I've added code so that it can be grabbed and dragged around).

When I attach a monitor to my laptop and set its font scale factor to 125% (or 150%), when I drag my main window over to the second monitor, you can see it suddenly "snap" to the larger size when it reaches the halfway point. Likewise, when I drag it back to my laptop screen it again "snaps" to the smaller size when I get halfway over (this behavior is what I want, so no problems here).

My problem is that when I drag my created borderless window over into the monitor, it keeps the original 100% scale factor and does not "snap" to a larger size. If I drag my main window over to the monitor, it gets larger but the borderless window remains at the smaller scale; only when I grab the borderless window and move it slightly does it suddenly "snap" to the larger scale size. The same thing happens in reverse - if I then drag the borderless window back onto the laptop, it remains at the larger size until I drag the main window back over and then move the borderless window slightly (at which point it suddenly "snaps" to the smaller size).

So it appears that this created Window uses the scale factor of the screen that the parent window window that created it is currently in, even if it is in a different screen itself.

Is this happening because the Window is borderless? (I'm about to test this but my build process is incredibly slow) Or is there any way to set this borderless Window up so that it detects that it is crossing into a new screen and re-scales itself (in the same way that my main window does)?

Update: I just ran a test giving my Window a native titlebar, and with a titlebar the window instantly adopts ("snaps to") the scale factor of whichever screen it happens to be in, just like my main window (and independent of the main window's scale factor).

So is there any way to duplicate this auto-scaling window behavior with a borderless window? Some flag I need to call, or some method(s) I need to call to get the OS to rescale the window?

Update 2: I tried out Felix's SetWindowPos solution. It does move the window, but this does not fix the scaling problem - the behavior of the frameless window is the same and it still does not correctly pick up the scaling factor of the screen it is in.

I am running a test using MoveWindow instead of SetWindowPos to see if that affects anything [edit: MoveWindow does not work, either - same problem]. Then I'm going to try SendMessage or PostMessage along with NoBugz' suggestion of the WM_DPICHANGED message.

Any other suggestions are welcome.

Update 3: I just created a quick C# app (winforms) to see if the same problem occurs with that, and it doesn't - when a borderless form in the C# app is dragged over into the other monitor, it immediately picks up the scale factor change. So it appears this is a Qt problem.

Update 4: See my answer below for a working solution to this problem (if a bit of a hack).

like image 911
MusiGenesis Avatar asked Aug 09 '18 00:08

MusiGenesis


1 Answers

So as far as I understand, your current goal is to move the window via the WIN-API. You will have to do so via C++. The approach would be:

  1. Pass the QML Window to a C++-Method exposed to QML as a QQuickWindow (The QML window instanciates that type, as seen in the documentation)
  2. Use QWindow::winId to get the native HWND
  3. Call the SetWindowPos WIN-API method to move it

Code sample (C++-part):

// the method
void moveWindow(QQuickWindow *window, int x, int y) {
    HWND handle = (HWND)window->winId();
    Q_ASSERT(handle);
    ::SetWindowPos(handle, HWND_TOP,
                   x, y, 0, 0,
                   SWP_NOSIZE | SWP_NOZORDER);
}

// assuming "moveWindow" is a member of "MyClass"
qmlEngine->rootContext()->setContextProperty("mover", new MyClass(qmlEngine));

Code sample (QML-part):

// call this method as soon as the drag has finished, with the new positions
mover.moveWindow(idOfWindow, xPos, yPos);

Note: I would recommend you try out calling this only after the drag was finished (and move the window as you do right now until then). If that works, you can try out what happens if you call this during the drag instead of changing the x/y of the window.

like image 198
Felix Avatar answered Sep 22 '22 20:09

Felix