WPF坐标转换

前言

假如屏幕是1920*1080,缩放是125%;

那么WPF窗口最大设置为1536*864就会占满屏幕。

像素无关单位

image-20240510173902840

如图

屏幕1 分辨率 1600x900 缩放 100%

屏幕1 分辨率 1920x1080 缩放 125%

打印值

1
2
3
4
5
6
Console.WriteLine(@"SystemParameters.PrimaryScreenWidth:"+ SystemParameters.PrimaryScreenWidth);
Console.WriteLine(@"SystemParameters.PrimaryScreenHeight:" + SystemParameters.PrimaryScreenHeight);
Console.WriteLine(@"SystemParameters.VirtualScreenWidth:" + SystemParameters.VirtualScreenWidth);
Console.WriteLine(@"SystemParameters.VirtualScreenHeight:" + SystemParameters.VirtualScreenHeight);
Console.WriteLine(@"SystemParameters.VirtualScreenLeft:" + SystemParameters.VirtualScreenLeft);
Console.WriteLine(@"SystemParameters.VirtualScreenTop:" + SystemParameters.VirtualScreenTop);

结果

1
2
3
4
5
6
SystemParameters.PrimaryScreenWidth:1600
SystemParameters.PrimaryScreenHeight:900
SystemParameters.VirtualScreenWidth:3520
SystemParameters.VirtualScreenHeight:991
SystemParameters.VirtualScreenLeft:-1920
SystemParameters.VirtualScreenTop:-91

我们可以看出

PrimaryScreenWidth 获取的是主屏的分辨率/缩放

虚拟尺寸是以主屏的缩放为准,以主屏的左上角为基准点,两个屏幕的最高点为Top,主屏的最低点为Bottom进行计算。

以像素为单位

所在屏幕大小

1
2
3
// 获取当前屏幕工作区的大小
System.Windows.Forms.Screen screen = System.Windows.Forms.Screen.FromHandle(new System.Windows.Interop.WindowInteropHelper(this).Handle);
System.Drawing.Rectangle screenArea = screen.Bounds;

可以获取

1
2
3
4
5
6
screenArea.Width
screenArea.Height
screenArea.Left
screenArea.Top
screenArea.Right
screenArea.Bottom

主屏幕宽高

1
2
int screenWidth = Screen.PrimaryScreen.Bounds.Width;
int screenHeight = Screen.PrimaryScreen.Bounds.Height;

窗口的位置及大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// 获取窗口中的点在屏幕上的位置(以像素为单位)
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
private Point PointToScreen(double x,double y)
{
Point pointInDIU = new Point(x, y);
Visual visual = this; // 获取你的 Visual 对象,比如某个控件或者窗口
// 将 DIU 坐标转换为屏幕像素坐标
Point pointInPixels = visual.PointToScreen(pointInDIU);
// 输出转换后的像素坐标
return pointInPixels;
}

private void SizePosChange()
{
var pointToScreen = PointToScreen(0,0);
PosTb.Text = pointToScreen.X + "," + pointToScreen.Y;
var pointToScreen2 = PointToScreen(Width, Height);
SizeTb.Text = (pointToScreen2.X- pointToScreen.X) + "x" + (pointToScreen2.Y- pointToScreen.Y);
}

获取鼠标位置

获取的是屏幕实际像素对应的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System.Runtime.InteropServices;

namespace ColorPicker.Utils
{
internal class ZPoint
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool GetCursorPos(out POINT pt);

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;

public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
}
}

调用

1
2
ZPoint.POINT point;
ZPoint.GetCursorPos(out point);

这样获取的坐标是屏幕的实际尺寸算的,即1920*1080。

如果我们根据这个值设置WPF的窗口就会发生偏移。

WPF坐标转像素坐标

这是相对于窗口的位置所在屏幕的像素位置,所以0,0就是窗口左上角在屏幕中的像素位置。

1
2
3
4
5
6
7
8
Point pointInDIU = new Point(0, 0); // 假设在 DIU 中的坐标为 (0, 0)
Visual visual = this; // 获取你的 Visual 对象,比如某个控件或者窗口

// 将 DIU 坐标转换为屏幕像素坐标
Point pointInPixels = visual.PointToScreen(pointInDIU);

// 输出转换后的像素坐标
Console.WriteLine($"像素坐标:({pointInPixels.X}, {pointInPixels.Y})");

像素坐标转换为WPF坐标

1
2
3
4
ZPoint.POINT point;
ZPoint.GetCursorPos(out point);
Matrix transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
Point wpfPoint = transform.Transform(new System.Windows.Point(point.X, point.Y));

获取窗口的缩放率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Windows;
using System.Windows.Forms;

