File: winforms\Managed\System\WinForms\Screen.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="Screen.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
/*
 */
 
namespace System.Windows.Forms {
    using System.Threading;
    using System.Runtime.InteropServices;
    using System.Diagnostics;
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Collections;
    using Microsoft.Win32;
    using Internal;
 
    /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen"]/*' />
    /// <devdoc>
    ///    <para>
    ///       Represents a display device or
    ///       multiple display devices on a single system.
    ///    </para>
    /// </devdoc>
    public class Screen {
 
        readonly IntPtr hmonitor;
        /// <devdoc>         
        ///     Bounds of the screen         
        /// </devdoc>         
        readonly Rectangle    bounds;
        /// <devdoc>         
        ///     Available working area on the screen. This excludes taskbars and other         
        ///     docked windows.         
        /// </devdoc>         
        private Rectangle    workingArea = Rectangle.Empty;
        /// <devdoc>         
        ///     Set to true if this screen is the primary monitor         
        /// </devdoc>         
        readonly bool         primary;
        /// <devdoc>         
        ///     Device name associated with this monitor         
        /// </devdoc>         
        readonly string       deviceName;
 
        readonly int          bitDepth;
 
        private static 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_DEFAULTTONULL       = 0x00000000;
        private const int MONITOR_DEFAULTTOPRIMARY    = 0x00000001;
        private const int MONITOR_DEFAULTTONEAREST    = 0x00000002;
        private const int MONITORINFOF_PRIMARY        = 0x00000001;
 
        private static bool multiMonitorSupport = (UnsafeNativeMethods.GetSystemMetrics(NativeMethods.SM_CMONITORS) != 0);
        private static Screen[] screens;
 
        internal Screen(IntPtr monitor) : this(monitor, IntPtr.Zero) {
        }
 
