forked from espresso3389/AdaptiveKeyboardConfig
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PerMonitorDpi.cs
267 lines (222 loc) · 8.53 KB
/
PerMonitorDpi.cs
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Data;
using System.Windows.Interop;
using System.Windows.Media;
namespace PerMonitorDpi
{
public class PerMonitorDpiHelper : DependencyObject
{
public PerMonitorDpiHelper(Window window)
{
Debug.WriteLine(
string.Format("Process DPI Awareness: {0}", NativeMethods.GetProcessDpiAwareness(IntPtr.Zero)));
this.window = window;
this.systemDpi = window.GetSystemDpi();
this.hwndSource = PresentationSource.FromVisual(window) as HwndSource;
if (this.hwndSource != null)
{
this.currentDpi = this.hwndSource.GetDpi();
this.ChangeDpi(this.currentDpi);
this.hwndSource.AddHook(this.WndProc);
this.window.Closed += (sender, args) => this.hwndSource.RemoveHook(this.WndProc);
}
}
/// <summary>
/// It can be used for <see cref="FrameworkElement.LayoutTransform"/>.
/// </summary>
public Transform DpiScaleTransform
{
get { return (Transform)this.GetValue(DpiScaleTransformProperty); }
set { this.SetValue(DpiScaleTransformProperty, value); }
}
public static readonly DependencyProperty DpiScaleTransformProperty =
DependencyProperty.Register("DpiScaleTransform", typeof(Transform), typeof(PerMonitorDpiHelper), new UIPropertyMetadata(Transform.Identity));
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == (int)NativeMethods.WindowMessage.WM_DPICHANGED)
{
var dpiX = wParam.ToHiWord();
var dpiY = wParam.ToLoWord();
this.ChangeDpi(new Dpi(dpiX, dpiY));
handled = true;
}
return IntPtr.Zero;
}
/// <summary>
/// Bind <see cref="DpiScaleTransform"/> to the specified <see cref="FrameworkElement"/> object's <see cref="FrameworkElement.LayoutTransformProperty"/>.
/// </summary>
/// <param name="element">
/// The element which uses the <see cref="DpiScaleTransform"/> as its <see cref="FrameworkElement.LayoutTransform"/>.
/// If the parameter is <c>null</c>, the method does nothing and return immediately.
/// </param>
public void BindLayoutTransformTo(FrameworkElement element)
{
if (element == null)
return;
element.SetBinding(FrameworkElement.LayoutTransformProperty, new Binding()
{
Source = this,
Path = new PropertyPath(DpiScaleTransformProperty)
});
}
private void ChangeDpi(Dpi dpi)
{
if (!PerMonitorDpiMethods.IsSupported) return;
this.DpiScaleTransform = (dpi == this.systemDpi)
? Transform.Identity
: new ScaleTransform((double)dpi.X / this.systemDpi.X, (double)dpi.Y / this.systemDpi.Y);
this.window.Width = this.window.Width * dpi.X / this.currentDpi.X;
this.window.Height = this.window.Height * dpi.Y / this.currentDpi.Y;
Debug.WriteLine(string.Format("DPI Change: {0} -> {1} (System: {2})",
this.currentDpi, dpi, this.systemDpi));
this.currentDpi = dpi;
}
private Dpi systemDpi;
private Dpi currentDpi;
private readonly Window window;
private readonly HwndSource hwndSource;
}
internal static class PerMonitorDpiMethods
{
public static bool IsSupported
{
get
{
var version = Environment.OSVersion.Version;
return version.Major * 1000 + version.Minor >= 6003; // Windows 8.1: 6.3
}
}
public static Dpi GetSystemDpi(this Visual visual)
{
var source = PresentationSource.FromVisual(visual);
if (source != null && source.CompositionTarget != null)
{
return new Dpi(
(uint)(Dpi.Default.X * source.CompositionTarget.TransformToDevice.M11),
(uint)(Dpi.Default.Y * source.CompositionTarget.TransformToDevice.M22));
}
return Dpi.Default;
}
public static Dpi GetDpi(this HwndSource hwndSource, MonitorDpiType dpiType = MonitorDpiType.Default)
{
if (!IsSupported) return Dpi.Default;
var hMonitor = NativeMethods.MonitorFromWindow(
hwndSource.Handle,
NativeMethods.MonitorDefaultTo.Nearest);
uint dpiX = 1, dpiY = 1;
NativeMethods.GetDpiForMonitor(hMonitor, dpiType, ref dpiX, ref dpiY);
return new Dpi(dpiX, dpiY);
}
}
public struct Dpi
{
public static readonly Dpi Default = new Dpi(96, 96);
public uint X { get; set; }
public uint Y { get; set; }
public Dpi(uint x, uint y)
: this()
{
this.X = x;
this.Y = y;
}
public static bool operator ==(Dpi dpi1, Dpi dpi2)
{
return dpi1.X == dpi2.X && dpi1.Y == dpi2.Y;
}
public static bool operator !=(Dpi dpi1, Dpi dpi2)
{
return !(dpi1 == dpi2);
}
public bool Equals(Dpi other)
{
return this.X == other.X && this.Y == other.Y;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
return obj is Dpi && Equals((Dpi)obj);
}
public override int GetHashCode()
{
unchecked
{
return ((int)this.X * 397) ^ (int)this.Y;
}
}
public override string ToString()
{
return string.Format("[X={0},Y={1}]", X, Y);
}
}
/// <summary>
/// Identifies dots per inch (dpi) type.
/// </summary>
public enum MonitorDpiType
{
/// <summary>
/// MDT_Effective_DPI
/// <para>Effective DPI that incorporates accessibility overrides and matches what Desktop Window Manage (DWM) uses to scale desktop applications.</para>
/// </summary>
EffectiveDpi = 0,
/// <summary>
/// MDT_Angular_DPI
/// <para>DPI that ensures rendering at a compliant angular resolution on the screen, without incorporating accessibility overrides.</para>
/// </summary>
AngularDpi = 1,
/// <summary>
/// MDT_Raw_DPI
/// <para>Linear DPI of the screen as measures on the screen itself.</para>
/// </summary>
RawDpi = 2,
/// <summary>
/// MDT_Default
/// </summary>
Default = EffectiveDpi,
}
internal static class NativeMethods
{
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromWindow(IntPtr hwnd, MonitorDefaultTo dwFlags);
[DllImport("shcore.dll")]
public static extern void GetDpiForMonitor(IntPtr hmonitor, MonitorDpiType dpiType, ref uint dpiX, ref uint dpiY);
[DllImport("shcore.dll")]
private static extern int GetProcessDpiAwareness(IntPtr handle, ref ProcessDpiAwareness awareness);
public static ProcessDpiAwareness GetProcessDpiAwareness(IntPtr handle)
{
ProcessDpiAwareness pda = ProcessDpiAwareness.Unaware;
if (GetProcessDpiAwareness(handle, ref pda) == 0)
return pda;
return ProcessDpiAwareness.Unaware;
}
public enum MonitorDefaultTo
{
Null = 0,
Primary = 1,
Nearest = 2,
}
public enum WindowMessage
{
WM_DPICHANGED = 0x02E0,
}
public enum ProcessDpiAwareness
{
Unaware = 0,
SystemDpiAware = 1,
PerMonitorDpiAware = 2
}
}
internal static class IntPtrExtensions
{
public static ushort ToLoWord(this IntPtr dword)
{
return (ushort)((uint)dword & 0xffff);
}
public static ushort ToHiWord(this IntPtr dword)
{
return (ushort)((uint)dword >> 16);
}
}
}