VS足夠強大,強大到只需動動鼠標就可以寫出個基本的界面出來,但是其自帶的控件都是千篇一律的樣式,對於追求完美的我而言,實在是忍不下去了,只好自己親自動手對其進行改造----繼承已有的控件,再對其相關的消息或事件進行處理。窗體Form作為界面的主體部分,必先對其進行美化,在窗體自繪的過程中,需要使用到GDI+,如若對GDI+不是很了解的同學可移步我的CSDN博客或者搜索下相關的介紹。
這篇文章將要介紹到的內容:
實現效果演示:
代碼下載
一:窗體圓角的處理
對於無邊框窗體圓角矩形的處理,我這里采用的是使用API函數CreateRoundRectRgn,相比於自己用GDI+寫的處理圓角的函數,效果要稍微好點,至少線條在圓角處過渡的比較平滑,為了便於復用,我把其封裝到窗體自繪輔助類RenderHlper的SetFormRoundRectRgn函數中:

/// <summary> /// 設置窗體的圓角矩形 /// </summary> /// <param name="form">需要設置的窗體</param> /// <param name="rgnRadius">圓角矩形的半徑</param> public static void SetFormRoundRectRgn(Form form, int rgnRadius) { int hRgn = 0; hRgn = Win32.CreateRoundRectRgn(0, 0, form.Width + 1, form.Height + 1, rgnRadius, rgnRadius); Win32.SetWindowRgn(form.Handle, hRgn, true); Win32.DeleteObject(hRgn); }
此處需要把所需要的API函數引用到類Win32中,引用的時候注意添加 System.Runtime.InteropServices 命名空間:

[DllImport("gdi32.dll")] public static extern int CreateRoundRectRgn(int x1, int y1, int x2, int y2, int x3, int y3); [DllImport("user32.dll")] public static extern int SetWindowRgn(IntPtr hwnd, int hRgn, Boolean bRedraw); [DllImport("gdi32.dll", EntryPoint = "DeleteObject", CharSet = CharSet.Ansi)] public static extern int DeleteObject(int hObject);
重寫窗體的OnSizeChanged事件,並在此事件中調用SetFormRoundRectRgn,此處的Radius參數為定義的窗體圓角半徑屬性:

protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); RenderHelper.SetFormRoundRectRgn(this, Radius); }
二:無邊框窗體大小的改變與移動
當把窗體的FormBorderStyle屬性調整為FormBorderStyle.None時,此時,窗體的大小改變不了,同時窗體不能移動。要想實現無邊框窗體大小的改變與移動,可采用如下方法:
(1)重寫窗體的過程WndProc:
主要是對WM_NCHITTEST消息進行處理,根據事件的發生位置來進行不同方向箭頭的調整,窗體大小改變與移動的函數:

//調整窗體大小 private void WmNcHitTest(ref Message m) { int wparam = m.LParam.ToInt32(); Point mouseLocation = new Point(RenderHelper.LOWORD(wparam),RenderHelper.HIWORD(wparam)); mouseLocation = PointToClient(mouseLocation); if (WindowState != FormWindowState.Maximized ) { if (CanResize == true) { if (mouseLocation.X < 5 && mouseLocation.Y < 5) { m.Result = new IntPtr(Win32.HTTOPLEFT); return; } if (mouseLocation.X > Width - 5 && mouseLocation.Y < 5) { m.Result = new IntPtr(Win32.HTTOPRIGHT); return; } if (mouseLocation.X < 5 && mouseLocation.Y > Height - 5) { m.Result = new IntPtr(Win32.HTBOTTOMLEFT); return; } if (mouseLocation.X > Width - 5 && mouseLocation.Y > Height - 5) { m.Result = new IntPtr(Win32.HTBOTTOMRIGHT); return; } if (mouseLocation.Y < 3) { m.Result = new IntPtr(Win32.HTTOP); return; } if (mouseLocation.Y > Height - 3) { m.Result = new IntPtr(Win32.HTBOTTOM); return; } if (mouseLocation.X < 3) { m.Result = new IntPtr(Win32.HTLEFT); return; } if (mouseLocation.X > Width - 3) { m.Result = new IntPtr(Win32.HTRIGHT); return; } } } m.Result = new IntPtr(Win32.HTCAPTION); }
重寫窗體過程:

