File: winforms\Managed\System\WinForms\CommonDialog.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="CommonDialog.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
/*
 */
namespace System.Windows.Forms {
    using System.Threading;
    using System.Runtime.InteropServices;
    using System.Runtime.Remoting;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System;
    using System.Drawing;   
    using System.Windows.Forms;    
    using System.Windows.Forms.Design;
    using Microsoft.Win32;
    using System.Security;
    using System.Security.Permissions;
 
    /// <include file='doc\CommonDialog.uex' path='docs/doc[@for="CommonDialog"]/*' />
    /// <devdoc>
    ///    <para>
    ///       Specifies the base class used for displaying
    ///       dialog boxes on the screen.
    ///    </para>
    /// </devdoc>
    [
    ToolboxItemFilter("System.Windows.Forms"),
    System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1012:AbstractTypesShouldNotHaveConstructors") // Shipped in Everett
    ]
    public abstract class CommonDialog : Component {
        private static readonly object EventHelpRequest = new object();
        private const int CDM_SETDEFAULTFOCUS = NativeMethods.WM_USER + 0x51;
        private static int helpMsg;
 
        private IntPtr defOwnerWndProc;
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
        private IntPtr hookedWndProc;
 
        private IntPtr  defaultControlHwnd;
 
        object userData;
 
        /// <include file='doc\CommonDialog.uex' path='docs/doc[@for="CommonDialog.CommonDialog"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Initializes a new instance of the <see cref='System.Windows.Forms.CommonDialog'/> class.
        ///    </para>
        /// </devdoc>
        public CommonDialog() {
        }
 
        /// <include file='doc\CommonDialog.uex' path='docs/doc[@for="CommonDialog.Tag"]/*' />
        [
        SRCategory(SR.CatData),
        Localizable(false),
        Bindable(true),
        SRDescription(SR.ControlTagDescr),
        DefaultValue(null),
        TypeConverter(typeof(StringConverter)),
        ]
        public object Tag {
            get {
                return userData;
            }
            set {
                userData = value;
            }
        }
 
        /// <include file='doc\CommonDialog.uex' path='docs/doc[@for="CommonDialog.HelpRequest"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Occurs when the user clicks the Help button on a common
        ///       dialog box.
        ///    </para>
        /// </devdoc>
        [SRDescription(SR.CommonDialogHelpRequested)]
        public event EventHandler HelpRequest {
            add {
                Events.AddHandler(EventHelpRequest, value);
            }
            remove {
                Events.RemoveHandler(EventHelpRequest, value);
            }
        }
 
        // Generate meaningful result from Windows.CommDlgExtendedError()
 
        /* Only used in PageSetupDialog.cs in a line that is commented out.
            Commenting out until we need it again.
        
        internal static string CommonDialogErrorToString(int error) {
            switch (error) {
                case NativeMethods.CDERR_DIALOGFAILURE: return "dialogfailure";
                case NativeMethods.CDERR_FINDRESFAILURE: return "findresfailure";
                case NativeMethods.CDERR_INITIALIZATION: return "initialization";
                case NativeMethods.CDERR_LOADRESFAILURE: return "loadresfailure";
                case NativeMethods.CDERR_LOADSTRFAILURE: return "loadstrfailure";
                case NativeMethods.CDERR_LOCKRESFAILURE: return "lockresfailure";
                case NativeMethods.CDERR_MEMALLOCFAILURE: return "memallocfailure";
                case NativeMethods.CDERR_MEMLOCKFAILURE: return "memlockfailure";
                case NativeMethods.CDERR_NOHINSTANCE: return "nohinstance";
                case NativeMethods.CDERR_NOHOOK: return "nohook";
                case NativeMethods.CDERR_NOTEMPLATE: return "notemplate";
                case NativeMethods.CDERR_REGISTERMSGFAIL: return "registermsgfail";
                case NativeMethods.CDERR_STRUCTSIZE: return "structsize";
                case NativeMethods.PDERR_CREATEICFAILURE: return "createicfailure";
                case NativeMethods.PDERR_DEFAULTDIFFERENT: return "defaultdifferent";
                case NativeMethods.PDERR_DNDMMISMATCH: return "dndmmismatch";
                case NativeMethods.PDERR_GETDEVMODEFAIL: return "getdevmodefail";
                case NativeMethods.PDERR_INITFAILURE: return "initfailure";
                case NativeMethods.PDERR_LOADDRVFAILURE: return "loaddrvfailure";
                case NativeMethods.PDERR_NODEFAULTPRN: return "nodefaultprn";
                case NativeMethods.PDERR_NODEVICES: return "nodevices";
                case NativeMethods.PDERR_PARSEFAILURE: return "parsefailure";
                case NativeMethods.PDERR_PRINTERNOTFOUND: return "printernotfound";
                case NativeMethods.PDERR_RETDEFFAILURE: return "retdeffailure";
                case NativeMethods.PDERR_SETUPFAILURE: return "setupfailure";
                case NativeMethods.CFERR_MAXLESSTHANMIN: return "maxlessthanmin";
                case NativeMethods.CFERR_NOFONTS: return "nofonts";
                case NativeMethods.FNERR_BUFFERTOOSMALL: return "buffertoosmall";
                case NativeMethods.FNERR_INVALIDFILENAME: return "invalidfilename";
                case NativeMethods.FNERR_SUBCLASSFAILURE: return "subclassfailure";
                case NativeMethods.FRERR_BUFFERLENGTHZERO : return "bufferlengthzero";
                default: return "unknown error";
            }
        }
 
        */
 