        internal Screen(IntPtr monitor, IntPtr hdc) {
 
            IntPtr screenDC = hdc;
 
            if (!multiMonitorSupport || monitor == (IntPtr)PRIMARY_MONITOR) {
                // Single monitor system
                //
                bounds = SystemInformation.VirtualScreen;
                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();
                SafeNativeMethods.GetMonitorInfo(new HandleRef(null, monitor), info);
                bounds = Rectangle.FromLTRB(info.rcMonitor.left, info.rcMonitor.top, info.rcMonitor.right, info.rcMonitor.bottom);
                primary = ((info.dwFlags & MONITORINFOF_PRIMARY) != 0);
 
                deviceName = new string(info.szDevice);
                deviceName = deviceName.TrimEnd((char)0);
 
                if (hdc == IntPtr.Zero) {
                    screenDC = UnsafeNativeMethods.CreateDC(deviceName);
                }
            }
            hmonitor = monitor;
 
            this.bitDepth = UnsafeNativeMethods.GetDeviceCaps(new HandleRef(null, screenDC), NativeMethods.BITSPIXEL);
            this.bitDepth *= UnsafeNativeMethods.GetDeviceCaps(new HandleRef(null, screenDC), NativeMethods.PLANES);
 
            if (hdc != screenDC) {
                UnsafeNativeMethods.DeleteDC(new HandleRef(null, screenDC));
            }
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.AllScreens"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets an array of all of the displays on the system.
        ///    </para>
        /// </devdoc>
        public static Screen[] AllScreens {
            get {
                if (screens == null) {
                    if (multiMonitorSupport) {
                        MonitorEnumCallback closure = new MonitorEnumCallback();
                        NativeMethods.MonitorEnumProc proc = new NativeMethods.MonitorEnumProc(closure.Callback);
                        SafeNativeMethods.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 Screen[] {new Screen((IntPtr)PRIMARY_MONITOR)};
                        }
                    }
                    else {
                        screens = new Screen[] {PrimaryScreen};
                    }
 
                    // Now that we have our screens, attach a display setting changed
                    // event so that we know when to invalidate them.
                    //
                    SystemEvents.DisplaySettingsChanging += new EventHandler(OnDisplaySettingsChanging);
                }
 
                return screens;
            }
        }
        
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.BitsPerPixel"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets Bits per Pixel value.
        ///    </para>
        /// </devdoc>
        public int BitsPerPixel {
            get {
                return bitDepth;
            }
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.Bounds"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets the bounds of the display.
        ///    </para>
        /// </devdoc>
        public Rectangle Bounds {
            get {
                return bounds;
            }
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.DeviceName"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets the device name associated with a display.
        ///    </para>
        /// </devdoc>
        public string DeviceName {
            get {
                return deviceName;
            }
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.Primary"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets a value indicating whether a particular display is
        ///       the primary device.
        ///    </para>
        /// </devdoc>
        public bool Primary {
            get {
                return primary;
            }
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.PrimaryScreen"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets the
        ///       primary display.
        ///    </para>
        /// </devdoc>
        public static Screen PrimaryScreen {
            get {
                if (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((IntPtr)PRIMARY_MONITOR, IntPtr.Zero);
                }
            }
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.WorkingArea"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets the working area of the screen.
        ///    </para>
        /// </devdoc>
        public 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 != Screen.DesktopChangedCount) {
 
                    Interlocked.Exchange(ref currentDesktopChangedCount, Screen.DesktopChangedCount);
 
                    if (!multiMonitorSupport ||hmonitor == (IntPtr)PRIMARY_MONITOR) {
                        // Single monitor system
                        //
                        workingArea = SystemInformation.WorkingArea;
                    }
                    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();
                        SafeNativeMethods.GetMonitorInfo(new HandleRef(null, hmonitor), info);
                        workingArea = Rectangle.FromLTRB(info.rcWork.left, info.rcWork.top, info.rcWork.right, info.rcWork.bottom);
                    }
                }
                
                return workingArea;
            }
        }
 
        /// <devdoc>
        ///     Screen instances call this property to determine
        ///     if their WorkingArea cache needs to be invalidated.
        /// </devdoc>
        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 += new UserPreferenceChangedEventHandler(OnUserPreferenceChanged);
 
                            desktopChangedCount = 0;
                        }
                    }
                }
                return desktopChangedCount;
            }
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.Equals"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Specifies a value that indicates whether the specified object is equal to
        ///       this one.
        ///    </para>
        /// </devdoc>
        public override bool Equals(object obj) {
            if (obj is Screen) {
                Screen comp = (Screen)obj;
                if (hmonitor == comp.hmonitor) {
                    return true;
                }
            }
            return false;
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.FromPoint"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Retrieves a <see cref='System.Windows.Forms.Screen'/>
        ///       for the monitor that contains the specified point.
        ///       
        ///    </para>
        /// </devdoc>
        public static Screen FromPoint(Point point) {
            if (multiMonitorSupport) {
                NativeMethods.POINTSTRUCT pt = new NativeMethods.POINTSTRUCT(point.X, point.Y);
                return new Screen(SafeNativeMethods.MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST));
            }
            else {
                return new Screen((IntPtr)PRIMARY_MONITOR);
            }
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.FromRectangle"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Retrieves a <see cref='System.Windows.Forms.Screen'/>
        ///       for the monitor that contains the
        ///       largest region of the rectangle.
        ///       
        ///    </para>
        /// </devdoc>
        public static Screen FromRectangle(Rectangle rect) {
            if (multiMonitorSupport) {
                NativeMethods.RECT rc = NativeMethods.RECT.FromXYWH(rect.X, rect.Y, rect.Width, rect.Height);
                return new Screen(SafeNativeMethods.MonitorFromRect(ref rc, MONITOR_DEFAULTTONEAREST));
            }
            else {
                return new Screen((IntPtr)PRIMARY_MONITOR, IntPtr.Zero);
            }
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.FromControl"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Retrieves a <see cref='System.Windows.Forms.Screen'/>
        ///       for the monitor that contains the largest
        ///       region of the window of the control.
        ///    </para>
        /// </devdoc>
        public static Screen FromControl(Control control) {
            return FromHandleInternal(control.Handle);
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.FromHandle"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Retrieves a <see cref='System.Windows.Forms.Screen'/>
        ///       for the monitor that
        ///       contains the largest region of the window.
        ///    </para>
        /// </devdoc>
        public static Screen FromHandle(IntPtr hwnd) {
            Debug.WriteLineIf(IntSecurity.SecurityDemand.TraceVerbose, "ObjectFromWin32Handle Demanded");
            IntSecurity.ObjectFromWin32Handle.Demand();
            return FromHandleInternal(hwnd);
        }
 
        internal static Screen FromHandleInternal(IntPtr hwnd) {
            if (multiMonitorSupport) {
                return new Screen(SafeNativeMethods.MonitorFromWindow(new HandleRef(null, hwnd), MONITOR_DEFAULTTONEAREST));
            }
            else {
                return new Screen((IntPtr)PRIMARY_MONITOR, IntPtr.Zero);
            }
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.GetWorkingArea"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Retrieves the working area for the monitor that is closest to the
        ///       specified point.
        ///       
        ///    </para>
        /// </devdoc>
        public static Rectangle GetWorkingArea(Point pt) {
            return Screen.FromPoint(pt).WorkingArea;
        }
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.GetWorkingArea1"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Retrieves the working area for the monitor that contains the largest region
        ///       of the specified rectangle.
        ///       
        ///    </para>
        /// </devdoc>
        public static Rectangle GetWorkingArea(Rectangle rect) {
            return Screen.FromRectangle(rect).WorkingArea;
        }
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.GetWorkingArea2"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Retrieves the working area for the monitor that contains the largest
        ///       region of the specified control.
        ///       
        ///    </para>
        /// </devdoc>
        public static Rectangle GetWorkingArea(Control ctl) {
            return Screen.FromControl(ctl).WorkingArea;
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.GetBounds"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Retrieves the bounds of the monitor that is closest to the specified
        ///       point.
        ///    </para>
        /// </devdoc>
        public static Rectangle GetBounds(Point pt) {
            return Screen.FromPoint(pt).Bounds;
        }
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.GetBounds1"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Retrieves the bounds of the monitor that contains the largest region of the
        ///       specified rectangle.
        ///    </para>
        /// </devdoc>
        public static Rectangle GetBounds(Rectangle rect) {
            return Screen.FromRectangle(rect).Bounds;
        }
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.GetBounds2"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Retrieves the bounds of the monitor
        ///       that contains the largest region of the specified control.
        ///    </para>
        /// </devdoc>
        public static Rectangle GetBounds(Control ctl) {
            return Screen.FromControl(ctl).Bounds;
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.GetHashCode"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Computes and retrieves a hash code for an object.
        ///    </para>
        /// </devdoc>
        public override int GetHashCode() {
            return(int)hmonitor;
        }
 
        /// <devdoc>
        ///     Called by the SystemEvents class when our display settings are
        ///     changing.  We cache screen information and at this point we must
        ///     invalidate our cache.
        /// </devdoc>
        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 -= new EventHandler(OnDisplaySettingsChanging);
 
            // Display settings changed, so the set of screens we have is invalid.
            //
            screens = null;
        }
 
        /// <devdoc>
        ///     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.
        /// </devdoc>
        private static void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) {
 
            if (e.Category == UserPreferenceCategory.Desktop) {
                Interlocked.Increment(ref desktopChangedCount);
            }
        }
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.ToString"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Retrieves a string representing this object.
        ///    </para>
        /// </devdoc>
        public override string ToString() {
            return GetType().Name + "[Bounds=" + bounds.ToString() + " WorkingArea=" + WorkingArea.ToString() + " Primary=" + primary.ToString() + " DeviceName=" + deviceName;
        }
 
 
        /// <include file='doc\Screen.uex' path='docs/doc[@for="Screen.MonitorEnumCallback"]/*' />
        /// <devdoc>         
        /// </devdoc>         
        private class MonitorEnumCallback {
            public ArrayList screens = new ArrayList();
 
            public virtual bool Callback(IntPtr monitor, IntPtr hdc, IntPtr lprcMonitor, IntPtr lparam) {
                screens.Add(new Screen(monitor, hdc));
                return true;
            }
        }
    }
}