protected override void WndProc(ref Message m) { switch (m.Msg) { case Win32.WM_NCHITTEST: WmNcHitTest(ref m); break; default: base.WndProc(ref m); break; } }
(2)對於僅僅只想實現窗體的移動而不改變窗體的大小,可以重寫OnMouseDown事件中發送HTCAPTION消息來實現無邊框窗體的移動,具體的實現代碼如下:

/// <summary> /// 移動窗體 /// </summary> public static void MoveWindow(Form form) { Win32.ReleaseCapture(); Win32.SendMessage(form.Handle, Win32.WM_NCLBUTTONDOWN, Win32.HTCAPTION, 0); }
調用窗體移動函數:

protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (e.Button == MouseButtons.Left) { Render.MoveWindow(this); } }
三:窗體邊框的繪制與邊框陰影的實現
邊框的繪制:邊框的繪制使用用PS制作好的圖片來進行貼圖操作,在貼圖的過程中使用九宮圖貼圖方法,保證此邊框圖片能滿足任何大小的窗體。
窗體邊框的實現:此部分主要涉及到對CS_DropSHADOW的了解,只要在窗口的ClassStyle添加此樣式即可,關鍵代碼如下:

protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; if (!DesignMode) { cp.ClassStyle |= (int) ClassStyle.CS_DropSHADOW; } return cp; } }
此部分是所有部分中最難的部分,在此部分中既要實現系統按鈕不同狀態下(鼠標操作改變按鈕狀態)的繪制,還有對其相應的事件進行處理,所以我創建了2個類:SystemButton類和SystemButtonManager類。SystemButton類表示系統按鈕類,而SystemButtonManager的功能是對系統按鈕各個狀態與事件的管理。類SystemButtonManager的類圖如下所示:
屬性、方法、事件的功能介紹如下表:
對於類SystemButtonManager,主要是管理三個系統按鈕的狀態與事件,其他特別要介紹的是定義的系統按鈕狀態索引器,根據提供的索引來獲取或者設置按鈕的當前狀態。
標題欄的繪制主要涉及到窗體Icon圖標的繪制與窗體標題的繪制,繪制的過程中定義了2個屬性:IconRect,TextRect,分別對應着圖標的坐標矩形與窗體標題的坐標矩形,圖標與標題的繪制在這個矩形中繪制,需要提醒的時,圖標的繪制需要注意到是否窗體的ShowIcon屬性。
在窗體的自繪過程中,當調整窗體的大小等操作而觸發窗體重繪,此時窗體的閃爍現象更為明顯,相信大部分同學在自定義控件的過程中或多或多的出現這種問題,對於此問題,每個人又不同的解決方法,這里我提供四種解決方案類解決窗體的閃爍:
方法一:第一個容易想到的是采用雙緩沖機制來進行圖形的繪制,對雙緩沖不了解的同學可以參考下我的另外一篇文章《淺談C#中是雙緩沖》。
方法二:當將CS_DropSHADOW樣式添加到窗體的ClassStyle樣式中可以明顯的解決窗體閃爍的現象。代碼見本文的第三部分--窗體邊框的繪制與邊框陰影的實現。
方法三:當窗體進行重繪時,對WM_ERASEBKGND消息進行忽略,應用代碼如下:

protected override void WndProc(ref Message m) { switch (m.Msg) { case Win32.WM_ERASEBKGND: m.Result = IntPtr.Zero; break; default: base.WndProc(ref m); break; } }
方法四:將WS_CLIPCHILDREN樣式添加到窗體的ExStyle樣式中,此方法對解決窗體掛有很多子控件時窗體閃爍的現象特別明顯,應用代碼如下:

protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; if (!DesignMode) { cp.ExStyle |= (int)WindowStyle.WS_CLIPCHILDREN; } return cp; } }
注:本博客所有文章均為作者個人原創 ,如若轉載,請標記文章出處:http://www.cnblogs.com/Keep-Silence-/ 苦笑。