在掌握 Windows 圖形子系統基本原理後,本講將引導大家完成第一個實戰專案——橢圓形按鈕控件。這個看似簡單的專案其實涵蓋了自訂控件開發的完整流程:繪製邏輯、滑鼠互動狀態機、事件觸發、座標點擊測試等核心技術,是進入 C# 圖形開發大門的最佳敲門磚。
Click 事件UserControl,封裝為可重用元件OnPaint 負責所有繪製OnMouseMove、OnMouseEnter、OnMouseLeave 處理懸停狀態OnClick 實作橢圓區域點擊測試OnPaint 深度剖析protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
// 1. 建立橢圓路徑(GraphicsPath 可組合多條曲線/直線)
using (GraphicsPath path = new GraphicsPath()) {
// 外接矩形即控件客戶區
path.AddEllipse(0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1);
// 2. 填充背景(先填再畫框,避免邊框被覆蓋)
using (SolidBrush brush = new SolidBrush(
bolMouseHoverFlag ? this.HoverBackColor : this.ButtonBackColor)) {
e.Graphics.FillPath(brush, path);
}
// 3. 繪製邊框
using (Pen pen = new Pen(
bolMouseHoverFlag ? this.HoverBorderColor : this.BorderColor, 2)) {
e.Graphics.DrawPath(pen, path);
}
}
// 4. 繪製置中文字
if (this.Caption != null) {
using (StringFormat fmt = new StringFormat()) {
fmt.Alignment = StringAlignment.Center; // 水平置中
fmt.LineAlignment = StringAlignment.Center; // 垂直置中
fmt.FormatFlags = StringFormatFlags.NoWrap; // 單行不換行
using (SolidBrush brush = new SolidBrush(this.ForeColor)) {
e.Graphics.DrawString(this.Caption, this.Font, brush,
new RectangleF(0, 0, this.ClientSize.Width, this.ClientSize.Height), fmt);
}
}
}
}
關鍵知識點:
GraphicsPath:矢量圖形路徑容器,支援 AddEllipse、AddRectangle、AddCurve 等,可重複使用於填充與描邊ClientSize vs Size:前者為可繪製客戶區(扣除邊框),後者為控件整體大小;繪圖務必使用 ClientSizeusing 區塊:Pen、Brush、GraphicsPath 實作 IDisposable,包含非託管 GDI+ 資源,必須及時釋放StringFormat 控制文字對齊、換行、截斷等排版行為private bool CheckMouseHover(int x, int y) {
using (GraphicsPath path = new GraphicsPath()) {
path.AddEllipse(0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1);
bool hit = path.IsVisible(x, y); // 核心:路徑幾何包含性測試
if (hit != bolMouseHoverFlag) {
bolMouseHoverFlag = hit;
this.Invalidate(); // 標記需重繪(非同步,效能較好)
// this.Refresh(); // 強制立即重繪(同步,特殊情況才用)
}
return hit;
}
}
GraphicsPath.IsVisible 直接利用 GDI+ 幾何引擎判斷點是否在路徑內,避免手寫橢圓方程式。Invalidate() 與 Refresh() 差異在於前者發送 WM_PAINT 訊息由訊息迴圈稍後處理(非同步),後者直接呼叫 UpdateWindow 同步重繪;一般 UI 互動用 Invalidate 即可。
protected override void OnMouseMove(MouseEventArgs e) {
base.OnMouseMove(e);
CheckMouseHover(e.X, e.Y); // 即時檢測
}
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave(e);
if (bolMouseHoverFlag) { bolMouseHoverFlag = false; this.Invalidate(); }
}
protected override void OnClick(EventArgs e) {
// base.OnClick(e); // 暫不呼叫基類
Point screenPt = Control.MousePosition; // 螢幕絕對座標
Point clientPt = this.PointToClient(screenPt); // 轉為控件客戶區座標
if (CheckMouseHover(clientPt.X, clientPt.Y)) {
base.OnClick(e); // 僅在橢圓內才觸發 Click 事件
}
}
因 OnClick 事件參數不含座標,需自行透過 Control.MousePosition + PointToClient 取得點擊位置,再複用 CheckMouseHover 判斷。
EllipseButtonCaption、顏色屬性Click 加入測試碼(如 MessageBox.Show)文章特別提到:人眼對畫面變化極其敏感(可感知數十毫秒延遲),因此圖形程式碼執行速度要求極高。.NET 圖形類庫雖封裝了 GDI+,但底層仍大量呼叫 Win32 API(InvalidateRect、UpdateWindow 等),深入掌握圖形開發需對 Windows 訊息機制與 GDI+ 管線有基本認知。
本講完成了首個具備動態視覺回饋的圖形控件,涵蓋:
GraphicsPath 建構複雜幾何形狀FillPath/DrawPath 矢量繪製流程IsVisible)Invalidate/Refresh 差異課後挑戰:嘗試在本專案基礎上實作「三角形按鈕」,提示:GraphicsPath.AddPolygon 配合三個頂點即可。下一講我們將挑戰資料網格控件,引入文檔-視圖架構、捲動虛擬化與多重選取邏輯。