namespace ColorPicker.Utils
{
internal class ScreenHelper
{

private const string User32 = "user32.dll";

[DllImport(User32, CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.None)]
public static extern bool GetMonitorInfo(HandleRef hmonitor, [In, Out] MONITORINFOEX info);

[DllImport(User32, ExactSpelling = true)]
[ResourceExposure(ResourceScope.None)]
public static extern bool EnumDisplayMonitors(HandleRef hdc, COMRECT rcClip, MonitorEnumProc lpfnEnum, IntPtr dwData);

public delegate bool MonitorEnumProc(IntPtr monitor, IntPtr hdc, IntPtr lprcMonitor, IntPtr lParam);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
public class MONITORINFOEX
{
internal int cbSize = Marshal.SizeOf(typeof(MONITORINFOEX));
internal RECT rcMonitor = new RECT();
internal RECT rcWork = new RECT();
internal int dwFlags = 0;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
internal char[] szDevice = new char[32];
}

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;

public RECT(Rect r)
{
left = (int)r.Left;
top = (int)r.Top;
right = (int)r.Right;
bottom = (int)r.Bottom;
}
}

[StructLayout(LayoutKind.Sequential)]
public class COMRECT
{
public int left;
public int top;
public int right;
public int bottom;
}

public static readonly HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);


/// <summary>
/// 获取缩放比例
/// </summary>
/// <returns></returns>
public static double GetScalingRatio()
{
var logicalHeight = GetLogicalHeight();
var actualHeight = GetActualHeight();

if (logicalHeight > 0 && actualHeight > 0)
{
return logicalHeight / actualHeight;
}

return 1;
}

private static double GetActualHeight()
{
return SystemParameters.PrimaryScreenHeight;
}

private static double GetLogicalHeight()
{
var logicalHeight = 0.0;

MonitorEnumProc proc = (m, h, lm, lp) =>
{
MONITORINFOEX info = new MONITORINFOEX();
GetMonitorInfo(new HandleRef(null, m), info);

//是否为主屏
if ((info.dwFlags & 0x00000001) != 0)
{
logicalHeight = info.rcMonitor.bottom - info.rcMonitor.top;
}

return true;
};
EnumDisplayMonitors(NullHandleRef, null, proc, IntPtr.Zero);

return logicalHeight;
}
}
}

调用

1
2
3
4
5
6
ZPoint.POINT point;
ZPoint.GetCursorPos(out point);
var ScalingRatio = ScreenHelper.GetScalingRatio();
Console.WriteLine($"当前缩放比例为{ScalingRatio}%");
colorWin.Top = point.Y / ScalingRatio+4;
colorWin.Left = point.X / ScalingRatio+4;

这种作用和下面是一样的。

限制窗口大小

限制窗口不能大于主屏大小,并且窗口在主屏内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
this.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(UIElement_OnMouseUp), true);


private void UIElement_OnMouseUp(object sender, MouseButtonEventArgs e)
{

// 获取当前屏幕工作区的大小
var primaryScreenWidth = SystemParameters.PrimaryScreenWidth;
var primaryScreenHeight = SystemParameters.PrimaryScreenHeight;


// 如果窗口的宽度或高度超出当前屏幕工作区的大小,则调整窗口的大小和位置
if (this.Width > primaryScreenWidth)
{
this.Width = primaryScreenWidth;
}
if (this.Height > primaryScreenHeight)
{
this.Height = primaryScreenHeight;
}
if (this.Left < 0)
{
this.Left = 0;
}
if (this.Top < 0)
{
this.Top = 0;
}
if (this.Left + this.Width > primaryScreenWidth)
{
this.Left = primaryScreenWidth - this.Width;
}
if (this.Top + this.Height > primaryScreenHeight)
{
this.Top = primaryScreenHeight - this.Height;
}
}

设置窗口在鼠标右下角

colorWin中添加如下方法

1
2
3
4
5
6
7
8
9
10
11
12
13
private void MoveBottomRightEdgeOfWindowToMousePosition()
{
var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
var mouse = transform.Transform(GetMousePosition());
Left = mouse.X + 2;
Top = mouse.Y - ActualHeight - 2;
}

public System.Windows.Point GetMousePosition()
{
System.Drawing.Point point = System.Windows.Forms.Control.MousePosition;
return new System.Windows.Point(point.X, point.Y);
}

更新位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new Thread(o => {
while (true) {
Dispatcher.Invoke(
() =>
{
if (Visibility == Visibility.Visible)
{
MoveBottomRightEdgeOfWindowToMousePosition();
}
}
);
Thread.Sleep(10);
}
})
{
IsBackground = true
}.Start();