C
C#4mo ago
Pandetthe

Positioning window in per monitor aware v2

Hi! I am trying to position the wpf window perfectly within the selected screen. When I am using the unaware option everything works fine, but in per monitor aware v2 it's a little bit complicated. I have information about screen scalling and bounds. Firstly I thought that I can just multiplicate scalling and bounds to get the correct position. I was wrong. When the selected screen is under the primary screen, then the top/y position should not be scaled, but when is above it should. What would happen if there were three monitors in the row, primary first, then some monitor with weird scaling and then selected screen? Is there some option to easily get these bounds? I want to make this clean, because now I have a temporary solution. When creating a new window I am changing dpi aware to unaware.
39 Replies
Pandetthe
PandettheOP4mo ago
This is my constructor of Screen class
private Screen(IntPtr monitor, IntPtr hdc)
{
if (monitor == PRIMARY_MONITOR)
monitor = NativeMethods.MonitorFromPoint(new POINT(0, 0), MONITOR_DEFAULT.MONITOR_DEFAULTTOPRIMARY);
if (NativeMethods.GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out uint dpiX, out uint dpiY) == HRESULT.S_OK)
{
Debug.Assert(dpiX == dpiY);
ScaleFactor = dpiX / 96.0;
}
if (!MultiMonitorSupport || monitor == PRIMARY_MONITOR)
{
var size = new Size(
NativeMethods.GetSystemMetrics(SM.SM_CXSCREEN),
NativeMethods.GetSystemMetrics(SM.SM_CYSCREEN));

Bounds = new Rect(0, 0, size.Width, size.Height);
Primary = true;
DeviceName = "DISPLAY";
}
else
{
MONITORINFOEX info = new();
NativeMethods.GetMonitorInfo(monitor, info);

Bounds = new(new Point(info.rcMonitor.left, info.rcMonitor.top), new Point(info.rcMonitor.right, info.rcMonitor.bottom));
Primary = (info.dwFlags & MONITORINFOF_PRIMARY) != 0;
DeviceName = new string(info.szDevice).TrimEnd((char)0);
}

