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 { /// /// 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 /// 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; /// /// Available working area on the screen. This excludes taskbars and other /// docked windows. /// 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; } /// /// Gets an array of all of the displays on the system. /// 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; } } /// /// Gets the bounds of the display. /// public Rect Bounds { get; } /// /// Gets the device name associated with a display. /// public string DeviceName { get; } /// /// Gets a value indicating whether a particular display is the primary device. /// public bool Primary { get; } /// /// Gets the primary display. /// 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); } } /// /// Gets the working area of the screen. /// 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; } } /// /// Screen instances call this property to determine /// if their WorkingArea cache needs to be invalidated. /// 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; } } /// /// Specifies a value that indicates whether the specified object is equal to this one. /// public override bool Equals(object obj) { return obj is Screen comp && _hmonitor == comp._hmonitor; } /// /// Retrieves a for the monitor that contains the specified point. /// 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); } /// /// Retrieves a for the monitor that contains the largest region of the Rect. /// 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); } /// /// Retrieves the working area for the monitor that is closest to the specified point. /// public static Rect GetWorkingArea(Point pt) { return FromPoint(pt).WorkingArea; } /// /// Retrieves the working area for the monitor that contains the largest region of the specified Rect. /// public static Rect GetWorkingArea(Rect rect) { return FromRect(rect).WorkingArea; } /// /// Retrieves the bounds of the monitor that is closest to the specified point. /// public static Rect GetBounds(Point pt) { return FromPoint(pt).Bounds; } /// /// Retrieves the bounds of the monitor that contains the largest region of the specified Rect. /// public static Rect GetBounds(Rect rect) { return FromRect(rect).Bounds; } /// /// Computes and retrieves a hash code for an object. /// public override int GetHashCode() { return (int)_hmonitor; } /// /// Called by the SystemEvents class when our display settings are /// changing. We cache screen information and at this point we must /// invalidate our cache. /// 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; } /// /// 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. /// private static void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) { if (e.Category == UserPreferenceCategory.Desktop) { Interlocked.Increment(ref _desktopChangedCount); } } /// /// Retrieves a string representing this object. /// public override string ToString() { return GetType().Name + "[Bounds=" + Bounds + " WorkingArea=" + WorkingArea + " Primary=" + Primary + " DeviceName=" + DeviceName; } private class MonitorEnumCallback { public List Screens { get; } = new List(); public virtual bool Callback(IntPtr monitor, IntPtr hdc, IntPtr lprcMonitor, IntPtr lparam) { Screens.Add(new Screen(monitor)); return true; } } } }