Open Perpete opened 4 years ago
You should make sure your system version later than Windows 10 Anniversary Update
My version of windows 10 is 1909. My Visual Studio version is 16.6.1. The FrameWork 4.7.2 and 4.8 reproduce the same problem. I have tried my test program on other PCs with other screens. I also have the same problem. I found this link to a problem similar to mine open in 2018 and stay open. https://github.com/QL-Win/QuickLook/issues/420 It is the same problem for the show method or the LocationChanged event, the top and left values are adapted after the event. There is a discrepancy between the logical coordinates given by the Hook mouse procedure and the coordinates reassessed after moving and displaying (physical coordinates) with dpi recognition per monitor. On the other hand after other tests, I noticed that the values of the Height and Width properties of the window remain constant after switching from one monitor to another with different scales.
Hello, Using a program inspecting system messages sent to windows, I found the following for a window move in WPF with dpiAwareness in PerMonitorV2 mode. Screen 1: 1680x1050 - 100% scale Screen 2: 1600x900 - Scale 150%
Window: Height = 492 - Width = 509
Test 1 Window on screen 1, asks to go to screen 2 at the position Me.Left = 1680 and Me.Top = 0.
I note that the message WM_DPICHANGED (0x02E0) was sent to my window with 144dpi (0x90) for X and 144dpi (0x90) for Y. (150%) Message WM_WINDOWPOSCHANGING shows the new size and position of the window about to change. As the scale on screen 2 is 150%, Me.Width becomes 509x1.5 = 764, Me.Height becomes 409x1.5 = 738. On the other hand X remains at 1680.
After moving the window, the properties have the following values: Me.Top = 0 Me.Left = 1120 Me.Width = 509 Me.Height = 492 All values in the window have been divided by 1.5.
Test 2 Window on screen 2, request to go to screen 1 at the position Me.Left = 100 and Me.Top = 0.
I note that the message WM_DPICHANGED (0x02E0) was sent to my window with 96dpi (0x60) for X and 96dpi (0x60) for Y. (100%) Message WM_WINDOWPOSCHANGING shows the new size and position of the window about to change. As the scale on screen 1 is 100%, Me.Width and Me.Height keep their value (509-492) On the other hand X goes to 150 while Me.Left = 100.
After moving the window, the properties have the following values: Me.Top = 0 Me.Left = 150 Me.Width = 509 Me.Height = 492 The values in the window have not been changed.
I think there is a problem with positioning the window on an extended desk. The positioning values are adapted according to the scale of the monitor where the window is located before it is moved, while the values of the size are linked to the scale of the destination monitor. Giving a window position in this way is disturbing for the user.
The positioning values are logical units (96dpi). Should we not keep these values without modifying them so that the user can set a clear positioning value?
Can this change be made in the future because I need to use the positioning values of different windows in a WPF program on several monitors with different scales?
Try this on netcoreapp3.1 for best experimental results, esp since most AppContext switches don’t have to be set for this TFM.
Make sure you have "Switch.System.Windows.DoNotUsePresentationDpiCapabilityTier2OrGreater"
to false
.
IIRC Window.Left/Top is expressed in WPF’s local device independent 1/96” coordinate space. It must be translated to screen coordinate using Visual.PointToScreen
. It’s no different that translating any point in a Visual from WPF to screen coordinates (or vice-versa).
@Perpete - please let us know if @vatsan-madhavan suggestions is working for you.
Hello,
To use Core 3.1, I had to convert my written test program from VB to C #. I carried out the same tests and the problem of managing the positioning of the window remains the same.
You can use my test program contained in the .zip file. It was created with Visual Studio 2019 Version 16.6.1 with core 3.1. The.exe file is located in the folder: WpfTestPositionCore.zip \ WpfTestPositionCore \ WpfTestPosition \ bin \ Debug \ netcoreapp3.1.
Thanks for your help.
What I tried to explain in my last comment simply related to interpreting Window.Left/Top
in relation to screen coordinates - that it follows certain rules (they are expressed in WPF's device-independent coordinate space, and not in screen-coordinates), and application developers must adapt to this reality by leveraging transformation routines provided by WPF (specifically, converting from WPF-space to screen-space can be accomplished by leveraging Visual.PointToScreen
etc.).
If you search for information regarding the reverse transformation (screen -> logical), you'll find plenty of references about how to do that. (Roughly, something like PresentationSource.FromVisual(window).CompositionTarget.TransformFromDevice.Transform(point)
)
Note that these transformations aren't static - don't try cache them esp. They depend on the current DPI, current position on the screen etc. Just rely on WPF to produce these transformations on-the-fly.
You can generalize this information to further debug your problems with MSLLHOOKSTRUCT
. Payload that comes from Win32 API's is outside of WPF's control. You can attempt to identify the semantics of Win32 API's based on their documentation, and supplement with experimentation.
When per-monitor dpi-awareness is involved, a critical piece of information that's needed wrt Win32 API's (esp. API's that carry coordinate information in their data-payload) is whether Windows auto-scales the coordinates (in the data-payload) to match the DPI_AWARENESS_CONTEXT
of the thread receiving the message. Windows has made significant effort to ensure that this is indeed the case. For e.g., a DPI_AWARENESS_CONTEXT_SYSTEM_AWARE
application would typically receive WM_MOVE
messages with lParam
payload (x,y)
values scaled as if the universe were entirely system-aware. This means that the application receiving this message can simply use these values without concerning itself with further transformations/scaling.
That said, some API's may not do this - either by design, or because they have not been updated. This is why I'm suggesting that you'll have to combine perusing documentation with experimental observations. In the case of MSLLHOOKSTRUCT
, the doc clearly states that the data is per-monitor-aware (which I interpret as "the coordinates are always reported in DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE
, irrespective of the receiving applications DPI_AWARENESS_CONTEXT
" - I wish this had been stated more clearly in the docs).
Have you considered using MOUSEHOOKSTRUCT
/SetWindowsHookEx
instead? I realize that this isn't suitable for all needs.
Also be aware that the "screen-coordinates" are essentially DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE[_V2]
space coordinates of the desktop window.
Hello,
In my test program, I simply request the displacement of the window with the property Me.Left = XXX whose value is entered in a TextBox. The value of the property displayed by the textbox is similar to the data provided by MOUSEHOOKSTRUCT / SetWindowsHookEx representing the coordinates of all the monitors not sensitive to their scale. I also display the coordinates of the mouse using MOUSEHOOKSTRUCT / SetWindowsHookEx in my test program. The problem for me is that the requested value is sensitive to the scale of the monitor where the window is located before it is moved.
If I am on the 2nd monitor which has a scale 150% and I want to move the window on the 1st monitor which has a scale 100% at the value Left = 100 (96dpi), I must set Me.Left = 66.66 (100 /1.5=66.66).
If I am on the 1st monitor which has a 100% scale and I want to move the window on the 2nd monitor which has a 150% scale in the center of it at the value Left = 2880 (96dpi), I must place Me .Left = 2880 (2880/1 = 2880).
If I am on the 1st monitor which has a 125% scale and I want to move the window on the 2nd monitor which has a 150% scale in the center of it at the value Left = 2880 (96dpi), I must place Me .Left = 2304 (2880 / 1.25 = 2304).
Managing the position with dpiAwareness in PerMonitorV2 mode is much more complicated due to the transformation of the Top and Left properties of the window from the monitor scale.
I think that the most easily usable positioning data are the resolutions of the monitors at 96dpi (1920x1080, 1680x1050, ... ...) To manage the positioning of monitors by scale, you must therefore know the scale of each monitor in order to easily and correctly place its window.
I'm also going to have another problem with managing the window positioning in a program where the window coordinates are saved and where these windows must be repositioned when this program is opened. It will be necessary to memorize the scale of the monitors to restore the correct positioning of these windows.
My starting question was to know if this management of the position with dpiAwareness in PerMonitorV2 mode of the windows on a extended desktop was correct and frozen in order to be able to start modifying my programs.
Thanks for your help.
PointToScreen
and TransformFromDevice
know everything about these characteristics and abstract away the need for such knowledge. Window.Top/Left
in WPF's device-independent coordinates on a multi-mon PMAv2 application will work.
Window.Top/Left
values as-is and have them just work. WPF does the heavy lifting for you. Switch.System.Windows.DoNotScaleForDpiChanges=false
Hello,
You are right, there is no need for programming code for adjusting window sizes with dpiAwareness in PerMonitorV2 mode.
My test program works very well with Core 3.1, FrameWork 4.8 and FrameWork 4.7.2. The size of my window adapts correctly to the scale of the monitors with clearly readable fonts. But for me, the management of positioning with several screens with different scales is a problem.
As I was explaining to you before, I have a program that stores window size and positioning values and recreates those windows at the size and position stored. With the current adaptation of positioning in PerMonitorV2 mode, management of positioning with several screens with different scales is more complex.
I will use examples to explain myself better.
Screen 1 (Main): 1680x1050 Screen 2: 1600x900 I position my window on the left edge at the top of the 2nd screen at x = 1680 (96dpi) y = 0 (96dpi)
1st Example Screen 1: 100% Screen 2: 150% Values from my window for WPF after scaling and saved in a file. This.Top = 0 This.Left = 1120 (1680x1) /1.5 This.Heigh = 492 This.Width = 509
When recreating the window with these values, my new window is found on screen 1 at the position This.Left = 1120 (1120/1) and This.Top = 0 instead of screen 2. If I want the correct position on screen 2, before positioning, I must assign the value This.Left to: ((1120x1.5) / 1) = 1680. After positioning the window, WPF gives This.Left = 1120.
The This.Height and This.Width values remain at 509 and 492.
2nd Example Screen 1: 125% Screen 2: 150% Values from my window for WPF after scaling and saved in a file. This.Top = 0 This.Left = 933 (1680x1.25) /1.5 This.Heigh = 492 This.Width = 509
When recreating the window with these values, my new window is found on screen 1 at the position This.Left = 933 ((1120x1.25) /1.5) and This.Top = 0 instead of screen 2 . If I want the correct position on screen 2, before positioning, I must assign the value This.Left to: ((1120x1.5) /1.25) = 1344. After positioning the window, WPF gives This.Left = 1120.
The This.Height and This.Width values remain at 509 and 492.
3rd Example Screen 1: 150% Screen 2: 100% Values from my window for WPF after scaling and saved in a file. This.Top = 0 This.Left = 1680 (1680x1.5) / 1 This.Heigh = 492 This.Width = 509
When recreating the window with these values, my new window is found on screen 2 at the position This.Left = 2520 (1680x1.5) instead of 1680 and This.Top = 0. If I want the correct position on screen 2, before positioning, I must assign the value This.Left to: ((1680x1) / 1.5) = 1120. After positioning the window, WPF gives This.Left = 1120.
The This.Height and This.Width values remain at 509 and 492.
To find the value on the scale of the correct position of the window on all screens, I simply apply the opposite formula used between Windows 10 and WPF from the initial value. You can see the procedure between windows 10 and WPF by the system messages in my previous comments. The initial position is first multiplied by the scale of the start screen and then divided by the scale of the destination screen. Obviously, for different scales between screens, with displacements between screens, this value is not constant.
It is for these reasons that I need to know the scale of the screens to restore or move the windows to the right position.
I think there is a problem with scaling the position between Windows 10 and WPF.
Let's take an example : Screen 1 (Main): 1680x1050 - 100% Screen 2: 1600x900 - 150% Let's position the upper left corner of a window in the center of the screen 2. Without PerMonitorV2 Before moving This.Left = 2480 -> (1680+ (1600/2)) After moving This.Left = 2480
Currently with PerMonitorV2 Before moving This.Left = 2480 -> (1680+ (1600/2)) After moving This.Left = 1653 -> (2480 x1) /1.5 I think the value of 1653 is not correct. It should be 2213 -> 1680 + (800 / 1.5).
Let's take another example: Screen 1 (Main): 1680x1050 - 125% Screen 2: 1600x900 - 150% Let's position the upper left corner of a window in the center of the screen 2. Without PerMonitorV2 Before moving This.Left = 2480 -> (1680+ (1600/2)) After moving This.Left = 2480
Currently with PerMonitorV2 Before moving This.Left = 2480 -> (1680+ (1600/2)) After moving This.Left = 2066 -> (2480 x1.25) /1.5 The window position is incorrect. To place it correctly, I have to adjust the initial value of This.Left by dividing it by the scale of the main screen This.Lefts = 1977 -> (1680+ (1600/2)) /1.25 After moving This.Left = 1647
I think the value of 1647 is not correct. It should be from 1877 -> (1680 / 1.25) + (800 / 1.5).
To correct this problem, a solution would be that the rectangle pointed by lParam in the WM_DPICHANGED message contains a Top and Left value calculated by considering each screen resolution with its separate scale. With my screen configuration in the example above we would have: This.Left = 2480 -> Rectangle.Left = 1877 -> (1680 / 1.25) + (800 / 1.5).
At this time, Wpf will receive a correct positioning without any adaptation to be made.
Another solution would be to keep the positioning values in logical units (96dpi) without modification between windows 10 and WPF like the modes without PerMonitorV2.
For size values, the management between Windows 10 and WPF works perfectly well. The This.Height and This.Width values always remain at 492 and 509 with any screen scale. If This.Height = 492 before positioning, the message WM_DPICHANGED gives the dpi for x and y of the destination monitor in the example window: 0x90 = 144dpi = 150%, the height proposal for scaling will be 492x1. 5 = 738. After the position change, WPF gives us This.Height = 492. The height value is divided by the scale factor of the monitor (1.5). For the user, this value therefore remains the same regardless of the scale.
hi sorry ,does it work now?
@xmaxrayx Sorry, no. It is a design issues. But you can get the correct postion from win32.
@xmaxrayx Sorry, no. It is a design issues. But you can get the correct position from win32.
@lindexi thanks currently my program get the mouse position from user32.DLL
but my WPF program won't show up at that position unless if changed windows DPI
if i set 100% the WPF can start under mouse position.
@xmaxrayx Yeah, you meet a problem with coordinate system transformation. The GetCursorPos is screen coordinate but the Window.Left
and Window.Top
is wpf coordinate .
@xmaxrayx Here is how I move a window by code to work around the positioning issue with PerMonitorV2. During testing, I noticed that I could consider that each monitor saw all the dimensions of the other monitors with its own scale. Let's take an example of positioning a window with the following monitors: Monitor 1 (1920x1080 - 100%) Monitor 2 (1920x1080 - 150%)
For window positioning references, I always use 100% monitor scale values. Let's put the upper left corner of the window in the center of the width of the monitor 2. If my window is initially positioned on monitor 1: The left value to give to the window will be 2879 -> ((1920 +1920/2) / 1) -1
If my window is initially positioned on monitor 2: The left value to give to the window will be 1919 -> ((1920 +1920/2) / 1.5) -1
Here is my test code in VB.
` Class MainWindow Private Sub btMove_Click(sender As Object, e As RoutedEventArgs) Handles btMove.Click
'Déplace la fenêtre
'Récupère le facteur d'échelle du moniteur
Dim ScaleMonitor As DpiScale = VisualTreeHelper.GetDpi(Me)
Dim CalculLeft As Double = CDbl(txtLeft.Text) / ScaleMonitor.DpiScaleX
Dim CalculTop As Double = CDbl(txtTop.Text) / ScaleMonitor.DpiScaleY
'Attibue les coordonnées à la fenêtre
Left = CalculLeft
Top = CalculTop
lbLeftCalcul.Content = CalculLeft
lbTopCalcul.Content = CalculTop
lbScale.Content = ScaleMonitor.DpiScaleX
lbLeftAfterMove.Content = Left
lbTopAfterMove.Content = Top
End Sub End Class`
Here is my code for test (wpf in vb with net 6). MoveWindows.zip
I don't understand why this problem hasn't been fixed all this time. This seems like a significant problem to me.
@Perpete Hi, many thanks it works now <3 my setup is only one monitor with 150% and yeah the WPF scale up the location , I think this bad approach , if it was width or high then no problem but window location shouldn't be that.
this is my code for c#
public MainWindow()
{
InitializeComponent();
DpiScale dpi = VisualTreeHelper.GetDpi(this);
POINT point; GetCursorPos(out point);
this.Top = (point.Y)/dpi.DpiScaleY;
this.Left = (point.X)/dpi.DpiScaleX;
}
yeah I found it funny winforms don't have that problem with wpf.
@xmaxrayx Yeah, you meet a problem with coordinate system transformation. The GetCursorPos is screen coordinate but the
Window.Left
andWindow.Top
is wpf coordinate .
thx, wish if they remove that " wpf transform" for window locations or at least we have option for true manual location
, wpf multiply that location with the dpi scale then we need divide it again to revoke it ,so in total we do more process work unnecessary.
You are absolutely right. I reported this issue in 2020 and it's 2024. Apparently this problem is not considered important.
I failed to fix this problem three years ago...
This bug causes any logic for saving/restoring window layouts to fail on any multi DPI system since WPF always uses the main display DPI. When windows are created they always pick up the default DPI and don't consider he fact that they may be moved around programatically.
Hello,
For a WPF project in VB, I retrieve the mouse coordinates using a Hook mouse (MSLLHOOKSTRUCT) to display a window on the desktop according to the selection rectangle defined by the user. I noticed that the mouse coordinates do not take into account the screen scales. Is this normal? So, I had to retrieve this scale using the GetDpiForMonitor API and created a manifest file with DPI recognition per screen.