當容器 (Form/Panel/TabControl) 狀態改變 (Normal ↔ Maximized、Dock 改變、Visible 切換) 時,Windows 會重繪背景與所有子控制項。若子控制項眾多且未暫停重繪,會產生「背景與前景交替閃爍」的視覺瑕疵。
參考 ListView.BeginUpdate/EndUpdate 內部實作,透過發送 WM_SETREDRAW (0x0B) 訊息告知視窗管理器暫停/恢復重繪。
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public static class ControlRedrawHelper
{
private const int WM_SETREDRAW = 0x0B;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
///
/// 暫停控制項重繪
///
public static void SuspendDrawing(Control control)
{
if (control.IsHandleCreated)
SendMessage(control.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
}
///
/// 恢復控制項重繪並立即失效重繪
///
public static void ResumeDrawing(Control control)
{
if (control.IsHandleCreated)
{
SendMessage(control.Handle, WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
control.Invalidate(true); // 遞迴失效所有子控制項
}
}
///
/// 執行動作期間暫停重繪 (自動恢復)
///
public static void SuspendDrawingScope(Control control, Action action)
{
try
{
SuspendDrawing(control);
action();
}
finally
{
ResumeDrawing(control);
}
}
}
private void LoadUserControls()
{
// 方式一:手動控制
ControlRedrawHelper.SuspendDrawing(panelContainer);
try
{
panelContainer.Controls.Clear();
for (int i = 0; i < 100; i++)
{
var uc = new MyUserControl { Dock = DockStyle.Top };
panelContainer.Controls.Add(uc);
}
}
finally
{
ControlRedrawHelper.ResumeDrawing(panelContainer);
}
// 方式二:使用 Scope 簡潔寫法 (C# 8.0+ using 宣告)
ControlRedrawHelper.SuspendDrawingScope(tabPageContent, () =>
{
tabPageContent.Controls.Clear();
// 大量操作...
});
}
protected override void OnResize(EventArgs e)
{
// 避免調整大小時閃爍
if (WindowState == FormWindowState.Maximized || WindowState == FormWindowState.Normal)
{
ControlRedrawHelper.SuspendDrawing(this);
base.OnResize(e);
ControlRedrawHelper.ResumeDrawing(this);
}
else
{
base.OnResize(e);
}
}
適用於自繪控制項或頻繁重繪場景:
// 表單層級啟用 (需在建構子中)
this.DoubleBuffered = true;
// 或針對特定控制項 (需透過反射設定 Protected 屬性)
public static void EnableDoubleBuffering(Control control)
{
var prop = typeof(Control).GetProperty("DoubleBuffered",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
prop?.SetValue(control, true, null);
}
// 使用範例
EnableDoubleBuffering(dataGridView1);
EnableDoubleBuffering(panelContainer);
當無邊框表單 (FormBorderStyle=None) 最大化且 TopMost=true 時,若使用 SuspendDrawing 可能導致工作列未重繪而消失。
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
private static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, uint flags);
private const uint RDW_INVALIDATE = 0x0001;
private const uint RDW_UPDATENOW = 0x0100;
private const uint RDW_ALLCHILDREN = 0x0080;
private void ForceTaskbarRedraw()
{
IntPtr taskbar = FindWindow("Shell_TrayWnd", null);
if (taskbar != IntPtr.Zero)
{
RedrawWindow(taskbar, IntPtr.Zero, IntPtr.Zero,
RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
}
}
// 在 ResumeDrawing 後呼叫
ControlRedrawHelper.ResumeDrawing(this);
ForceTaskbarRedraw();
.NET Framework 內部 Control.BeginUpdateInternal() 實作相同邏輯,但標記為 internal 僅供 ListView, TreeView, DataGridView 等少數控制項使用。上述輔助類別實質上是將此功能通用化。
SuspendDrawingScope 中。OnResize 或 SizeChanged 中暫停重繪。DoubleBuffered + ResizeRedraw。ForceTaskbarRedraw。SendMessage 內部處理,但邏輯層面應避免巢狀呼叫。