        /// <include file='doc\CommonDialog.uex' path='docs/doc[@for="CommonDialog.HookProc"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Defines the common dialog box hook
        ///       procedure that is overridden to add specific functionality to a common dialog
        ///       box.
        ///    </para>
        /// </devdoc>
        [SecurityPermission(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode),
        SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
        protected virtual IntPtr HookProc(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) {
            if (msg == NativeMethods.WM_INITDIALOG) {
                MoveToScreenCenter(hWnd);
 
                // Under some circumstances, the dialog
                // does not initially focus on any control. We fix that by explicitly
                // setting focus ourselves. See ASURT 39435.
                //
                this.defaultControlHwnd = wparam;
                UnsafeNativeMethods.SetFocus(new HandleRef(null, wparam));
            }
            else if (msg == NativeMethods.WM_SETFOCUS) {
                UnsafeNativeMethods.PostMessage(new HandleRef(null, hWnd), CDM_SETDEFAULTFOCUS, 0, 0);
            }
            else if (msg == CDM_SETDEFAULTFOCUS) {
                // If the dialog box gets focus, bounce it to the default control.
                // so we post a message back to ourselves to wait for the focus change then push it to the default
                // control. See ASURT 84016.
                //
                UnsafeNativeMethods.SetFocus(new HandleRef(this, defaultControlHwnd));
            }
            return IntPtr.Zero;
        }
 
        /// <include file='doc\CommonDialog.uex' path='docs/doc[@for="CommonDialog.MoveToScreenCenter"]/*' />
        /// <devdoc>
        ///     Centers the given window on the screen. This method is used by the default
        ///     common dialog hook procedure to center the dialog on the screen before it
        ///     is shown.
        /// </devdoc>
        internal static void MoveToScreenCenter(IntPtr hWnd) {
            NativeMethods.RECT r = new NativeMethods.RECT();
            UnsafeNativeMethods.GetWindowRect(new HandleRef(null, hWnd), ref r);
            Rectangle screen = Screen.GetWorkingArea(Control.MousePosition);
            int x = screen.X + (screen.Width - r.right + r.left) / 2;
            int y = screen.Y + (screen.Height - r.bottom + r.top) / 3;
            SafeNativeMethods.SetWindowPos(new HandleRef(null, hWnd), NativeMethods.NullHandleRef, x, y, 0, 0, NativeMethods.SWP_NOSIZE |
                                 NativeMethods.SWP_NOZORDER | NativeMethods.SWP_NOACTIVATE);
        }
 
        /// <include file='doc\CommonDialog.uex' path='docs/doc[@for="CommonDialog.OnHelpRequest"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Raises the <see cref='System.Windows.Forms.CommonDialog.HelpRequest'/>
        ///       event.
        ///    </para>
        /// </devdoc>
        protected virtual void OnHelpRequest(EventArgs e) {
            EventHandler handler = (EventHandler)Events[EventHelpRequest];
            if (handler != null) handler(this, e);
        }
 
        /// <include file='doc\CommonDialog.uex' path='docs/doc[@for="CommonDialog.OwnerWndProc"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Defines the owner window procedure that is
        ///       overridden to add specific functionality to a common dialog box.
        ///    </para>
        /// </devdoc>
        [
            System.Security.Permissions.SecurityPermissionAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Flags=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode),
            System.Security.Permissions.SecurityPermissionAttribute(System.Security.Permissions.SecurityAction.InheritanceDemand, Flags=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)
        ]
        protected virtual IntPtr OwnerWndProc(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam) {
            if (msg == helpMsg) {
                if (NativeWindow.WndProcShouldBeDebuggable) {
                    OnHelpRequest(EventArgs.Empty);
                }
                else {
                    try {
                        OnHelpRequest(EventArgs.Empty);
                    }
                    catch (Exception e) {
                        Application.OnThreadException(e);
                    }
                }
                return IntPtr.Zero;
            }
            return UnsafeNativeMethods.CallWindowProc(defOwnerWndProc, hWnd, msg, wparam, lparam);         
        }
 