monitorHandle = monitor;
monitorHdc = hdc;
}
private Screen(IntPtr monitor, IntPtr hdc)
{
if (monitor == PRIMARY_MONITOR)
monitor = NativeMethods.MonitorFromPoint(new POINT(0, 0), MONITOR_DEFAULT.MONITOR_DEFAULTTOPRIMARY);
if (NativeMethods.GetDpiForMonitor(monitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out uint dpiX, out uint dpiY) == HRESULT.S_OK)
{
Debug.Assert(dpiX == dpiY);
ScaleFactor = dpiX / 96.0;
}
if (!MultiMonitorSupport || monitor == PRIMARY_MONITOR)
{
var size = new Size(
NativeMethods.GetSystemMetrics(SM.SM_CXSCREEN),
NativeMethods.GetSystemMetrics(SM.SM_CYSCREEN));

Bounds = new Rect(0, 0, size.Width, size.Height);
Primary = true;
DeviceName = "DISPLAY";
}
else
{
MONITORINFOEX info = new();
NativeMethods.GetMonitorInfo(monitor, info);

Bounds = new(new Point(info.rcMonitor.left, info.rcMonitor.top), new Point(info.rcMonitor.right, info.rcMonitor.bottom));
Primary = (info.dwFlags & MONITORINFOF_PRIMARY) != 0;
DeviceName = new string(info.szDevice).TrimEnd((char)0);
}

monitorHandle = monitor;
monitorHdc = hdc;
}
sibber
sibber4mo ago
when is above it should
what does this mean? wdym above/under? what are you trying to do? center a window in the screen the window is in?
Pandetthe
PandettheOP4mo ago
No description
Pandetthe
PandettheOP4mo ago
It's about layout Set window's initial position into specified screen, that it's filling its full width and height I have got this working in dpi unaware, but I don't have any idea how to achieve this in per monitor v2
sibber
sibber4mo ago
so like maximized but not actually maximized?
Pandetthe
PandettheOP4mo ago
Yes
sibber
sibber4mo ago
can you maximize, get the size and pos, then unmaximize and set the pos? user wont notice
Pandetthe
PandettheOP4mo ago
It's a working solution too
sibber
sibber4mo ago
When the selected screen is under the primary screen, then the top/y position should not be scaled, but when is above it should.
youre scaling the dimentions of the screen?
Pandetthe
PandettheOP4mo ago
But I am looking for a method based on windows api NativeMethods.GetMonitorInfo(monitor, info); returns a width and height without scalling And when using it's dimensions windows is too big For 125% scaling for example But when scaling it (value / scalingfactor) where scalingfactor is equal to dpi / 96 and selected screen is under primary screen, top value is too low
sibber
sibber4mo ago
ah so setting the window position sets it via the virtual screen coords right? iirc
Pandetthe
PandettheOP4mo ago
Right
sibber
sibber4mo ago
its been a while since ive done winapi stuff so how are you calculating the top of the screen you want? easiest way to to calculate the raw values and then at the end when setting the window position scale it to the screen so that you dont miss scaling something somwhere
Pandetthe
PandettheOP4mo ago
It's the thing I am looking for. Previously it was needed only to get selected screen bounds with NativeMethods.GetMonitorInfo and scale it by system's scale factor and it was enought for wpf coordinates for unaware(default) mode
sibber
sibber4mo ago
oh i dont know how wpf handles dpi i think it allows you to set the coords before scaling or something
Pandetthe
PandettheOP4mo ago
Raw values = values from getmonitorinfo?
sibber
sibber4mo ago
values without the scaling
Pandetthe
PandettheOP4mo ago
So basicly it is what I said
Pandetthe
PandettheOP4mo ago
\
No description
Pandetthe
PandettheOP4mo ago
Scale by? Only width and height? Left and top too?
sibber
sibber4mo ago
only width and height iirc it must be mentioned somewhere in the docs
Pandetthe
PandettheOP4mo ago
It will work for this situations
No description
Pandetthe
PandettheOP4mo ago
It wont for in this situation for example
No description
sibber
sibber4mo ago
that's weird
Pandetthe
PandettheOP4mo ago
It's because x is based on 2'nd screen width not on 1st screen width like in the first image I have a solution for this Just calculate where is monitor based on primary monitor But how to calculate this situation Selected screen is number 3
Pandetthe
PandettheOP4mo ago
No description
Pandetthe
PandettheOP4mo ago
Sorry wrong image
sibber
sibber4mo ago
wdym where the monitor is?
Pandetthe
PandettheOP4mo ago
Primary is number 1 How to calculate x for third monitor? With this scaling I cannot just take x(left) and multiply by 3rd scalling, or just left it without scaling And I could write code to calculate this, and find screen by screen and add all scalled coordinates, but there is a lot of edge cases Or maybe there is easier solution
sibber
sibber4mo ago
right i don't know how windows handles this
Pandetthe
PandettheOP4mo ago
Maybe there is some windows api method that I can call
sibber
sibber4mo ago
yeah I'd hope lol what a pain in the ass sorry i don't know haha
Pandetthe
PandettheOP4mo ago
Maybe another person here know how to solve it easily Okay I found something interesting Everything works fine with normal bounds, when using SetWindowPlacement Oh it's interesting, when I set window top to -720, after a second, wpf translate to -480 Oh my problem is related to wpf issue
Pandetthe
PandettheOP4mo ago
GitHub
Window.Left and Window.Top are broken when usign PerMonitorV2 · Iss...
.NET Core Version: FW 4.8 Windows version: 10.0.19042.746 Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes Example In this example, the user is using Windows 10, the app is PerMonito...
Pandetthe
PandettheOP4mo ago
This is my solution
public class DpiAwareWindow : Window
{
public new static readonly DependencyProperty LeftProperty =
DependencyProperty.RegisterAttached(nameof(Left), typeof(double), typeof(DpiAwareWindow),
new PropertyMetadata(OnPlacementChange));

public new double Left
{
get => (double)GetValue(LeftProperty);
set => SetValue(LeftProperty, value);
}

public new static readonly DependencyProperty TopProperty =
DependencyProperty.RegisterAttached(nameof(Top), typeof(double), typeof(DpiAwareWindow),
new PropertyMetadata(OnPlacementChange));

public new double Top
{
get => (double)GetValue(TopProperty);
set => SetValue(TopProperty, value);
}

public new static readonly DependencyProperty WidthProperty =
DependencyProperty.RegisterAttached(nameof(Width), typeof(double), typeof(DpiAwareWindow),
new PropertyMetadata(OnPlacementChange));

public new double Width
{
get => (double)GetValue(WidthProperty);
set => SetValue(WidthProperty, value);
}

public new static readonly DependencyProperty HeightProperty =
DependencyProperty.RegisterAttached(nameof(Height), typeof(double), typeof(DpiAwareWindow),
new PropertyMetadata(OnPlacementChange));

public new double Height
{
get => (double)GetValue(HeightProperty);
set => SetValue(HeightProperty, value);
}

private static void OnPlacementChange(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj is not DpiAwareWindow window) return;
IntPtr hwnd = new WindowInteropHelper(window).EnsureHandle();
if (!NativeMethods.GetWindowPlacement(hwnd, out WINDOWPLACEMENT placement))
return;
placement.normalPosition = RECT.FromXYWH((int)window.Left, (int)window.Top, (int)window.Width, (int)window.Height);
NativeMethods.SetWindowPlacement(hwnd, ref placement);
}
public class DpiAwareWindow : Window
{
public new static readonly DependencyProperty LeftProperty =
DependencyProperty.RegisterAttached(nameof(Left), typeof(double), typeof(DpiAwareWindow),
new PropertyMetadata(OnPlacementChange));

public new double Left
{
get => (double)GetValue(LeftProperty);
set => SetValue(LeftProperty, value);
}

public new static readonly DependencyProperty TopProperty =
DependencyProperty.RegisterAttached(nameof(Top), typeof(double), typeof(DpiAwareWindow),
new PropertyMetadata(OnPlacementChange));

public new double Top
{
get => (double)GetValue(TopProperty);
set => SetValue(TopProperty, value);
}

public new static readonly DependencyProperty WidthProperty =
DependencyProperty.RegisterAttached(nameof(Width), typeof(double), typeof(DpiAwareWindow),
new PropertyMetadata(OnPlacementChange));

public new double Width
{
get => (double)GetValue(WidthProperty);
set => SetValue(WidthProperty, value);
}

public new static readonly DependencyProperty HeightProperty =
DependencyProperty.RegisterAttached(nameof(Height), typeof(double), typeof(DpiAwareWindow),
new PropertyMetadata(OnPlacementChange));

public new double Height
{
get => (double)GetValue(HeightProperty);
set => SetValue(HeightProperty, value);
}

private static void OnPlacementChange(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj is not DpiAwareWindow window) return;
IntPtr hwnd = new WindowInteropHelper(window).EnsureHandle();
if (!NativeMethods.GetWindowPlacement(hwnd, out WINDOWPLACEMENT placement))
return;
placement.normalPosition = RECT.FromXYWH((int)window.Left, (int)window.Top, (int)window.Width, (int)window.Height);
NativeMethods.SetWindowPlacement(hwnd, ref placement);
}
sibber
sibber4mo ago
oh
sibber
sibber4mo ago
well at least it works now haha saved 15h of research for the next person
Pandetthe
PandettheOP4mo ago
I was just randomly checking difference between screen boundries and restoreboundries of the window after show method. I was amazed that my -720 top was converted to -480. I thought it is impossible and just googled it and this came up as a first research. Spoiler, every previous search did not show this
Want results from more Discord servers?
Add your server