[轉]【C#】分享一個彈出浮動層,像右鍵菜單那樣召即來揮則去

適用於:.net Winform項目html

背景:

有時候咱們須要開一個簡單的窗口來作一些事,例如輸入一些東西、點選一個item之類的,可能像這樣:windows

完了返回原窗體並獲取剛剛的輸入,這樣作並無什麼問題,但在幾天前我忽然產生了一些想法:爲何非得有板有眼的彈出一個窗體給用戶呢,是否是能夠在按鈕附近迅速呈現一個層來作這些事呢,相似快捷菜單那樣,用戶高興就在裏面作一下該作的事,不高興就在其它地方點一下它就消失,原本很輕便快捷的操做,DUANG~彈出一個窗體來會不會令用戶內心咯噔一下呢,感覺層面的事情每每是很微妙的,無論怎樣,我既然起了這個念頭,just try it。ide

我首先找了一下現成的方案,果真在牛逼的codeproject.com已經有牛人作了這樣的事情:函數

http://www.codeproject.com/Articles/17502/Simple-Popup-Control工具

簡單體驗了一下,的確是了不得的創造。原理是利用ToolStripControlHost能夠承載自定義控件的這一能力,讓下拉式控件ToolStripDropDown將任何自定義控件像右鍵菜單那樣彈出來(別忘了右鍵菜單ContextMenuStrip就是繼承自ToolStripDropDown),這樣就等於把菜單做爲一個容器,能夠彈出任何或簡單或複雜的控件組合,同時又具備菜單具備的便捷性,召之即來揮之即去。當時瞭解到這方案的時候真挺開心,正是我想要的效果,感受這下好了,不用瞎費勁本身造了。性能

但很快發現一個在我看來還挺在乎的不足,就是ToolStripDropDown只有Show,沒有ShowDialog,就是不能以模式化(Modal,也有叫模態的,鑑於MSDN都稱模式,我也隨流叫它模式)的方式彈出,這是由ToolStripDropDown的固有能力決定的,該方案既然基於ToolStripDropDown,天然也受限於此,不能模式化彈出。這樣帶來的問題是某些狀況下的調用體驗很差(體驗這種事固然不是用戶纔有的專利,俺們碼農也是人,也要講體驗的說),好比彈出的控件是讓用戶輸入一些東西,完了用戶點擊某個按鈕什麼的返回原窗體,而後在原窗體獲取用戶剛剛的輸入,而後接着作後面的事。因爲非模式的Show不會阻塞代碼,因此就不能在Show的下方想固然的獲取值、使用值~這是顯然的。要想得到值可能就得額外採起一些作法,例如響應彈出控件的關閉事件,或者把原窗體傳入彈出控件完了在後者中作本來應該在原窗體中作的事~等等,辦法固然有不少,但這都是由於只能Show帶來的多餘的事,有什麼比在一個方法中彈出控件、等待返回、繼續處理來的爽滑的呢,像這樣不是很天然嗎:ui

string s;
using (Popup p = new Popup())
{
    if (p.ShowDialog() != DialogResult.OK) { return; }

    s = p.InputText;
}
//go on
...

因此很遺憾,不得不揮別這個優秀的方案,造本身的輪子。不過受該方案的啓發,我想到用ContextMenu來作容器(注意這個菜單類跟上面提到的繼承自ToolStripDropDown的ContextMenuStrip大大的不一樣,前者是OS原生的菜單,就是在桌面、圖標以及文本框中右鍵彈出的那種菜單,.net是經過調API的方式來操做這樣的菜單,然後者則徹底是.net實現,更多信息請參考MSDN,此處不展開),由於ContextMenu的Show是阻塞式的,正合我意。但一番嘗試以後放棄,它的菜單項MenuItem不像ToolStripItem那樣能夠經過ToolStripControlHost承載自定義控件,但願是我能力有限,總之我作不到把自定義控件弄到ContextMenu上,也沒見過原生菜單上出現過文本框、複選框等奇怪的東西,若是您知道怎麼擴展原生菜單,還望不吝賜教,先行謝過!this

我仍是打回.net的主意,當中仍然是作了許多不一樣的嘗試,Form、Panel、UserControl、ContainerControl、Control等等看起來適合作容器層的東西都試了個遍,甚至從新在ToolStripDropDown上打主意,最後選用Form,改造一番,自我感受較理想的實現了我要的東西:一個叫作FloatLayerBase的基類,它自己繼承自System.Windows.Forms.Form類,而須要做爲浮動層顯示的應用則繼承自FloatLayerBase進行實現,例以下面這個接受用戶輸入數值的NumInputDemo實現:spa

樣子和特色:

  • 不會令父窗口失去焦點(不會搶焦點的層纔是好層):

    固然,男人不止一面:.net

    還有其它邊框樣式,有待用戶自行體驗,最後有demo提供。

  • 能夠有調整尺寸的手柄

  • 能夠點住客戶區拖動

別的一些應用:

這些都只是demo,沒那麼好看和強大,重點是有了這個FloatLayerBase,就能夠實現本身的浮動應用。

使用說明:

確保FloatLayerBase類在項目中~廢話。源碼在此:

using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace AhDung.WinForm.Controls
{
    /// <summary>
    /// 浮動層基類
    /// </summary>
    public class FloatLayerBase : Form
    {
        /// <summary>
        /// 鼠標消息篩選器
        /// </summary>
        //因爲本窗體爲WS_CHILD,因此不會收到在窗體之外點擊鼠標的消息
        //該消息篩選器的做用就是讓本窗體獲知鼠標點擊狀況,進而根據鼠標是否在本窗體之外的區域點擊,作出相應處理
        readonly AppMouseMessageHandler _mouseMsgFilter;

        /// <summary>
        /// 指示本窗體是否已ShowDialog過
        /// </summary>
        //因爲屢次ShowDialog會使OnLoad/OnShown重入,故需設置此標記以供重入時判斷
        bool _isShowDialogAgain;

        //邊框相關字段
        BorderStyle _borderType;
        Border3DStyle _border3DStyle;
        ButtonBorderStyle _borderSingleStyle;
        Color _borderColor;

        /// <summary>
        /// 獲取或設置邊框類型
        /// </summary>
        [Description("獲取或設置邊框類型。")]
        [DefaultValue(BorderStyle.Fixed3D)]
        public BorderStyle BorderType
        {
            get { return _borderType; }
            set
            {
                if (_borderType == value) { return; }
                _borderType = value;
                Invalidate();
            }
        }

        /// <summary>
        /// 獲取或設置三維邊框樣式
        /// </summary>
        [Description("獲取或設置三維邊框樣式。")]
        [DefaultValue(Border3DStyle.RaisedInner)]
        public Border3DStyle Border3DStyle
        {
            get { return _border3DStyle; }
            set
            {
                if (_border3DStyle == value) { return; }
                _border3DStyle = value;
                Invalidate();
            }
        }

        /// <summary>
        /// 獲取或設置線型邊框樣式
        /// </summary>
        [Description("獲取或設置線型邊框樣式。")]
        [DefaultValue(ButtonBorderStyle.Solid)]
        public ButtonBorderStyle BorderSingleStyle
        {
            get { return _borderSingleStyle; }
            set
            {
                if (_borderSingleStyle == value) { return; }
                _borderSingleStyle = value;
                Invalidate();
            }
        }

        /// <summary>
        /// 獲取或設置邊框顏色(僅當邊框類型爲線型時有效)
        /// </summary>
        [Description("獲取或設置邊框顏色(僅當邊框類型爲線型時有效)。")]
        [DefaultValue(typeof(Color), "DarkGray")]
        public Color BorderColor
        {
            get { return _borderColor; }
            set
            {
                if (_borderColor == value) { return; }
                _borderColor = value;
                Invalidate();
            }
        }

        protected override sealed CreateParams CreateParams
        {
            get
            {
                CreateParams prms = base.CreateParams;

                //prms.Style = 0;
                //prms.Style |= -2147483648;   //WS_POPUP
                prms.Style |= 0x40000000;      //WS_CHILD  重要,只有CHILD窗體纔不會搶父窗體焦點
                prms.Style |= 0x4000000;       //WS_CLIPSIBLINGS
                prms.Style |= 0x10000;         //WS_TABSTOP
                prms.Style &= ~0x40000;        //WS_SIZEBOX       去除
                prms.Style &= ~0x800000;       //WS_BORDER        去除
                prms.Style &= ~0x400000;       //WS_DLGFRAME      去除
                //prms.Style &= ~0x20000;      //WS_MINIMIZEBOX   去除
                //prms.Style &= ~0x10000;      //WS_MAXIMIZEBOX   去除

                prms.ExStyle = 0;
                //prms.ExStyle |= 0x1;         //WS_EX_DLGMODALFRAME 立體邊框
                //prms.ExStyle |= 0x8;         //WS_EX_TOPMOST
                prms.ExStyle |= 0x10000;       //WS_EX_CONTROLPARENT
                //prms.ExStyle |= 0x80;        //WS_EX_TOOLWINDOW
                //prms.ExStyle |= 0x100;       //WS_EX_WINDOWEDGE
                //prms.ExStyle |= 0x8000000;   //WS_EX_NOACTIVATE
                //prms.ExStyle |= 0x4;         //WS_EX_NOPARENTNOTIFY

                return prms;
            }
        }

        //構造函數
        public FloatLayerBase()
        {
            //初始化消息篩選器。添加和移除在顯示/隱藏時負責
            _mouseMsgFilter = new AppMouseMessageHandler(this);

            //初始化基類屬性
            InitBaseProperties();

            //初始化邊框相關
            _borderType = BorderStyle.Fixed3D;
            _border3DStyle = System.Windows.Forms.Border3DStyle.RaisedInner;
            _borderSingleStyle = ButtonBorderStyle.Solid;
            _borderColor = Color.DarkGray;
        }

        protected override void OnLoad(EventArgs e)
        {
            //防止重入
            if (_isShowDialogAgain) { return; }

            //需得減掉兩層邊框寬度,運行時尺寸才與設計時徹底相符,緣由不明
            //肯定與ControlBox、FormBorderStyle有關,但具體聯繫不明
            if (!DesignMode)
            {
                Size size = SystemInformation.FrameBorderSize;
                this.Size -= size + size;//不能夠用ClientSize,後者會根據窗口風格從新調整Size
            }
            base.OnLoad(e);
        }

        protected override void OnShown(EventArgs e)
        {
            //防止重入
            if (_isShowDialogAgain) { return; }

            //在OnShown中爲首次ShowDialog設標記
            if (Modal) { _isShowDialogAgain = true; }

            if (!DesignMode)
            {
                //激活首控件
                Control firstControl;
                if ((firstControl = GetNextControl(this, true)) != null)
                {
                    firstControl.Focus();
                }
            }
            base.OnShown(e);
        }

        protected override void WndProc(ref Message m)
        {
            //當本窗體做爲ShowDialog彈出時,在收到WM_SHOWWINDOW前,Owner會被Disable
            //故需在收到該消息後當即Enable它,否則Owner窗體和本窗體都將處於無響應狀態
            if (m.Msg == 0x18 && m.WParam != IntPtr.Zero && m.LParam == IntPtr.Zero
                && Modal && Owner != null && !Owner.IsDisposed)
            {
                if (Owner.IsMdiChild)
                {
                    //當Owner是MDI子窗體時,被Disable的是MDI主窗體
                    //而且Parent也會指向MDI主窗體,故需改回爲Owner,這樣彈出窗體的Location纔會相對於Owner而非MDIParent
                    NativeMethods.EnableWindow(Owner.MdiParent.Handle, true);
                    NativeMethods.SetParent(this.Handle, Owner.Handle);//只能用API設置Parent,由於模式窗體是TopLevel,.Net拒絕爲頂級窗體設置Parent
                }
                else
                {
                    NativeMethods.EnableWindow(Owner.Handle, true);
                }
            }
            base.WndProc(ref m);
        }

        //畫邊框
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            base.OnPaintBackground(e);

            if (_borderType == BorderStyle.Fixed3D)//繪製3D邊框
            {
                ControlPaint.DrawBorder3D(e.Graphics, ClientRectangle, Border3DStyle);
            }
            else if (_borderType == BorderStyle.FixedSingle)//繪製線型邊框
            {
                ControlPaint.DrawBorder(e.Graphics, ClientRectangle, BorderColor, BorderSingleStyle);
            }
        }

        //顯示後添加鼠標消息篩選器以開始捕捉,隱藏時則移除篩選器。之因此不放Dispose中是想盡早移除篩選器
        protected override void OnVisibleChanged(EventArgs e)
        {
            if (!DesignMode)
            {
                if (Visible) { Application.AddMessageFilter(_mouseMsgFilter); }
                else { Application.RemoveMessageFilter(_mouseMsgFilter); }
            }
            base.OnVisibleChanged(e);
        }

        //實現窗體客戶區拖動
        //在WndProc中實現這個較麻煩,因此放到這裏作
        protected override void OnMouseDown(MouseEventArgs e)
        {
            //讓鼠標點擊客戶區時達到與點擊標題欄同樣的效果,以此實現客戶區拖動
            NativeMethods.ReleaseCapture();
            NativeMethods.SendMessage(Handle, 0xA1/*WM_NCLBUTTONDOWN*/, (IntPtr)2/*CAPTION*/, IntPtr.Zero);

            base.OnMouseDown(e);
        }

        /// <summary>
        /// 顯示爲模式窗體
        /// </summary>
        /// <param name="control">顯示在該控件下方</param>
        public DialogResult ShowDialog(Control control)
        {
            return ShowDialog(control, 0, control.Height);
        }

        /// <summary>
        /// 顯示爲模式窗體
        /// </summary>
        /// <param name="control">觸發彈出窗體的控件</param>
        /// <param name="offsetX">相對control水平偏移</param>
        /// <param name="offsetY">相對control垂直偏移</param>
        public DialogResult ShowDialog(Control control, int offsetX, int offsetY)
        {
            return ShowDialog(control, new Point(offsetX, offsetY));
        }

        /// <summary>
        /// 顯示爲模式窗體
        /// </summary>
        /// <param name="control">觸發彈出窗體的控件</param>
        /// <param name="offset">相對control偏移</param>
        public DialogResult ShowDialog(Control control, Point offset)
        {
            return this.ShowDialogInternal(control, offset);
        }

        /// <summary>
        /// 顯示爲模式窗體
        /// </summary>
        /// <param name="item">顯示在該工具欄項的下方</param>
        public DialogResult ShowDialog(ToolStripItem item)
        {
            return ShowDialog(item, 0, item.Height);
        }

        /// <summary>
        /// 顯示爲模式窗體
        /// </summary>
        /// <param name="item">觸發彈出窗體的工具欄項</param>
        /// <param name="offsetX">相對item水平偏移</param>
        /// <param name="offsetY">相對item垂直偏移</param>
        public DialogResult ShowDialog(ToolStripItem item, int offsetX, int offsetY)
        {
            return ShowDialog(item, new Point(offsetX, offsetY));
        }

        /// <summary>
        /// 顯示爲模式窗體
        /// </summary>
        /// <param name="item">觸發彈出窗體的工具欄項</param>
        /// <param name="offset">相對item偏移</param>
        public DialogResult ShowDialog(ToolStripItem item, Point offset)
        {
            return this.ShowDialogInternal(item, offset);
        }

        /// <summary>
        /// 顯示窗體
        /// </summary>
        /// <param name="control">顯示在該控件下方</param>
        public void Show(Control control)
        {
            Show(control, 0, control.Height);
        }

        /// <summary>
        /// 顯示窗體
        /// </summary>
        /// <param name="control">觸發彈出窗體的控件</param>
        /// <param name="offsetX">相對control水平偏移</param>
        /// <param name="offsetY">相對control垂直偏移</param>
        public void Show(Control control, int offsetX, int offsetY)
        {
            Show(control, new Point(offsetX, offsetY));
        }

        /// <summary>
        /// 顯示窗體
        /// </summary>
        /// <param name="control">觸發彈出窗體的控件</param>
        /// <param name="offset">相對control偏移</param>
        public void Show(Control control, Point offset)
        {
            this.ShowInternal(control, offset);
        }

        /// <summary>
        /// 顯示窗體
        /// </summary>
        /// <param name="item">顯示在該工具欄下方</param>
        public void Show(ToolStripItem item)
        {
            Show(item, 0, item.Height);
        }

        /// <summary>
        /// 顯示窗體
        /// </summary>
        /// <param name="item">觸發彈出窗體的工具欄項</param>
        /// <param name="offsetX">相對item水平偏移</param>
        /// <param name="offsetY">相對item垂直偏移</param>
        public void Show(ToolStripItem item, int offsetX, int offsetY)
        {
            Show(item, new Point(offsetX, offsetY));
        }

        /// <summary>
        /// 顯示窗體
        /// </summary>
        /// <param name="item">觸發彈出窗體的工具欄項</param>
        /// <param name="offset">相對item偏移</param>
        public void Show(ToolStripItem item, Point offset)
        {
            this.ShowInternal(item, offset);
        }

        /// <summary>
        /// ShowDialog內部方法
        /// </summary>
        private DialogResult ShowDialogInternal(Component controlOrItem, Point offset)
        {
            //快速連續彈出本窗體將有可能遇到還沒有Hide的狀況下再次彈出,這會引起異常,故需作處理
            if (this.Visible) { return System.Windows.Forms.DialogResult.None; }

            this.SetLocationAndOwner(controlOrItem, offset);
            return base.ShowDialog();
        }

        /// <summary>
        /// Show內部方法
        /// </summary>
        private void ShowInternal(Component controlOrItem, Point offset)
        {
            if (this.Visible) { return; }//緣由見ShowDialogInternal

            this.SetLocationAndOwner(controlOrItem, offset);
            base.Show();
        }

        /// <summary>
        /// 設置座標及全部者
        /// </summary>
        /// <param name="controlOrItem">控件或工具欄項</param>
        /// <param name="offset">相對偏移</param>
        private void SetLocationAndOwner(Component controlOrItem, Point offset)
        {
            Point pt = Point.Empty;

            if (controlOrItem is ToolStripItem)
            {
                ToolStripItem item = (ToolStripItem)controlOrItem;
                pt.Offset(item.Bounds.Location);
                controlOrItem = item.Owner;
            }

            Control c = (Control)controlOrItem;
            pt.Offset(GetControlLocationInForm(c));
            pt.Offset(offset);
            this.Location = pt;

            //設置Owner屬性與Show[Dialog](Owner)有不一樣,當Owner是MDIChild時,後者會改Owner爲MDIParent
            this.Owner = c.FindForm();
        }

        /// <summary>
        /// 獲取控件在窗體中的座標
        /// </summary>
        private static Point GetControlLocationInForm(Control c)
        {
            Point pt = c.Location;
            while (!((c = c.Parent) is Form))
            {
                pt.Offset(c.Location);
            }
            return pt;
        }

        #region 屏蔽對本類影響重大的基類方法和屬性

        /// <summary>
        /// 初始化部分基類屬性
        /// </summary>
        private void InitBaseProperties()
        {
            base.ControlBox = false;                           //重要
            //必須得是SizableToolWindow才能支持調整大小的同時,不受SystemInformation.MinWindowTrackSize的限制
            base.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow;
            base.Text = string.Empty;                          //重要
            base.HelpButton = false;
            base.Icon = null;
            base.IsMdiContainer = false;
            base.MaximizeBox = false;
            base.MinimizeBox = false;
            base.ShowIcon = false;
            base.ShowInTaskbar = false;
            base.StartPosition = FormStartPosition.Manual;     //重要
            base.TopMost = false;
            base.WindowState = FormWindowState.Normal;
        }

        //屏蔽原方法
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("請使用別的重載!", true)]
        public new DialogResult ShowDialog() { throw new NotImplementedException(); }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("請使用別的重載!", true)]
        public new DialogResult ShowDialog(IWin32Window owner) { throw new NotImplementedException(); }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("請使用別的重載!", true)]
        public new void Show() { throw new NotImplementedException(); }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("請使用別的重載!", true)]
        public new void Show(IWin32Window owner) { throw new NotImplementedException(); }

        //屏蔽原屬性
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("禁用該屬性!", true)]
        public new bool ControlBox { get { return false; } set { } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("設置邊框請使用Border相關屬性!", true)]
        public new FormBorderStyle FormBorderStyle { get { return System.Windows.Forms.FormBorderStyle.SizableToolWindow; } set { } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("禁用該屬性!", true)]
        public override sealed string Text { get { return string.Empty; } set { } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("禁用該屬性!", true)]
        public new bool HelpButton { get { return false; } set { } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("禁用該屬性!", true)]
        public new Image Icon { get { return null; } set { } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("禁用該屬性!", true)]
        public new bool IsMdiContainer { get { return false; } set { } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("禁用該屬性!", true)]
        public new bool MaximizeBox { get { return false; } set { } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("禁用該屬性!", true)]
        public new bool MinimizeBox { get { return false; } set { } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("禁用該屬性!", true)]
        public new bool ShowIcon { get { return false; } set { } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("禁用該屬性!", true)]
        public new bool ShowInTaskbar { get { return false; } set { } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("禁用該屬性!", true)]
        public new FormStartPosition StartPosition { get { return FormStartPosition.Manual; } set { } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("禁用該屬性!", true)]
        public new bool TopMost { get { return false; } set { } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Obsolete("禁用該屬性!", true)]
        public new FormWindowState WindowState { get { return FormWindowState.Normal; } set { } }

        #endregion

        /// <summary>
        /// 程序鼠標消息篩選器
        /// </summary>
        private class AppMouseMessageHandler : IMessageFilter
        {
            readonly FloatLayerBase _layerForm;

            public AppMouseMessageHandler(FloatLayerBase layerForm)
            {
                _layerForm = layerForm;
            }

            public bool PreFilterMessage(ref Message m)
            {
                //若是在本窗體之外點擊鼠標,隱藏本窗體
                //若想在點擊標題欄、滾動條等非客戶區也要讓本窗體消失,取消0xA1的註釋便可
                //本例是根據座標判斷,亦能夠改成根據句柄,但要考慮子孫控件
                //之因此用API而不用Form.DesktopBounds是由於後者不可靠
                if ((m.Msg == 0x201/*|| m.Msg==0xA1*/)
                    && _layerForm.Visible && !NativeMethods.GetWindowRect(_layerForm.Handle).Contains(MousePosition))
                {
                    _layerForm.Hide();//之因此不Close是考慮應該由調用者負責銷燬
                }

                return false;
            }
        }

        /// <summary>
        /// API封裝類
        /// </summary>
        private static class NativeMethods
        {
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool EnableWindow(IntPtr hWnd, bool bEnable);

            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

            [DllImport("user32.dll")]
            public static extern bool ReleaseCapture();

            [DllImport("user32.dll", SetLastError = true)]
            public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

            [DllImport("user32.dll", SetLastError = true)]
            private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);

            [StructLayout(LayoutKind.Sequential)]
            private struct RECT
            {
                public int left;
                public int top;
                public int right;
                public int bottom;

                public static explicit operator Rectangle(RECT rect)
                {
                    return new Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
                }
            }

            public static Rectangle GetWindowRect(IntPtr hwnd)
            {
                RECT rect;
                GetWindowRect(hwnd, out rect);
                return (Rectangle)rect;
            }
        }
    }
}
View Code

