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

為 TreeView 控制項新增背景圖片:自訂繪圖完整實作

     編輯:關於C語言

問題背景:TreeView 不觸發 Paint 事件

.NET Framework 的 TreeView 與 ListView 只是 Win32 通用控制項的簡易封裝,預設不會引發 Paint 事件。微軟官方建議的解法是設定 ControlStyles.UserPaint 並自行處理所有繪圖工作,但這會導致需重寫整個控制項的繪製邏輯。

解決方案:雙重緩衝與 WM_PRINTCLIENT

採用內部 Bitmap + Graphics 雙重緩衝策略,攔截 WM_PAINT 訊息,透過 WM_PRINTCLIENT 讓原生控制項繪製到記憶體位圖,再合成背景圖片,最後一次性輸出到螢幕。

1. 內部繪圖物件重建(OnResize)

protected override void OnResize(EventArgs e)
{
    base.OnResize(e);
    if (internalBitmap == null || internalBitmap.Width != Width || internalBitmap.Height != Height)
    {
        if (Width > 0 && Height > 0)
        {
            DisposeInternal();
            internalBitmap = new Bitmap(Width, Height);
            internalGraphics = Graphics.FromImage(internalBitmap);
        }
    }
}

2. 覆寫 WndProc 處理 WM_PAINT

protected override void WndProc(ref Message m)
{
    const int WM_PAINT = 0x000F;
    const int WM_PRINTCLIENT = 0x0318;
    const int WM_ERASEBKGND = 0x0014;

    if (m.Msg == WM_ERASEBKGND)
    {
        // 過濾背景擦除,減少閃爍
        return;
    }

    if (m.Msg == WM_PAINT)
    {
        // 步驟 1:讓原生控制項畫到內部 Graphics
        IntPtr hdc = internalGraphics.GetHdc();
        Message printMsg = Message.Create(Handle, WM_PRINTCLIENT, hdc, IntPtr.Zero);
        DefWndProc(ref printMsg);
        internalGraphics.ReleaseHdc(hdc);

        // 步驟 2:建立 PaintEventArgs 觸發使用者 OnPaint
        RECT updateRect;
        GetUpdateRect(Handle, out updateRect, false);
        OnPaint(new PaintEventArgs(internalGraphics, Rectangle.FromLTRB(updateRect.Left, updateRect.Top, updateRect.Right, updateRect.Bottom)));

        // 步驟 3:合成背景圖片(關鍵新增邏輯)
        if (backgroundImage != null)
        {
            // 建立臨時位圖保存原始繪製內容
            using (Bitmap temp = new Bitmap(internalBitmap, internalBitmap.Size))
            {
                temp.MakeTransparent(Color.White); // 白色設為透明
                // 重畫白色背景
                internalGraphics.FillRectangle(Brushes.White, 0, 0, Width, Height);
                // 繪製背景圖
                internalGraphics.DrawImage(backgroundImage, 0, 0, backgroundImage.Width, backgroundImage.Height);
                // 將原始內容(透過白色透明)合成回去
                internalGraphics.DrawImage(temp, 0, 0, temp.Width, temp.Height);
            }
        }

        // 步驟 4:輸出到螢幕
        using (Graphics screenGraphics = CreateGraphics())
        {
            screenGraphics.DrawImage(internalBitmap, 0, 0);
        }
        return;
    }

    base.WndProc(ref m);
}

3. 避免無限 WM_PAINT 迴圈的關鍵

使用 GetDC/ReleaseDC 取代 BeginPaint/EndPaint 可繞過更新區域限制,繪製完整客戶區。但必須手動呼叫 GetUpdateRect 與 ValidateRect 將更新區域標記為已驗證,否則會持續收到 WM_PAINT 導致 CPU 100%。

[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hWnd);

[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

[DllImport("user32.dll")]
private static extern bool GetUpdateRect(IntPtr hWnd, out RECT lpRect, bool bErase);

[DllImport("user32.dll")]
private static extern bool ValidateRect(IntPtr hWnd, ref RECT lpRect);

測試結果展示

TreeView 背景圖測試程式畫面

圖一:測試程式執行畫面,TreeView 節點選取時顯示黃色邊框,背景圖片完整呈現無重疊變形。

總結

  • 核心技巧:WM_PRINTCLIENT 捕獲原生繪製 + 雙重緩衝合成
  • 背景合成:臨時位圖 + MakeTransparent 實現透明疊加
  • 效能關鍵:GetDC/ReleaseDC + 手動 ValidateRect 防止死迴圈
  • 完整程式碼包含 Win32 API 封裝類別,可直接整合至專案
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved