-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Window.Left and Window.Top are broken when usign PerMonitorV2 #4127
Comments
For what it's worth, we've hit this same issue with Window.Left and Window.Top being ambiguous in a PerMonitorV2 application. I agree with the assessment that an API which allows the use of screen coordinates would be helpful. Given a destination window rectangle in screen coordinates, and given that the window movement might cause the window to jump to a different screen with a different DPI scaling %, we've had to resort to this hack in the code behind (*.xaml.cs) of the window class. The caller can move the window using window.MoveThisWindow( rect ), where rect is in screen coordinates. public partial class MyWindow : Window
{
private IntPtr _hwnd;
public MyWindow()
{
InitializeComponent();
SourceInitialized += MyWindow_SourceInitialized;
}
private void MyWindow_SourceInitialized( object sender, System.EventArgs e )
{
_hwnd = new WindowInteropHelper( this ).Handle;
}
[DllImport( "user32.dll", SetLastError = true )]
private static extern bool MoveWindow( IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint );
public void MoveThisWindow( Rectangle rect )
{
// The first move puts it on the correct monitor, which triggers WM_DPICHANGED
// The +1/-1 coerces WPF to update Window.Top/Left/Width/Height in the second move
MoveWindow( _hwnd, rect.Left + 1, rect.Top, rect.Width - 1, rect.Height, false );
MoveWindow( _hwnd, rect.Left, rect.Top, rect.Width, rect.Height, true );
}
} Clearly, this is a hack. But it's the best we've come up with so far to workaround this limitation of WPF Window.Left and Window.Top when crossing between screens. It's not perfect. Pros:
Cons:
If there's a better way, I'd love to hear it. |
* Rework library for supporting dpi awareness: - All values returns in wpf units - Project update for StyleCop rules - Bugfix for SystemInformation.VirtualScreen returning only primary screen * Added test project for window position check (keep in mind that you should set WindowStyle="None" ResizeMode="NoResize" properties for window or you will get position problem. See dotnet/wpf#4127) * Fix for MONITORINFOEX initialization after code cleanup * Final changes WindowHelper added; Some fixes for SystemInformation; * code review * simplify build batch using dotnet tool * upgrade to netcoreapp3.1 because netcoreapp3.0 is out of support * bump version to v1.1.0 Co-authored-by: andbayd <andrew@monosnap.com> Co-authored-by: Michael Denny <micdenny@gmail.com>
Hello, For window positioning references, I always use 100% monitor scale values. If my window is initially positioned on monitor 2: Here is my test code in VB. `
End Class` Here is a screen of the window moving from monitor 1 to monitor 2 with always the same initial coordinates at 100% scale. Here is a screen of the window of the movement of monitor 2 on monitor 2 with always the same initial coordinates at 100% scale. Here is my code for test (wpf in vb with net 6). |
I am also experiencing this issue and would like it to be fixed. In .net 4.8 the logic was changed. So that before the window is shown, the manually entered value of Left=1921 would be automatically scaled based on the window that the monitor would end up on. Thus it is impossible to manually open a window at the top left corner of monitor 2. The fix would be to make sure that the Window initalization logic does not auto scale the manually inserted values for Top/Left/Width/Height. I have already taken scaling into account and can provide the correct values. Even better would be to stop this scaling nonsense in the first place and allow me to position things perfectly by physical pixels. |
Just in case someone is looking for a way to work around automatic scaling when positioning a window. Here is how Microsoft devs fixed high DPI issues in NotePad. Generally, an interesting post. |
this is quite annoying. im trying to have a window as a tooltip. as soon as its shown on the other window it jumpes between both windows and i lose focus of my the ide. |
Please fix this! It makes basic window manipulation impossible. Edit: The 'hack' above works for me, thank you. Unfortunately it took me a while before I landed on this page. |
@dt200r The problem is more of a design problem. Because it's not a continuous value. |
Understood, by basic window manipulation I mean programmatically placing a window in the right location in a multi-monitor diverse DPI setup. |
Easy APIs to do the following would make (my) life much easier:
As it stands, I have just completed my window management coding for a new project where every top level window is in a dedicated thread and it is an unholy mess of DPI awareness, Windows Forms, WPF, Windows API, WndProc hooks and overrides, event masking, and so on. The worst part is it took a week when it should have taken a day. |
I am wondering if this is the same issue that I am having, if anybody could take a look I would appreciate it so I don't open up a new issue. In my NET 7.0 WPF app I am setting window.Left and window.Top on 2 different windows, one is positioned in the working area of Screen 1, and the 2nd in the working area of Screen 2. In the first call, the windows are placed correctly. In the 2nd call , the window on the secondary screen ignores the Left and puts it on the primary screen. The 3rd AND ALL OTHER CALLS puts it on the secondary screen just fine! I confirmed that this worked fine in 4.7.2 with no issues. Here is my stackoverflow link for context https://stackoverflow.com/questions/76024476/different-behavior-for-window-placement-only-on-2nd-showing-of-windows-in-wpf |
Example
In this example, the user is using Windows 10, the app is PerMonitorV2 enabled and the user has two monitors, as shown below.
Scaling Between WPF and Screen Sizes
A window can be freely moved between the available monitors and WPF automatically changes scaling and other parameters.
Depending on the window’s current monitor, WPF calculates the scaling factor between Screen and WPF sizes, and vice versa. To my knowledge, this works fine.
In the above example, the left window is on monitor 1, which has DPI of 144. This gives a scaling factor of: 144 / 96 = 1,5. The right window is on monitor 2, which has DPI of 192. This gives a scaling factor of: 192 / 96 = 2,0. Knowing the scaling factors, it is easy to calculate between the screen size and WPF sizes. For example, the right window has a width of 500 WPF units. This is 500 x 2.0 = 1000 pixels.
Scaling Between WPF and Screen Positions – BROKEN
Similar to scaling of WPF sizes, we can try to scale between WPF and Screen positions, for example the window’s top-left corner.
The current buggy WPF implementation uses the scaling factor from the current monitor (see above) to calculate the top-left corner of the window.
Example for the left window. Left: 375 pixels / 1,5 = 250 WPF units. This is correct. To position the window on the screen, we need to calculate from WPF to Screen units. This is trickier, as we don’t know on which screen 250 WPF units is. This could be on monitor 1 or monitor 2. For now, we assume monitor 1. Screen-left: 250 WPF units * 1,5 = 375.
Example for the right window: Left: 2200 pixel / 2,0 = 1100 WPF units. To calculate the other way, WPF needs to figure out on which monitor 1100 WPF units is. It starts enumerating monitors to try to find one that will work.
• Monitor 1: 1100 * 1,5 = 1650. Monitor has bounds 0..1500. 1650 is outside the bounds.
• Monitor 2: 1100 * 2,0 = 2200. Monitor has bounds 1500..3500. 2200 is inside the bounds. We’ve found our monitor.
Therefore, to position the window at 1100 WPF, we calculate: 1100 WPF * 2,0 = 2200 pixel.
Now, let’s look at a broken calculation. We again have two windows, but this time positioned slightly different on monitor 1 and 2.
The first (left) window’s left edge: 1275 pixel / 1,5 = 850 WPF units.
The second (right) window’s left edge: 1700 pixel / 2.0 = 850 WPF units.
It is clear that both windows are at different positions on the virtual desktop, but WPF calculates the same position for both windows.
When trying to position the windows programmatically on the screen, things of course go terribly wrong. If the window is already opened, then WPF know the affiliated monitor and will use the scaling factor for that monitor. This will work, unit the window is moved to the other monitor, then the coordinates may jump due to change of scaling factor.
If the window is not opened, then WPF will have to guess on which monitor WPF position 850 is. It iterates the monitors, as explained above. The issue is that the calculations give a position that is valid for both monitors.
• Monitor 1: 850 * 1,5 = 1275. Monitor has bounds 0..1500. 1275 is inside the bounds.
• Monitor 2: 850 * 2,0 = 1700. Monitor has bounds 1500..3500. 1700 is inside the bounds.
The logic WPF uses is to iterate the monitors in the order in which Windows returns them. Once it finds a monitor that contains the requested coordinates, the search terminates. If it is alphabetically, the search for position 850 will end on monitor 1. This means that it is impossible to programmatically open a window at the position of the right window on monitor 2.
Proposed Solution
I see no easy way to fix Window.Left and Window.Top. An unfeasible fix is to divide and map the virtual desktop to individual adjacent areas, each having different scaling factor and WPF bounds. How to handle areas not visible on any monitor is unclear.
I propose to take the simple solution and expose an API to position the window directly using screen coordinates.
Some Additional Challenges
Once a window is opened, positions relative to the window’s top-left corner can be calculated mostly without issue. This means that the relative position of a button compared to the window’s top-left corner can be calculated both ways without issues.
Some issues may exist, as Windows scales the part of the window that is not on the monitor associated with the window according to some internal logic. In the example, the part of the window on monitor 1 may be scaled. This must be tested, especially how mouse or similar coordinates are converted to WPF positions relative to the window’s top-left, as we have little experience with this.
Related Bugs
#3343
#3105
The text was updated successfully, but these errors were encountered: