-
-
Notifications
You must be signed in to change notification settings - Fork 666
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved DPI Scaling on Windows and Fixed related Bugs #2155
base: main
Are you sure you want to change the base?
Conversation
I have added an event handler to detect DPI changes while the app is running. I have also modified Scalable class to:
The latest commit detects the DPI changes of the Primary Screen only. But this will be fixed in #1930, where the DPI scale factor of each screen will be detected individually and will be used to do the scaling. I have tested the latest commit and it correctly detects new DPI change and scales the elements accordingly. However, the fonts' don't seem to be using the latest DPI value and as such they are not being scaled when a new DPI change is detected while the app is running. This needs to be fixed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for looking into this, I think you're on the right track.
The latest commit detects the DPI changes of the Primary Screen only. But this will be fixed in #1930, where the DPI scale factor of each screen will be detected individually and will be used to do the scaling.
WinForms has some per-window events for detecting DPI changes, which I think would allow this to be fixed without requiring Toga to be aware of multiple screens. That would allow us to fix all the DPI issues together in this PR, so I've added a "fixes" link to the top comment.
A few more comments:
I have tested them previously and was thinking about using DpiChanged event & DeviceDpiNew: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.dpichangedeventargs.devicedpinew?view=windowsdesktop-7.0#system-windows-forms-dpichangedeventargs-devicedpinew But, none of these events trigger when the system DPI changes. Only the SystemEvents.DisplaySettingsChanged event is triggered consistently when system DPI changes. I am searching for a proper way to address this and will let you know as soon as I find a viable solution. |
I have added support so that the font scales when the DPI changes while the app is running. I have also added Screen as an optional dependency to Scalable class, so that the DPI can be found for the current screen, without the need for the API in #1930, while still allowing other APIs to use scale_in and scale_out methods. The following is the same run of the test script with the latest commit: The fonts scale correctly and detect the DPI changes while the app is running. |
Nevermind. The bug was caused due to the faulty implementation of WeakRef introduced in #2066 and reported in #2163. toga/winforms/src/toga_winforms/window.py Line 36 in 7a46d41
After removing the WeakRef wrapping, the resizing event handler appears to fire consistently when the DPI scale is changed. Here is the app after fixing the bug: So, this bug will be resolved automatically when #2163 is addressed. Looks like the weakref calls in different places are causing problems. I wonder if this is also leading to failing of the tests on windows testbed. |
To be clear - the tests are not failing on main at present. If they're failing in this PR, it's either an unintended side effect of something in this PR, or an error that isn't 100% reproducible. The latter does happen sometimes - it's the nature of running a GUI test that sometimes, the GUI doesn't respond quite quickly enough, which results in a test failure. Re-running the test will (usually) fix these problems; however, I've just re-run the tests, and the same problem is occurring, which suggests it likely isn't a transient problem - it's an unintended side effect. From a quick inspection, I can't see any obvious connection between this PR's changes and OptionContainer. However, I can confirm that when I run the optioncontainer tests, I see the problem locally, and if I remove the Weakref usage from OptionContainer, the problem remains. |
In that case, I'll search further and report back what is causing the tests to fail. |
Turns out the bugs were related to Hwnds being created at inappropriate times. As discussed in #2155 (comment), the Hwnds are being created even before the Application instance is created. I have fixed the current bug by initializing and disposing a graphics context at the time of widget Hwnd creation. But, I recon more bugs related to Hwnd will be encountered in the future due to the way toga app execution flow works. But that's for the future. |
@mhsmith I need some guidance on how to test the behavior. Currently, I have the following test: toga/testbed/tests/app/test_app.py Lines 595 to 713 in 5e58f35
When the dpi scale is manually changed from the settings app then Windows automatically scales and resizes the window and does some other related stuff. Due to this reason, we are not manually resizing the window size on dpi change events as doing so would lead to wrong calculation of layout, glitchy behavior and the window will not restore to the correct size after change in dpi scale(i.e. the window size will be incorrect when we scale down from higher DPI to lower DPI). So, when Windows automatically resizes the window then only the calculated/expected size of widgets in the test are correct. But when we manually trigger any dpi change event like
The widget sizes are not changing and are stuck at the initial size. But visually the widgets are resized. I had also tried to manually resizing the window on DPI change event but the calculations were still wrong.
I am not sure on how to test the behavior correctly without actually changing the system DPI setting manually. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see what this has to do with resizing the window. We already found above that my machine and yours were behaving differently here, but I don't think this is critical as long as the content of the window is scaled correctly.
You should be able to test this by setting up a situation where some widgets are set to their minimum size, which is smaller than the window, and then checking that they scale correctly. For example:
toga.Box(
style=Pack(direction="row"),
children=[
toga.Box(style=Pack(flex=1)),
toga.Button(text="hello"),
toga.Button(text="world"),
toga.Box(style=Pack(flex=1)),
]
)
When the scale factor increases, the buttons should get bigger, and the spacer boxes should get smaller.
The monkey patch won't have any effect on the size of the toolbar and menu bar, because they're not implemented in Python. So there's probably no point in including them in the test.
FInally, this test still depends too much on implementation details. Notice that out of the 45 occurences of ._
in the file, 44 of them are from this new test. Where at all possible, tests should depend only on public Toga and WinForms APIs. Look at the existing tests that use the assert_layout
method.
testbed/tests/app/test_app.py
Outdated
# This test is windows specific | ||
if toga.platform.current_platform == "windows": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pytest.mark.skipif
is a cleaner way of doing this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Thanks!
The core test fails on |
@mhsmith just a friendly reminder to review this whenever you are free. |
Sorry for the slow reply; I'll look at this as soon as I can. |
71196f3
to
37e1add
Compare
I have updated to latest main branch. The dpi scaling is working properly. But between the last time I had updated to the latest main branch and now, something has changed in the main branch due to which it reports wrong
P.S. If you suspect anything that might be causing these bugs, then please let me know. |
On doing a deeper inspection, I have found that when dpi awareness is enabled, the following things happen:
This complicates things, since earlier, to move a window to the right side of the screen, we could do: self.main_window.screen_position = (
(self.main_window.screen.size.width - self.main_window.size.width),
self.main_window.position.y,
) But now, we need to do: self.main_window.screen_position = (
(self.main_window.screen.size.width - self.main_window.size.width*(dpi_scale)),
self.main_window.position.y,
) |
@mhsmith I would greatly appreciate your feedback regarding this new issue. |
@mhsmith friendly reminder to take a look look at this, as soon as you are free. |
While investigating the scaling problems encountered in #1930, I found that the call to
SetProcessDpiAwarenessContext
is erroneous. Hence, all the toga apps are always running in the DPI Unaware mode.This PR fixes the call and also checks if it was successful or not.
Note that this PR only has changes that fix the call to
SetProcessDpiAwarenessContext
, so that the call works. It doesn't fix any of the scaling issues currently present in toga. In particular, the test script from #1930, still shows the scaling bugs:At 125% scaling:
As you can see, the
DpiX
anddpi_scale
will always be 96 and 1.0 respectively. Furthermore, in the DPI Aware mode, the app menu has a disproportionately larger size compared to the rest of the window elements.Both of these bugs, indicate that there are problems in the scaling on windows. But those are for separate PRs.
Also, if required, I can add the code to correctly detect the actual system dpi.
PR Checklist: