生活随笔
收集整理的這篇文章主要介紹了
【C#】分享一个弹出容器层,像右键菜单那样召即来挥则去
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
【C#】分享一個(gè)彈出容器層,像右鍵菜單那樣召即來(lái)?yè)]則去
適用于: .net2.0+ Winform項(xiàng)目
------------------201508261813更新(源碼有更新、Demo未更新)------------------
重新繪制調(diào)整大小手柄(SizeGrip,右下角那個(gè)),因?yàn)橄到y(tǒng)自繪的太靠邊角,在XP下會(huì)蓋過(guò)那部分邊框,視覺體驗(yàn)不好。改進(jìn)如圖: 新增的DrawSizeGrip 方法就是繪制方法,是protected virtual 的,所以如果你看不上我畫的這個(gè),可以在子類重寫該方法畫你自己滿意的(題外,畫這個(gè)我還參考了VS2010的效果,不過(guò)是相反的,VS的是凸起效果,我這是塌陷style) 支持四邊+四角 全方位拖動(dòng)改變浮動(dòng)層尺寸,改善體驗(yàn)。如圖: 注:浮動(dòng)層是否可以調(diào)整大小是根據(jù)SizeGripStyle屬性決定,分3種情況:
SizeGripStyle為Show ,則始終允許用戶調(diào)整大小,手柄會(huì)出現(xiàn)、鼠標(biāo)移至邊緣邊角會(huì)產(chǎn)生視覺變化并可以拖動(dòng) SizeGripStyle為Hide ,則始終禁止用戶調(diào)整大小,手柄不 會(huì)出現(xiàn)、鼠標(biāo)移至邊緣邊角不 會(huì)產(chǎn)生視覺變化,也不可以拖動(dòng)改變大小 SizeGripStyle為Auto ,則在模式化打開(Modal為true,即通過(guò)ShowDialog打開的) 時(shí)與Show一致,非模式化打開(Modal為false,通過(guò)Show打開) 時(shí)與Hide一致,這也是原版Form的邏輯,只不過(guò)原版Form還會(huì)根據(jù)FormBorderStyle,但本類已將該屬性固化,所以請(qǐng)注意Auto這貨,建議始終顯式指定Show/Hide為妙 ------------------201508251458更新------------------
激活首控件之前是在OnShown中進(jìn)行,經(jīng)過(guò)研究,改為令TopMost=true,就能使浮動(dòng)層與正常窗體有一致的激活首控件行為,同時(shí)省卻了對(duì)OnShown的重寫 解決子控件有時(shí)沒有聚焦框(焦點(diǎn)虛線框)的問(wèn)題。如圖: 注:最后的demo沒更新,請(qǐng)重新取FloatLayerBase.cs源碼就好
------------------201508240846原文(已更新)------------------
背景: 有時(shí)候我們需要開一個(gè)簡(jiǎn)單的窗口來(lái)做一些事,例如輸入一些東西、點(diǎn)選一個(gè)item之類的,可能像這樣:
完了返回原窗體并獲取剛剛的輸入,這樣做并沒有什么問(wèn)題,但在幾天前我突然產(chǎn)生了一些想法:為什么非得有板有眼的彈出一個(gè)窗體給用戶呢,是不是可以在按鈕附近迅速呈現(xiàn)一個(gè)層來(lái)做這些事呢,類似快捷菜單那樣,用戶高興就在里面做一下該做的事,不高興就在其它地方點(diǎn)一下它就消失,本來(lái)很輕便快捷的操作,DUANG~彈出一個(gè)窗體來(lái)會(huì)不會(huì)令用戶心里咯噔一下呢,感受層面的事情往往是很微妙的,不管怎樣,我既然起了這個(gè)念頭,just try it。
我首先找了一下現(xiàn)成的方案,果然在牛逼的codeproject.com已經(jīng)有牛人做了這樣的事情:
http://www.codeproject.com/Articles/17502/Simple-Popup-Control
簡(jiǎn)單體驗(yàn)了一下,的確是了不起的創(chuàng)造。原理是利用ToolStripControlHost可以承載自定義控件的這一能力,讓下拉式控件ToolStripDropDown將任何自定義控件像右鍵菜單那樣彈出來(lái)(別忘了右鍵菜單ContextMenuStrip就是繼承自ToolStripDropDown) ,這樣就等于把菜單作為一個(gè)容器,可以彈出任何或簡(jiǎn)單或復(fù)雜的控件組合,同時(shí)又具有菜單具有的便捷性,召之即來(lái)?yè)]之即去。當(dāng)時(shí)了解到這方案的時(shí)候真挺開心,正是我想要的效果,感覺這下好了,不用瞎費(fèi)勁自己造了。
但很快發(fā)現(xiàn)一個(gè)在我看來(lái)還挺在意的不足,就是ToolStripDropDown只有Show,沒有ShowDialog,就是不能以模式化(Modal,也有叫模態(tài)的,鑒于MSDN都稱模式,我也隨流叫它模式) 的方式彈出,這是由ToolStripDropDown的固有能力決定的,該方案既然基于ToolStripDropDown,自然也受限于此,不能模式化彈出。這樣帶來(lái)的問(wèn)題是某些情況下的調(diào)用體驗(yàn)不好(體驗(yàn)這種事當(dāng)然不是用戶才有的專利,俺們碼農(nóng)也是人,也要講體驗(yàn)的說(shuō)) ,比如彈出的控件是讓用戶輸入一些東西,完了用戶點(diǎn)擊某個(gè)按鈕什么的返回原窗體,然后在原窗體獲取用戶剛剛的輸入,然后接著做后面的事。由于非模式的Show不會(huì)阻塞代碼,所以就不能在Show的下方想當(dāng)然的獲取值、使用值~這是顯然的。要想獲得值可能就得額外采取一些做法,例如響應(yīng)彈出控件的關(guān)閉事件,或者把原窗體傳入彈出控件完了在后者中做原本應(yīng)該在原窗體中做的事~等等,辦法當(dāng)然有很多,但這都是因?yàn)橹荒躍how帶來(lái)的多余的事,有什么比在一個(gè)方法中彈出控件、等待 返回、繼續(xù)處理來(lái)的爽滑的呢,像這樣不是很自然嗎:
string s;
using (Popup p =
new Popup())
{ if (p.ShowDialog() != DialogResult.OK) {
return ; }s =
p.InputText;
}
// go on
...
所以很遺憾,不得不揮別這個(gè)優(yōu)秀的方案,造自己的輪子。不過(guò)受該方案的啟發(fā),我想到用ContextMenu來(lái)做容器(注意這個(gè)菜單類跟上面提到的繼承自ToolStripDropDown的ContextMenuStrip大大的不同,前者是OS原生的菜單,就是在桌面、圖標(biāo)以及文本框中右鍵彈出的那種菜單,.net是通過(guò)調(diào)API的方式來(lái)操作這樣的菜單,而后者則完全是.net實(shí)現(xiàn),更多信息請(qǐng)參考MSDN,此處不展開) ,因?yàn)镃ontextMenu的Show是阻塞式的,正合我意。但一番嘗試之后放棄,它的菜單項(xiàng)MenuItem不像ToolStripItem那樣可以通過(guò)ToolStripControlHost承載自定義控件,希望是我能力有限,總之我做不到把自定義控件弄到ContextMenu上,也沒見過(guò)原生菜單上出現(xiàn)過(guò)文本框、復(fù)選框等奇怪的東西,如果您知道怎么擴(kuò)展原生菜單,還望不吝賜教,先行謝過(guò)!
我還是打回.net的主意,當(dāng)中仍然是做了許多不同的嘗試,Form、Panel、UserControl、ContainerControl、Control等等看起來(lái)適合做容器層的東西都試了個(gè)遍,甚至重新在ToolStripDropDown上打主意,最后選用Form,改造一番,自我感覺較理想的實(shí)現(xiàn)了我要的東西:一個(gè)叫做FloatLayerBase 的基類,它本身繼承自System.Windows.Forms.Form類,而需要作為浮動(dòng)層顯示的應(yīng)用則繼承自FloatLayerBase進(jìn)行實(shí)現(xiàn),例如下面這個(gè)接受用戶輸入數(shù)值的NumInputDemo實(shí)現(xiàn):
樣子和特點(diǎn): 別的一些應(yīng)用:
這些都只是demo,沒那么好看和強(qiáng)大,重點(diǎn)是有了這個(gè)FloatLayerBase,就可以實(shí)現(xiàn)自己的浮動(dòng)應(yīng)用。
使用說(shuō)明: 確保FloatLayerBase類在項(xiàng)目中~廢話。源碼 在此: using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms; namespace AhDung.WinForm.Controls
{ /// <summary> /// 浮動(dòng)層基類 /// </summary> // Update:201508251451 // - 將由OnShow中負(fù)責(zé)的首控件激活改為設(shè)TopMost=true實(shí)現(xiàn),同時(shí)移除OnShow重寫 // - 解決子控件無(wú)聚焦框(焦點(diǎn)虛線框,FocusCues)的問(wèn)題 // Update:201508261806 // - 重繪右下角調(diào)整大小手柄,解決系統(tǒng)自繪在XP下太靠邊角從而覆蓋邊框的問(wèn)題 // - 支持邊緣和邊角拖動(dòng)改變窗體大小 // - 啟用雙緩沖 public class FloatLayerBase : Form{ /// <summary> /// 鼠標(biāo)消息篩選器 /// </summary> // 由于本窗體為WS_CHILD,所以不會(huì)收到在窗體以外點(diǎn)擊鼠標(biāo)的消息 // 該消息篩選器的作用就是讓本窗體獲知鼠標(biāo)點(diǎn)擊情況,進(jìn)而根據(jù)鼠標(biāo)是否在本窗體以外的區(qū)域點(diǎn)擊,做出相應(yīng)處理 readonly AppMouseMessageHandler _mouseMsgFilter; /// <summary> /// 指示本窗體是否已ShowDialog過(guò) /// </summary> // 由于多次ShowDialog會(huì)使OnLoad/OnShown重入,故需設(shè)置此標(biāo)記以供重入時(shí)判斷 bool _isShowDialogAgain; // 邊框相關(guān)字段
BorderStyle _borderType;Border3DStyle _border3DStyle;ButtonBorderStyle _borderSingleStyle;Color _borderColor; int _borderWidth;// 邊框?qū)挾?#xff0c;用于繪制SizeGrip時(shí)計(jì)算邊角偏移 /// <summary> /// 獲取所繪制的邊框尺寸(邊框?qū)挾葂2) /// </summary> [Browsable(false )] public Size BorderSize{ get { return new Size(_borderWidth, _borderWidth); }} /// <summary> /// 指示窗體是否處于可調(diào)整大小狀態(tài) /// </summary> [Browsable(false )] public bool CanReSize{ get { return this .SizeGripStyle == System.Windows.Forms.SizeGripStyle.Show || (this .SizeGripStyle == System.Windows.Forms.SizeGripStyle.Auto && Modal);}} /// <summary> /// 獲取或設(shè)置邊框類型 /// </summary> [Description(" 獲取或設(shè)置邊框類型。 " )][DefaultValue(BorderStyle.Fixed3D)] public BorderStyle BorderType{ get { return _borderType; } set { if (_borderType == value) { return ; }_borderType = value; this .UpdateBorderWidth();Invalidate();}} /// <summary> /// 獲取或設(shè)置三維邊框樣式 /// </summary> [Description(" 獲取或設(shè)置三維邊框樣式。 " )][DefaultValue(Border3DStyle.RaisedInner)] public Border3DStyle Border3DStyle{ get { return _border3DStyle; } set { if (_border3DStyle == value) { return ; }_border3DStyle = value; this .UpdateBorderWidth();Invalidate();}} /// <summary> /// 獲取或設(shè)置線型邊框樣式 /// </summary> [Description(" 獲取或設(shè)置線型邊框樣式。 " )][DefaultValue(ButtonBorderStyle.Solid)] public ButtonBorderStyle BorderSingleStyle{ get { return _borderSingleStyle; } set { if (_borderSingleStyle == value) { return ; }_borderSingleStyle = value; this .UpdateBorderWidth();Invalidate();}} /// <summary> /// 獲取或設(shè)置邊框顏色(僅當(dāng)邊框類型為線型時(shí)有效) /// </summary> [Description(" 獲取或設(shè)置邊框顏色(僅當(dāng)邊框類型為線型時(shí)有效)。 " )][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窗體才不會(huì)搶父窗體焦點(diǎn) 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;}} // 構(gòu)造函數(shù) public FloatLayerBase(){ // 初始化消息篩選器。添加和移除在顯示/隱藏時(shí)負(fù)責(zé) _mouseMsgFilter = new AppMouseMessageHandler(this ); this .DoubleBuffered = true ; // 初始化基類屬性
InitBaseProperties(); // 初始化邊框相關(guān) _borderType = BorderStyle.Fixed3D;_border3DStyle = System.Windows.Forms.Border3DStyle.RaisedInner;_borderSingleStyle = ButtonBorderStyle.Solid;_borderColor = Color.DarkGray; this .UpdateBorderWidth();} protected override void OnLoad(EventArgs e){ // 防止重入 if (_isShowDialogAgain) { return ; } // 為首次ShowDialog設(shè)標(biāo)記 if (Modal) { _isShowDialogAgain = true ; } // 需得減掉兩層邊框?qū)挾?#xff0c;運(yùn)行時(shí)尺寸才與設(shè)計(jì)時(shí)完全相符,原因不明 // 確定與ControlBox、FormBorderStyle有關(guān),但具體聯(lián)系不明 if (!DesignMode){Size size = SystemInformation.FrameBorderSize; this .Size -= size + size;// 不可以用ClientSize,后者會(huì)根據(jù)窗口風(fēng)格重新調(diào)整Size
} base .OnLoad(e);} protected override void WndProc(ref Message m){ // 當(dāng)本窗體作為ShowDialog彈出時(shí),在收到WM_SHOWWINDOW前,Owner會(huì)被Disable // 故需在收到該消息后立即Enable它,不然Owner窗體和本窗體都將處于無(wú)響應(yīng)狀態(tài) if (m.Msg == 0x18 && m.WParam != IntPtr.Zero && m.LParam == IntPtr.Zero && Modal && Owner != null && !Owner.IsDisposed){ if (Owner.IsMdiChild){ // 當(dāng)Owner是MDI子窗體時(shí),被Disable的是MDI主窗體 // 并且Parent也會(huì)指向MDI主窗體,故需改回為Owner,這樣彈出窗體的Location才會(huì)相對(duì)于Owner而非MDIParent NativeMethods.EnableWindow(Owner.MdiParent.Handle, true );NativeMethods.SetParent( this .Handle, Owner.Handle);// 只能用API設(shè)置Parent,因?yàn)槟J酱绑w是TopLevel,.Net拒絕為頂級(jí)窗體設(shè)置Parent
} else {NativeMethods.EnableWindow(Owner.Handle, true );}} else if (m.Msg == 0x84 && this .CanReSize)// WM_NCHITTEST。實(shí)現(xiàn)邊緣和邊角拖動(dòng)改變窗體大小
{Point pt = this .PointToClient(NativeMethods.MakePoint(m.LParam));Size size = this .ClientSize; if (new Rectangle(0 , 0 , 5 , 5 ).Contains(pt)){m.Result = (IntPtr)13 ;// HTTOPLEFT return ;} if (new Rectangle(5 , 0 , size.Width - 10 , 3 ).Contains(pt)){m.Result = (IntPtr)12 ;// HTTOP return ;} if (new Rectangle(size.Width - 5 , 0 , 5 , 5 ).Contains(pt)){m.Result = (IntPtr)14 ;// HTTOPRIGHT return ;} if (new Rectangle(size.Width - 3 , 5 , 3 , size.Height - 5 - 16 ).Contains(pt)){m.Result = (IntPtr)11 ;// HTRIGHT return ;} if (new Rectangle(5 , size.Height - 3 , size.Width - 5 - 16 , 3 ).Contains(pt)){m.Result = (IntPtr)15 ;// HTBOTTOM return ;} if (new Rectangle(0 , size.Height - 5 , 5 , 5 ).Contains(pt)){m.Result = (IntPtr)16 ;// HTBOTTOMLEFT return ;} if (new Rectangle(0 , 5 , 3 , size.Height - 10 ).Contains(pt)){m.Result = (IntPtr)10 ;// HTLEFT return ;}} 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);}} protected override void OnPaint(PaintEventArgs e){ if (this .CanReSize){Size clientSize = this .ClientSize;Rectangle rect = new Rectangle(clientSize.Width - 16 , clientSize.Height - 16 , 16 , 16 ); // 畫手柄 DrawSizeGrip(e.Graphics, new Rectangle(rect.Location - BorderSize - new Size(1 , 1 ), rect.Size)); // 刨掉SizeGrip區(qū)域,防止基類再畫
e.Graphics.SetClip(rect, System.Drawing.Drawing2D.CombineMode.Exclude);} base .OnPaint(e);e.Graphics.ResetClip();} /// <summary> /// 繪制SizeGrip(調(diào)整大小的手柄),子類可重寫 /// </summary> /// <param name="g"> 繪制器 </param> /// <param name="rect"> 建議作圖區(qū)域 </param> protected virtual void DrawSizeGrip(Graphics g, Rectangle rect){Color backColor = this .BackColor;Brush color1 = new SolidBrush(ControlPaint.Dark(backColor));Brush color2 = new SolidBrush(ControlPaint.Dark(backColor, -0.5F ));Brush color3 = new SolidBrush(ControlPaint.Dark(backColor, -0.1F ));Brush color4 = new SolidBrush(ControlPaint.Light(backColor));Point pt = new Point(rect.X + 5 , rect.Y + 5 );// 左上角偏移 for (int i = 0 ; i < 4 ; i++){ for (int j = 0 ; j < 4 ; j++){ if (j >= 3 - i){g.FillRectangle(color1, new Rectangle(pt.X + j * 3 , pt.Y + i * 3 , 1 , 1 ));g.FillRectangle(color2, new Rectangle(pt.X + j * 3 + 1 , pt.Y + i * 3 , 1 , 1 ));g.FillRectangle(color3, new Rectangle(pt.X + j * 3 , pt.Y + i * 3 + 1 , 1 , 1 ));g.FillRectangle(color4, new Rectangle(pt.X + j * 3 + 1 , pt.Y + i * 3 + 1 , 1 , 1 ));}}}} protected override void OnVisibleChanged(EventArgs e){ if (!DesignMode){ if (Visible){ // 使焦點(diǎn)子控件擁有聚焦框,重寫ShowFocusCues較麻煩 NativeMethods.SendMessage(this .Handle, 0x127 /* WM_CHANGEUISTATE */ , (IntPtr)0x10002 /* UISF_HIDEFOCUS | UIS_CLEAR */ , IntPtr.Zero);NativeMethods.SendMessage( this .Handle, 0x128 /* WM_UPDATEUISTATE */ , (IntPtr)0x10002 /* UISF_HIDEFOCUS | UIS_CLEAR */ , IntPtr.Zero); // 顯示后添加鼠標(biāo)消息篩選器以開始捕捉
Application.AddMessageFilter(_mouseMsgFilter);} else { // 隱藏時(shí)則移除篩選器。之所以不放Dispose中是想盡早移除篩選器
Application.RemoveMessageFilter(_mouseMsgFilter);}} base .OnVisibleChanged(e);} // 實(shí)現(xiàn)窗體客戶區(qū)拖動(dòng) // 在WndProc中實(shí)現(xiàn)這個(gè)較麻煩,所以放到這里做 protected override void OnMouseDown(MouseEventArgs e){ // 讓鼠標(biāo)點(diǎn)擊客戶區(qū)時(shí)達(dá)到與點(diǎn)擊標(biāo)題欄一樣的效果,以此實(shí)現(xiàn)客戶區(qū)拖動(dòng)
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"> 觸發(fā)彈出窗體的控件 </param> /// <param name="offsetX"> 相對(duì)control水平偏移 </param> /// <param name="offsetY"> 相對(duì)control垂直偏移 </param> public DialogResult ShowDialog(Control control, int offsetX, int offsetY){ return ShowDialog(control, new Point(offsetX, offsetY));} /// <summary> /// 顯示為模式窗體 /// </summary> /// <param name="control"> 觸發(fā)彈出窗體的控件 </param> /// <param name="offset"> 相對(duì)control偏移 </param> public DialogResult ShowDialog(Control control, Point offset){ return this .ShowDialogInternal(control, offset);} /// <summary> /// 顯示為模式窗體 /// </summary> /// <param name="item"> 顯示在該工具欄項(xiàng)的下方 </param> public DialogResult ShowDialog(ToolStripItem item){ return ShowDialog(item, 0 , item.Height);} /// <summary> /// 顯示為模式窗體 /// </summary> /// <param name="item"> 觸發(fā)彈出窗體的工具欄項(xiàng) </param> /// <param name="offsetX"> 相對(duì)item水平偏移 </param> /// <param name="offsetY"> 相對(duì)item垂直偏移 </param> public DialogResult ShowDialog(ToolStripItem item, int offsetX, int offsetY){ return ShowDialog(item, new Point(offsetX, offsetY));} /// <summary> /// 顯示為模式窗體 /// </summary> /// <param name="item"> 觸發(fā)彈出窗體的工具欄項(xiàng) </param> /// <param name="offset"> 相對(duì)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"> 觸發(fā)彈出窗體的控件 </param> /// <param name="offsetX"> 相對(duì)control水平偏移 </param> /// <param name="offsetY"> 相對(duì)control垂直偏移 </param> public void Show(Control control, int offsetX, int offsetY){Show(control, new Point(offsetX, offsetY));} /// <summary> /// 顯示窗體 /// </summary> /// <param name="control"> 觸發(fā)彈出窗體的控件 </param> /// <param name="offset"> 相對(duì)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"> 觸發(fā)彈出窗體的工具欄項(xiàng) </param> /// <param name="offsetX"> 相對(duì)item水平偏移 </param> /// <param name="offsetY"> 相對(duì)item垂直偏移 </param> public void Show(ToolStripItem item, int offsetX, int offsetY){Show(item, new Point(offsetX, offsetY));} /// <summary> /// 顯示窗體 /// </summary> /// <param name="item"> 觸發(fā)彈出窗體的工具欄項(xiàng) </param> /// <param name="offset"> 相對(duì)item偏移 </param> public void Show(ToolStripItem item, Point offset){ this .ShowInternal(item, offset);} /// <summary> /// ShowDialog內(nèi)部方法 /// </summary> private DialogResult ShowDialogInternal(Component controlOrItem, Point offset){ // 快速連續(xù)彈出本窗體將有可能遇到尚未Hide的情況下再次彈出,這會(huì)引發(fā)異常,故需做處理 if (this .Visible) { return System.Windows.Forms.DialogResult.None; } this .SetLocationAndOwner(controlOrItem, offset); return base .ShowDialog();} /// <summary> /// Show內(nèi)部方法 /// </summary> private void ShowInternal(Component controlOrItem, Point offset){ if (this .Visible) { return ; }// 原因見ShowDialogInternal this .SetLocationAndOwner(controlOrItem, offset); base .Show();} /// <summary> /// 設(shè)置坐標(biāo)及所有者 /// </summary> /// <param name="controlOrItem"> 控件或工具欄項(xiàng) </param> /// <param name="offset"> 相對(duì)偏移 </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; // 設(shè)置Owner屬性與Show[Dialog](Owner)有不同,當(dāng)Owner是MDIChild時(shí),后者會(huì)改Owner為MDIParent this .Owner = c.FindForm();} /// <summary> /// 獲取控件在窗體中的坐標(biāo) /// </summary> private static Point GetControlLocationInForm(Control c){Point pt = c.Location; while (!((c = c.Parent) is Form)){pt.Offset(c.Location);} return pt;} /// <summary> /// 更新邊框?qū)挾?/span>/// </summary> private void UpdateBorderWidth(){ if (_borderType == BorderStyle.None){_borderWidth = 0 ;} else if (_borderType == BorderStyle.Fixed3D){ if (_border3DStyle == System.Windows.Forms.Border3DStyle.Adjust) { _borderWidth = 0 ; } else if (_border3DStyle == System.Windows.Forms.Border3DStyle.Flat) { _borderWidth = 1 ; } else { _borderWidth = CountOneInBits((uint )_border3DStyle); }} else { if (_borderSingleStyle == ButtonBorderStyle.None) { _borderWidth = 0 ; } else if (_borderSingleStyle == ButtonBorderStyle.Outset) { _borderWidth = 2 ; } else { _borderWidth = 1 ; }}} /// <summary> /// 統(tǒng)計(jì)二進(jìn)制中1的個(gè)數(shù) /// </summary> private static int CountOneInBits(uint num){ int count = 0 ; while (num != 0 ){num &= num - 1 ;count ++;} return count;} #region 屏蔽對(duì)本類影響重大的基類方法和屬性/// <summary> /// 初始化部分基類屬性 /// </summary> private void InitBaseProperties(){ base .ControlBox = false ; // 重要 // 必須得是SizableToolWindow才能支持調(diào)整大小的同時(shí),不受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 = true ; // 使本窗體像普通窗體一樣顯示后自動(dòng)激活首控件 base .WindowState = FormWindowState.Normal;} // 屏蔽原方法 [Browsable(false ), EditorBrowsable(EditorBrowsableState.Never)][Obsolete( " 請(qǐng)使用別的重載! " , true )] public new DialogResult ShowDialog() { throw new NotImplementedException(); }[Browsable( false ), EditorBrowsable(EditorBrowsableState.Never)][Obsolete( " 請(qǐng)使用別的重載! " , true )] public new DialogResult ShowDialog(IWin32Window owner) { throw new NotImplementedException(); }[Browsable( false ), EditorBrowsable(EditorBrowsableState.Never)][Obsolete( " 請(qǐng)使用別的重載! " , true )] public new void Show() { throw new NotImplementedException(); }[Browsable( false ), EditorBrowsable(EditorBrowsableState.Never)][Obsolete( " 請(qǐng)使用別的重載! " , 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( " 設(shè)置邊框請(qǐng)使用Border相關(guān)屬性! " , 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 true ; } set { } }[Browsable( false ), EditorBrowsable(EditorBrowsableState.Never)][Obsolete( " 禁用該屬性! " , true )] public new FormWindowState WindowState { get { return FormWindowState.Normal; } set { } } #endregion /// <summary> /// 程序鼠標(biāo)消息篩選器 /// </summary> private class AppMouseMessageHandler : IMessageFilter{ readonly FloatLayerBase _layerForm; public AppMouseMessageHandler(FloatLayerBase layerForm){_layerForm = layerForm;} public bool PreFilterMessage(ref Message m){ // 如果在本窗體以外點(diǎn)擊鼠標(biāo),隱藏本窗體 // 若想在點(diǎn)擊標(biāo)題欄、滾動(dòng)條等非客戶區(qū)也要讓本窗體消失,取消0xA1的注釋即可 // 本例是根據(jù)坐標(biāo)判斷,亦可以改為根據(jù)句柄,但要考慮子孫控件 // 之所以用API而不用Form.DesktopBounds是因?yàn)楹笳卟豢煽?/span>if ((m.Msg == 0x201 /* || m.Msg==0xA1 */ ) && _layerForm.Visible && !NativeMethods.GetWindowRect(_layerForm.Handle).Contains(MousePosition)){_layerForm.Hide(); // 之所以不Close是考慮應(yīng)該由調(diào)用者負(fù)責(zé)銷毀
} 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;} public static int LOWORD(IntPtr n){ return ((int )n) & 0xFFFF ;} public static int HIWORD(IntPtr n){ return (((int )n) >> 16 ) & 0xFFFF ;} public static Point MakePoint(IntPtr n){ return new Point(LOWORD(n), HIWORD(n));}}}
} FloatLayerBase.cs 新建繼承窗體,選擇繼承自FloatLayerBase類;也可以新建普通窗體,然后把基類由Form改為FloatLayerBase 在設(shè)計(jì)器和源碼中打造浮動(dòng)應(yīng)用 在需要的地方使用它。關(guān)于使用,先看一下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個(gè)屬性都是跟邊框有關(guān)的,邊框總共有3種形態(tài),三維、線型、無(wú),由BorderType指定;當(dāng)為三維形態(tài)時(shí),由Border3DStyle指定具體樣式;為線型時(shí),由BorderSingleStyle和BorderColor分別指定具體線型和顏色。原Form.FormBorderStyle屬性已被屏蔽,不允許子類訪問(wèn),還有若干原Form的屬性也已屏蔽,原因都在源碼里。另外,原Form.SizeGripStyle照常使用,是否允許調(diào)整浮動(dòng)層大小就靠它了
方法就說(shuō)一下Show和ShowDialog,顯然分別是用來(lái)非模式化/模式化顯示浮動(dòng)層的,兩者在調(diào)用角度的重大區(qū)別就是,前者不會(huì)阻塞代碼,后者則會(huì),實(shí)際應(yīng)用中根據(jù)情況選用。每個(gè)方法從參數(shù)又分Control和ToolStripItem兩類,都是代表從什么控件上彈出浮動(dòng)層的意思,前者接受Button、TextBox等控件(不能傳入Form,后果會(huì)不愉快),后者接受工具欄上面的項(xiàng)目,例如ToolStripButton、ToolStripTextBox之類的。重載可以指定相對(duì)control或item的偏移位置,默認(rèn)是在control/item的下方彈出浮動(dòng)層。最后無(wú)論是Show還是ShowDialog彈出來(lái)的浮動(dòng)層,都可以像右鍵菜單那樣通過(guò)在其它地方點(diǎn)鼠標(biāo)使之消失,這里需要說(shuō)明一下:
鼠標(biāo)只會(huì)點(diǎn)在本程序內(nèi)的窗體中時(shí),讓浮動(dòng)層消失。點(diǎn)在程序外的窗口、桌面、任務(wù)欄這些則不會(huì)。為什么要這樣是因?yàn)橐龅酵耆裼益I菜單那樣對(duì)全局鼠標(biāo)敏感,需要全局鉤子,這會(huì)增加代碼量(性能且不說(shuō),沒測(cè)過(guò)不妄言),而且我認(rèn)為沒必要全局敏感 浮動(dòng)層消失是調(diào)用Hide方法,所以對(duì)于模式化打開的浮動(dòng)層,會(huì)返回DialogResult.Cancel,這是.net對(duì)模式對(duì)話框的設(shè)計(jì)使然,模式對(duì)話框被Hide或Close時(shí),就是返回Cancel。在此也提醒一下調(diào)用者,在使用模式對(duì)話框時(shí),永遠(yuǎn)考慮有返回Cancel這種情況,不限于本例,而是所有對(duì)話框 原Show()/Show(IWin32Window)和ShowDialog()/ShowDialog(IWin32Window)已被屏蔽,原因見源碼。
其它: 編寫期間一直使用PopupFormBase作為類名,發(fā)布最后時(shí)刻才改為現(xiàn)在的FloatLayerBase,所以demo中可能尚有依據(jù)原名起名的子類、方法名等。
Demo下載: http://pan.baidu.com/s/1mgnGPGc
里面有個(gè)Tester供您體驗(yàn)。
?
-文畢-
posted on
2015-08-24 08:46 ahdung 閱讀(
... ) 評(píng)論() 編輯 收藏
轉(zhuǎn)載于:https://www.cnblogs.com/ahdung/p/FloatLayerBase.html
總結(jié)
以上是生活随笔 為你收集整理的【C#】分享一个弹出容器层,像右键菜单那样召即来挥则去 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。