新建繼承窗體,選擇繼承自FloatLayerBase類;也能夠新建普通窗體,而後把基類由Form改成FloatLayerBase

在設計器和源碼中打造浮動應用

在須要的地方使用它。關於使用,先看一下FloatLayerBase的部分公開成員:

//屬性
public BorderStyle BorderType { get; set; }
public Border3DStyle Border3DStyle { get; set; }
public ButtonBorderStyle BorderSingleStyle { get; set; }
public Color BorderColor { get; set; }

//方法
public void Show(Control control);
public void Show(Control control, Point offset);
public void Show(Control control, int offsetX, int offsetY);
public void Show(ToolStripItem item);
public void Show(ToolStripItem item, Point offset);
public void Show(ToolStripItem item, int offsetX, int offsetY);
public DialogResult ShowDialog(Control control);
public DialogResult ShowDialog(Control control, Point offset);
public DialogResult ShowDialog(Control control, int offsetX, int offsetY);
public DialogResult ShowDialog(ToolStripItem item);
public DialogResult ShowDialog(ToolStripItem item, Point offset);
public DialogResult ShowDialog(ToolStripItem item, int offsetX, int offsetY);

上面4個屬性都是跟邊框有關的,邊框總共有3種形態,三維、線型、無,由BorderType指定;當爲三維形態時,由Border3DStyle指定具體樣式;爲線型時,由BorderSingleStyle和BorderColor分別指定具體線型和顏色。原Form.FormBorderStyle屬性已被屏蔽,不容許子類訪問,還有若干原Form的屬性也已屏蔽,緣由都在源碼裏。另外,原Form.SizeGripStyle照常使用,是否容許調整浮動層大小就靠它了

