首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WinForms 介面開發 (四):從零重構視窗非客戶區繪製 — 標題列、按鈕、邊框完整實作

     編輯:關於.NET

系列第四篇:在移除視窗預設邊框 (FormBorderStyle=None) 與設定 Region 後,本文詳細示範如何使用 GDI+ 完整重繪非客戶區,包含標題列背景、視窗圖示、標題文字、控制盒按鈕 (最小化/最大化/關閉) 以及視窗邊框。

非客戶區架構拆解

WinForms 視窗可視區域分為:

  • Client Area:客戶區,單純設定 BackColor 即可。
  • Non-Client Area:非客戶區,包含標題列 (Caption)、系統選單圖示、標題文字、控制盒按鈕、邊框。完全由開發者負責繪製 (處理 WM_NCPAINT、WM_NCCALCSIZE、WM_NCHITTEST 等訊息)。

核心繪製流程:NcPaint 方法深度解析

private bool NcPaint(SkinningForm form, SkinEngine engine)
{
    // 1. 取得螢幕座標視窗矩形
    RECT rectScreen = new RECT();
    NativeMethod.GetWindowRect(_parentForm.Handle, ref rectScreen);
    Rectangle rectBounds = rectScreen.ToRectangle();
    rectBounds.Offset(-rectBounds.X, -rectBounds.Y); // 轉為客戶區原點座標

    // 2. 建立裁剪區域:排除客戶區,僅保留非客戶區
    Region region = new Region(rectBounds);
    Rectangle rectClip = rectBounds;
    rectClip.Inflate(-engine.SkinAppearance.BorderWidth, -engine.SkinAppearance.BorderWidth);
    rectClip.Y += engine.SkinAppearance.CaptionHeight;
    rectClip.Height -= engine.SkinAppearance.CaptionHeight;
    region.Exclude(rectClip); // 關鍵:只繪製邊框與標題列

    // 3. 取得裝置內容 (DC) 並套用裁剪區域
    IntPtr hdc = NativeMethod.GetDCEx(_parentForm.Handle, IntPtr.Zero,
        DCXFlags.DCX_CACHE | DCXFlags.DCX_CLIPSIBLINGS | DCXFlags.DCX_WINDOW);
    Graphics g = Graphics.FromHdc(hdc);
    IntPtr hrgn = region.GetHrgn(g);
    NativeMethod.SelectClipRgn(hdc, hrgn);

    // 4. 離螢幕緩衝 (BufferedGraphics) 避免閃爍
    if (_bufferGraphics == null || _currentCacheSize != rectBounds.Size) { ... }
    g = _bufferGraphics.Graphics; // 切換至緩衝 Graphics

    // 5. 依序繪製非客戶區元素
    Rectangle captionBounds = rectBounds;
    captionBounds.Height = engine.SkinAppearance.BorderWidth + engine.SkinAppearance.CaptionHeight;

    engine.SkinAppearance.DrawCaptionBackground(g, captionBounds, _formIsActive, engine); // 標題列背景
    if (_parentForm.ShowIcon && _parentForm.Icon != null) DrawIcon(g);                     // 視窗圖示
    DrawCaptionText(g, _parentForm.Text, _parentForm.Font);                                // 標題文字
    DrawCaptionControlBox(g);                                                              // 控制盒按鈕 (核心難點)
    engine.SkinAppearance.DrawBorder(g, rectBounds, engine);                               // 視窗邊框

    // 6. 緩衝內容輸出至螢幕 + 資源清理
    _bufferGraphics.Render();
    // ... Dispose Region, ReleaseDC, SelectClipRgn(null)
}

關鍵技術細節

1. DCX_FLAGS 組合意義

  • DCX_CACHE:使用快取 DC,效能較佳。
  • DCX_CLIPSIBLINGS:裁剪同層視窗避免重疊繪製。
  • DCX_WINDOW:取得整個視窗 DC (含非客戶區),而非僅客戶區。

2. 裁剪區域計算邏輯

先建立完整視窗矩形 Region,再 Exclude 客戶區矩形。客戶區矩形由邊框寬度 (BorderWidth) 與標題列高度 (CaptionHeight) 推算。此技巧確保 Graphics 僅在非客戶區繪製,不汙染客戶區內容。

3. BufferedGraphics 雙緩衝

視窗大小變更時重新配置緩衝區 (_bufferContext.Allocate)。所有繪圖操作導向記憶體位圖,最後 Render() 一次性輸出,徹底消除逐步繪製造成的閃爍。

4. 控制盒按鈕繪製 (DrawCaptionControlBox)

按鈕區域陣列依標題列高度與右邊距計算。繪製時依按鈕狀態 (Normal/Hover/Pressed/Disabled) 選取不同筆刷 (漸層背景、邊框、圖示路徑)。滑鼠非客戶區訊息 (WM_NCHITTEST, WM_NCMOUSEMOVE 等) 驅動狀態機更新並觸發 Invalidate() 重繪。

執行成果

完整自繪非客戶區效果:標題列漸層、圖示、文字、三顆立體按鈕、圓角邊框

系列脈絡

  • (一) Hook 所有視窗訊息
  • (二) NativeWindow 封裝與訊息攔截
  • (三) 設定視窗 Region (圓角)
  • (四) 本文:非客戶區完整 GDI+ 繪製
  • (五) 閃爍優化與自訂按鈕細節
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved