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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
|
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using System.Windows;
namespace MaterialDesignThemes.Wpf
{
///<summary>
/// Represents a display device or multiple display devices on a single system.
/// Based on http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Screen.cs
/// </summary>
internal class Screen
{
private static class NativeMethods
{
private const string User32 = "user32.dll";
[DllImport(User32, ExactSpelling = true, CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.None)]
public static extern int GetSystemMetrics(int nIndex);
[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);
[DllImport(User32, ExactSpelling = true)]
[ResourceExposure(ResourceScope.None)]
public static extern IntPtr MonitorFromPoint(POINTSTRUCT pt, int flags);
[DllImport(User32, ExactSpelling = true)]
[ResourceExposure(ResourceScope.None)]
public static extern IntPtr MonitorFromRect(ref RECT rect, int flags);
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;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINTSTRUCT
{
public int x;
public int y;
public POINTSTRUCT(int x, int y)
{
this.x = x;
this.y = y;
}
}
public static readonly HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);
public const int SM_CMONITORS = 80;
}
private readonly IntPtr _hmonitor;
/// <summary>
/// Available working area on the screen. This excludes taskbars and other
/// docked windows.
/// </summary>
private Rect _workingArea = Rect.Empty;
private static readonly object _syncLock = new object();//used to lock this class before sync'ing to SystemEvents
private static int _desktopChangedCount = -1;//static counter of desktop size changes
private int _currentDesktopChangedCount = -1;//instance-based counter used to invalidate WorkingArea
// This identifier is just for us, so that we don't try to call the multimon
// functions if we just need the primary monitor... this is safer for
// non-multimon OSes.
private const int PRIMARY_MONITOR = unchecked((int)0xBAADF00D);
private const int MONITOR_DEFAULTTONEAREST = 0x00000002;
private const int MONITORINFOF_PRIMARY = 0x00000001;
private static readonly bool _multiMonitorSupport = NativeMethods.GetSystemMetrics(NativeMethods.SM_CMONITORS) != 0;
private static Screen[] _screens;
private Screen(IntPtr monitor)
{
if (!_multiMonitorSupport || monitor == (IntPtr)PRIMARY_MONITOR)
{
// Single monitor system
Bounds = new Rect(SystemParameters.VirtualScreenLeft, SystemParameters.VirtualScreenTop,
SystemParameters.VirtualScreenWidth, SystemParameters.VirtualScreenHeight);
Primary = true;
DeviceName = "DISPLAY";
}
else
{
// MultiMonitor System
// We call the 'A' version of GetMonitorInfoA() because
// the 'W' version just never fills out the struct properly on Win2K.
NativeMethods.MONITORINFOEX info = new NativeMethods.MONITORINFOEX();
NativeMethods.GetMonitorInfo(new HandleRef(null, monitor), info);
Bounds = new Rect(info.rcMonitor.left, info.rcMonitor.top, info.rcMonitor.right - info.rcMonitor.left, info.rcMonitor.bottom - info.rcMonitor.top);
Primary = (info.dwFlags & MONITORINFOF_PRIMARY) != 0;
DeviceName = new string(info.szDevice);
DeviceName = DeviceName.TrimEnd((char)0);
}
_hmonitor = monitor;
}
/// <summary>
/// Gets an array of all of the displays on the system.
/// </summary>
public static Screen[] AllScreens
{
get
{
if (_screens == null)
{
if (_multiMonitorSupport)
{
MonitorEnumCallback closure = new MonitorEnumCallback();
NativeMethods.MonitorEnumProc proc = closure.Callback;
NativeMethods.EnumDisplayMonitors(NativeMethods.NullHandleRef, null, proc, IntPtr.Zero);
if (closure.Screens.Count > 0)
{
Screen[] temp = new Screen[closure.Screens.Count];
closure.Screens.CopyTo(temp, 0);
_screens = temp;
}
else
{
_screens = new[] { new Screen((IntPtr)PRIMARY_MONITOR) };
}
}
else
{
_screens = new[] { PrimaryScreen };
}
// Now that we have our screens, attach a display setting changed
// event so that we know when to invalidate them.
SystemEvents.DisplaySettingsChanging += OnDisplaySettingsChanging;
}
return _screens;
}
}
/// <summary>
/// Gets the bounds of the display.
/// </summary>
public Rect Bounds { get; }
/// <summary>
/// Gets the device name associated with a display.
/// </summary>
public string DeviceName { get; }
/// <summary>
/// Gets a value indicating whether a particular display is the primary device.
/// </summary>
public bool Primary { get; }
/// <summary>
/// Gets the primary display.
/// </summary>
public static Screen PrimaryScreen
{
get
{
if (_multiMonitorSupport)
{
foreach (Screen screen in AllScreens)
{
if (screen.Primary)
{
return screen;
}
}
return null;
}
return new Screen((IntPtr)PRIMARY_MONITOR);
}
}
/// <summary>
/// Gets the working area of the screen.
/// </summary>
public Rect WorkingArea
{
get
{
//if the static Screen class has a different desktop change count
//than this instance then update the count and recalculate our working area
if (_currentDesktopChangedCount != DesktopChangedCount)
{
Interlocked.Exchange(ref _currentDesktopChangedCount, DesktopChangedCount);
if (!_multiMonitorSupport || _hmonitor == (IntPtr)PRIMARY_MONITOR)
{
// Single monitor system
_workingArea = SystemParameters.WorkArea;
}
else
{
// MultiMonitor System
// We call the 'A' version of GetMonitorInfoA() because
// the 'W' version just never fills out the struct properly on Win2K.
NativeMethods.MONITORINFOEX info = new NativeMethods.MONITORINFOEX();
NativeMethods.GetMonitorInfo(new HandleRef(null, _hmonitor), info);
_workingArea = new Rect(info.rcWork.left, info.rcWork.top, info.rcWork.right - info.rcWork.left, info.rcWork.bottom - info.rcWork.top);
}
}
return _workingArea;
}
}
/// <summary>
/// Screen instances call this property to determine
/// if their WorkingArea cache needs to be invalidated.
/// </summary>
private static int DesktopChangedCount
{
get
{
if (_desktopChangedCount == -1)
{
lock (_syncLock)
{
//now that we have a lock, verify (again) our changecount...
if (_desktopChangedCount == -1)
{
//sync the UserPreference.Desktop change event. We'll keep count
//of desktop changes so that the WorkingArea property on Screen
//instances know when to invalidate their cache.
SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
_desktopChangedCount = 0;
}
}
}
return _desktopChangedCount;
}
}
/// <summary>
/// Specifies a value that indicates whether the specified object is equal to this one.
/// </summary>
public override bool Equals(object obj)
{
return obj is Screen comp && _hmonitor == comp._hmonitor;
}
/// <summary>
/// Retrieves a <see cref='Screen'/> for the monitor that contains the specified point.
/// </summary>
public static Screen FromPoint(Point point)
{
if (_multiMonitorSupport)
{
NativeMethods.POINTSTRUCT pt = new NativeMethods.POINTSTRUCT((int)point.X, (int)point.Y);
return new Screen(NativeMethods.MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST));
}
return new Screen((IntPtr)PRIMARY_MONITOR);
}
/// <summary>
/// Retrieves a <see cref='Screen'/> for the monitor that contains the largest region of the Rect.
/// </summary>
public static Screen FromRect(Rect rect)
{
if (_multiMonitorSupport)
{
NativeMethods.RECT rc = new NativeMethods.RECT(rect);
return new Screen(NativeMethods.MonitorFromRect(ref rc, MONITOR_DEFAULTTONEAREST));
}
return new Screen((IntPtr)PRIMARY_MONITOR);
}
///<summary>
/// Retrieves the working area for the monitor that is closest to the specified point.
/// </summary>
public static Rect GetWorkingArea(Point pt)
{
return FromPoint(pt).WorkingArea;
}
///<summary>
/// Retrieves the working area for the monitor that contains the largest region of the specified Rect.
/// </summary>
public static Rect GetWorkingArea(Rect rect)
{
return FromRect(rect).WorkingArea;
}
///<summary>
/// Retrieves the bounds of the monitor that is closest to the specified point.
/// </summary>
public static Rect GetBounds(Point pt)
{
return FromPoint(pt).Bounds;
}
/// <summary>
/// Retrieves the bounds of the monitor that contains the largest region of the specified Rect.
/// </summary>
public static Rect GetBounds(Rect rect)
{
return FromRect(rect).Bounds;
}
/// <summary>
/// Computes and retrieves a hash code for an object.
/// </summary>
public override int GetHashCode()
{
return (int)_hmonitor;
}
/// <summary>
/// Called by the SystemEvents class when our display settings are
/// changing. We cache screen information and at this point we must
/// invalidate our cache.
/// </summary>
private static void OnDisplaySettingsChanging(object sender, EventArgs e)
{
// Now that we've responded to this event, we don't need it again until
// someone re-queries. We will re-add the event at that time.
SystemEvents.DisplaySettingsChanging -= OnDisplaySettingsChanging;
// Display settings changed, so the set of screens we have is invalid.
_screens = null;
}
/// <summary>
/// Called by the SystemEvents class when our display settings have
/// changed. Here, we increment a static counter that Screen instances
/// can check against to invalidate their cache.
/// </summary>
private static void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
{
if (e.Category == UserPreferenceCategory.Desktop)
{
Interlocked.Increment(ref _desktopChangedCount);
}
}
/// <summary>
/// Retrieves a string representing this object.
/// </summary>
public override string ToString()
{
return GetType().Name + "[Bounds=" + Bounds + " WorkingArea=" + WorkingArea + " Primary=" + Primary + " DeviceName=" + DeviceName;
}
private class MonitorEnumCallback
{
public List<Screen> Screens { get; } = new List<Screen>();
public virtual bool Callback(IntPtr monitor, IntPtr hdc, IntPtr lprcMonitor, IntPtr lparam)
{
Screens.Add(new Screen(monitor));
return true;
}
}
}
}
|