| File: System\Windows\Forms\Screen.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; using Microsoft.Win32; namespace System.Windows.Forms; /// <summary> /// Represents a display device or multiple display devices on a single system. /// </summary> public partial class Screen { private readonly HMONITOR _hmonitor; /// <summary> /// Bounds of the screen /// </summary> private readonly Rectangle _bounds; /// <summary> /// Available working area on the screen. This excludes taskbars and other docked windows. /// </summary> private Rectangle _workingArea = Rectangle.Empty; /// <summary> /// Set to true if this screen is the primary monitor /// </summary> private readonly bool _primary; /// <summary> /// Device name associated with this monitor /// </summary> private readonly string _deviceName; private readonly int _bitDepth; private static readonly Lock s_syncLock = new(); // used to lock this class before syncing to SystemEvents private static int s_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 static readonly HMONITOR s_primaryMonitor = (HMONITOR)unchecked((nint)0xBAADF00D); private static Screen[]? s_screens; internal Screen(HMONITOR monitor) : this(monitor, default) { } internal unsafe Screen(HMONITOR monitor, HDC hdc) { HDC screenDC = hdc; if (!SystemInformation.MultiMonitorSupport || monitor == s_primaryMonitor) { // Single monitor system _bounds = SystemInformation.VirtualScreen; _primary = true; _deviceName = "DISPLAY"; } else { // Multiple monitor system MONITORINFOEXW info = new() { monitorInfo = new() { cbSize = (uint)sizeof(MONITORINFOEXW) } }; // API takes EX, determines which one you pass by size. PInvokeCore.GetMonitorInfo(monitor, (MONITORINFO*)&info); _bounds = info.monitorInfo.rcMonitor; _primary = ((info.monitorInfo.dwFlags & PInvokeCore.MONITORINFOF_PRIMARY) != 0); _deviceName = new string(info.szDevice.ToString()); if (hdc.IsNull) { screenDC = PInvokeCore.CreateDCW(_deviceName, pwszDevice: null, pszPort: null, pdm: null); } } _hmonitor = monitor; _bitDepth = PInvokeCore.GetDeviceCaps(screenDC, GET_DEVICE_CAPS_INDEX.BITSPIXEL); _bitDepth *= PInvokeCore.GetDeviceCaps(screenDC, GET_DEVICE_CAPS_INDEX.PLANES); if (hdc != screenDC) { PInvokeCore.DeleteDC(screenDC); } } /// <summary> /// Gets an array of all of the displays on the system. /// </summary> public static unsafe Screen[] AllScreens { get { if (s_screens is null) { if (SystemInformation.MultiMonitorSupport) { List<Screen> screens = []; PInvokeCore.EnumDisplayMonitors((HMONITOR hmonitor, HDC hdc) => { screens.Add(new(hmonitor, hdc)); return true; }); s_screens = screens.Count > 0 ? [.. screens] : [new(s_primaryMonitor)]; } else { s_screens = [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 s_screens; } } /// <summary> /// Gets Bits per Pixel value. /// </summary> public int BitsPerPixel => _bitDepth; /// <summary> /// Gets the bounds of the display. /// </summary> public Rectangle Bounds => _bounds; /// <summary> /// Gets the device name associated with a display. /// </summary> public string DeviceName => _deviceName; /// <summary> /// Gets a value indicating whether a particular display is the primary device. /// </summary> public bool Primary => _primary; /// <summary> /// Gets the primary display. /// </summary> public static Screen? PrimaryScreen { get { if (SystemInformation.MultiMonitorSupport) { Screen[] screens = AllScreens; for (int i = 0; i < screens.Length; i++) { if (screens[i]._primary) { return screens[i]; } } return null; } else { return new Screen(s_primaryMonitor, default); } } } /// <summary> /// Gets the working area of the screen. /// </summary> public unsafe Rectangle 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 (!SystemInformation.MultiMonitorSupport || _hmonitor == s_primaryMonitor) { // Single monitor system _workingArea = SystemInformation.WorkingArea; } else { // Multiple monitor System MONITORINFOEXW info = new() { monitorInfo = new() { cbSize = (uint)sizeof(MONITORINFOEXW) } }; // API takes EX, determines which one you pass by size. PInvokeCore.GetMonitorInfo(_hmonitor, (MONITORINFO*)&info); _workingArea = info.monitorInfo.rcWork; } } 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 (s_desktopChangedCount == -1) { lock (s_syncLock) { // Now that we have a lock, verify (again) our changecount. if (s_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; s_desktopChangedCount = 0; } } } return s_desktopChangedCount; } } /// <summary> /// Specifies a value that indicates whether the specified object is equal to this one. /// </summary> public override bool Equals(object? obj) => 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) => SystemInformation.MultiMonitorSupport ? new Screen(PInvokeCore.MonitorFromPoint(point, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST)) : new Screen(s_primaryMonitor); /// <summary> /// Retrieves a <see cref="Screen"/> for the monitor that contains the largest region of the rectangle. /// </summary> public static Screen FromRectangle(Rectangle rect) => SystemInformation.MultiMonitorSupport ? new Screen(PInvokeCore.MonitorFromRect(rect, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST)) : new Screen(s_primaryMonitor, default); /// <summary> /// Retrieves a <see cref="Screen"/> for the monitor that contains the largest region of the window of the control. /// </summary> public static Screen FromControl(Control control) { ArgumentNullException.ThrowIfNull(control); return FromHandle(control.Handle); } /// <summary> /// Retrieves a <see cref="Screen"/> for the monitor that contains the largest region of the window. /// </summary> public static Screen FromHandle(IntPtr hwnd) => SystemInformation.MultiMonitorSupport ? new Screen(PInvokeCore.MonitorFromWindow((HWND)hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST)) : new Screen(s_primaryMonitor, default); /// <summary> /// Retrieves the working area for the monitor that is closest to the specified point. /// </summary> public static Rectangle GetWorkingArea(Point pt) => FromPoint(pt).WorkingArea; /// <summary> /// Retrieves the working area for the monitor that contains the largest region of the specified rectangle. /// </summary> public static Rectangle GetWorkingArea(Rectangle rect) => FromRectangle(rect).WorkingArea; /// <summary> /// Retrieves the working area for the monitor that contains the largest region of the specified control. /// </summary> public static Rectangle GetWorkingArea(Control ctl) => FromControl(ctl).WorkingArea; /// <summary> /// Retrieves the bounds of the monitor that is closest to the specified point. /// </summary> public static Rectangle GetBounds(Point pt) => FromPoint(pt).Bounds; /// <summary> /// Retrieves the bounds of the monitor that contains the largest region of the specified rectangle. /// </summary> public static Rectangle GetBounds(Rectangle rect) => FromRectangle(rect).Bounds; /// <summary> /// Retrieves the bounds of the monitor that contains the largest region of the specified control. /// </summary> public static Rectangle GetBounds(Control ctl) => FromControl(ctl).Bounds; /// <summary> /// Computes and retrieves a hash code for an object. /// </summary> public override int GetHashCode() => PARAM.ToInt(_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. s_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 s_desktopChangedCount); } } /// <summary> /// Retrieves a string representing this object. /// </summary> public override string ToString() => $"{GetType().Name}[Bounds={_bounds} WorkingArea={WorkingArea} Primary={_primary} DeviceName={_deviceName}"; }