        /// <include file='doc\CommonDialog.uex' path='docs/doc[@for="CommonDialog.Reset"]/*' />
        /// <devdoc>
        ///    <para>
        ///       When overridden in a derived class,
        ///       resets the properties of a common dialog to their default
        ///       values.
        ///    </para>
        /// </devdoc>
        [System.Security.Permissions.SecurityPermissionAttribute(System.Security.Permissions.SecurityAction.InheritanceDemand, Flags=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
        public abstract void Reset();
 
        /// <include file='doc\CommonDialog.uex' path='docs/doc[@for="CommonDialog.RunDialog"]/*' />
        /// <devdoc>
        ///    <para>
        ///       When overridden in a derived class,
        ///       specifies a common dialog box.
        ///    </para>
        /// </devdoc>
        [System.Security.Permissions.SecurityPermissionAttribute(System.Security.Permissions.SecurityAction.InheritanceDemand, Flags=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
        protected abstract bool RunDialog(IntPtr hwndOwner);
 
        /// <include file='doc\CommonDialog.uex' path='docs/doc[@for="CommonDialog.ShowDialog"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Runs a common dialog box.
        ///    </para>
        /// </devdoc>
        public DialogResult ShowDialog() {
            return ShowDialog(null);
        }
 
        /// <include file='doc\CommonDialog.uex' path='docs/doc[@for="CommonDialog.ShowDialog1"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Runs a common dialog box, parented to the given IWin32Window.
        ///    </para>
        /// </devdoc>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")]
        public DialogResult ShowDialog( IWin32Window owner ) {
 
            IntSecurity.SafeSubWindows.Demand();
 
            if (!SystemInformation.UserInteractive) {
                throw new InvalidOperationException(SR.GetString(SR.CantShowModalOnNonInteractive));
            }
 
            NativeWindow native = null;//This will be used if there is no owner or active window (declared here so it can be kept alive)
 
            IntPtr hwndOwner = IntPtr.Zero;
            DialogResult result = DialogResult.Cancel;
            try {
                if (owner != null) {
                    hwndOwner = Control.GetSafeHandle(owner);
                }
    
                if (hwndOwner == IntPtr.Zero) {
                    hwndOwner = UnsafeNativeMethods.GetActiveWindow();
                }
    
                if (hwndOwner == IntPtr.Zero) {
                    //We will have to create our own Window
                    native = new NativeWindow();
                    native.CreateHandle(new CreateParams());
                    hwndOwner = native.Handle;
                }
                
                if (helpMsg == 0) {
                    helpMsg = SafeNativeMethods.RegisterWindowMessage("commdlg_help");
                }
    
                NativeMethods.WndProc ownerProc = new NativeMethods.WndProc(this.OwnerWndProc);
                hookedWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(ownerProc);
                System.Diagnostics.Debug.Assert(IntPtr.Zero == defOwnerWndProc, "The previous subclass wasn't properly cleaned up");
 
                IntPtr userCookie = IntPtr.Zero;
                try {
                    //UnsafeNativeMethods.[Get|Set]WindowLong is smart enough to call SetWindowLongPtr on 64-bit OS
                    defOwnerWndProc = UnsafeNativeMethods.SetWindowLong(new HandleRef(this, hwndOwner), NativeMethods.GWL_WNDPROC, ownerProc);
 
                    if (Application.UseVisualStyles) {
                        userCookie = UnsafeNativeMethods.ThemingScope.Activate();
                    }
                    
                    Application.BeginModalMessageLoop();
                    try {
                        result = RunDialog(hwndOwner) ? DialogResult.OK : DialogResult.Cancel;
                    }
                    finally {
                        Application.EndModalMessageLoop();
                    }
                }
                finally {
                    IntPtr currentSubClass = UnsafeNativeMethods.GetWindowLong(new HandleRef(this, hwndOwner), NativeMethods.GWL_WNDPROC);
                    if ( IntPtr.Zero != defOwnerWndProc || currentSubClass != hookedWndProc) {
                        UnsafeNativeMethods.SetWindowLong(new HandleRef(this, hwndOwner), NativeMethods.GWL_WNDPROC, new HandleRef(this, defOwnerWndProc));
                    }
                    UnsafeNativeMethods.ThemingScope.Deactivate(userCookie);
 
                    defOwnerWndProc = IntPtr.Zero;
                    hookedWndProc = IntPtr.Zero;
                    //Ensure that the subclass delegate will not be GC collected until after it has been subclassed
                    GC.KeepAlive(ownerProc);
                }
            }
            finally {
                if (null != native) {
                    native.DestroyHandle();
                }
            }
 
            return result;
        }
    }
}