方法就說一下Show和ShowDialog,顯然分別是用來非模式化/模式化顯示浮動層的,二者在調用角度的重大區別就是,前者不會阻塞代碼,後者則會,實際應用中根據狀況選用。每一個方法從參數又分Control和ToolStripItem兩類,都是表明從什麼控件上彈出浮動層的意思,前者接受Button、TextBox等控件(不能傳入Form,後果會不愉快),後者接受工具欄上面的項目,例如ToolStripButton、ToolStripTextBox之類的。重載能夠指定相對control或item的偏移位置,默認是在control/item的下方彈出浮動層。最後不管是Show仍是ShowDialog彈出來的浮動層,均可以像右鍵菜單那樣經過在其它地方點鼠標使之消失,這裏須要說明一下:

  • 鼠標只會點在本程序內的窗體中時,讓浮動層消失。點在程序外的窗口、桌面、任務欄這些則不會。爲何要這樣是由於要作到徹底像右鍵菜單那樣對全局鼠標敏感,須要全局鉤子,這會增長代碼量(性能且不說,沒測過不妄言),並且我認爲不必全局敏感
  • 浮動層消失是調用Hide方法,因此對於模式化打開的浮動層,會返回DialogResult.Cancel,這是.net對模式對話框的設計使然,模式對話框被Hide或Close時,就是返回Cancel。在此也提醒一下調用者,在使用模式對話框時,永遠考慮有返回Cancel這種狀況,不限於本例,而是全部對話框

原 Show(), Show(IWin32Window) 和 ShowDialog(), ShowDialog(IWin32Window) 已被屏蔽,緣由見源碼。

Demo下載:

編寫期間一直使用PopupFormBase做爲類名,發佈最後時刻才改成如今的FloatLayerBase,因此demo中可能尚有依據原名起名的子類、方法名等。

http://pan.baidu.com/s/1mgnGPGc

裏面有個Tester供您體驗。

 

-文畢-

(轉自:http://www.myexception.cn/c-sharp/1998342.html)

相關文章
相關標籤/搜索