【发布时间】:2010-06-24 16:21:43
【问题描述】:
我注意到,如果我在具有图像背景的面板中有一个 TabControl,则当鼠标悬停在选项卡上时,它会闪烁并重绘。是否有解决方法来防止这种情况发生?
【问题讨论】:
标签: winforms tabcontrol
我注意到,如果我在具有图像背景的面板中有一个 TabControl,则当鼠标悬停在选项卡上时,它会闪烁并重绘。是否有解决方法来防止这种情况发生?
【问题讨论】:
标签: winforms tabcontrol
我看到了。发生这种情况是因为 TabControl 部分通过要求父控件在其自己的窗口内绘制自身来绘制自身。这是必要的,因为选项卡没有覆盖控件的整个宽度,它们“伸出”。如果 BackgroundImage 的绘制速度很慢,您会看到正在绘制的背景和在其上绘制的选项卡之间闪烁。
这将很难解决,TabControl 不支持任何类型的双缓冲。您只能通过使 BackgroundImage 有效地绘制来最小化效果。为此,您需要使图像的大小与面板的 ClientSize 完全相同,这样就不必调整图像的大小。并使用 PixelFormat32bppPArgb 像素格式创建该位图,它通常比其他格式快 10 倍。
有一种灵丹妙药,窗口有一个样式标志,可以为整个窗口(包括其子控件)启用双缓冲。自 XP 以来受支持,但报告了一些副作用。将此代码粘贴到您的表单中,它修复了 TabControl 闪烁:
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}
但请注意,TabControl 的视觉样式渲染器与此样式标志有一个相当大的不兼容。如果您的选项卡溢出并且您看到选择箭头,那么它会变得很糟糕并开始一遍又一遍地渲染选项卡,从而产生非常高的闪烁率。
【讨论】:
我已尝试使用 CreateParams 解决方案,但它引入了自己的问题。我需要动态添加和删除选项卡,并且带有 WS_EX_COMPOSITED 的 TabControl 在我删除选项卡后不会重绘自身,即使在 Invalidate() 方法之后也是如此。只有当我将鼠标移动到选项卡区域时,TabControl 才会开始以一种非常奇怪的方式重绘自身。
所以我终于找到了这个解决方案:
public class TabControlX : TabControl
{
protected override void WndProc( ref Message m )
{
if( m.Msg == WinAPI.WM_MOUSEMOVE && !HotTrack )
return;
base.WndProc(ref m);
}
}
在哪里
public const int WM_MOUSEMOVE = 0x0200;
由于某些未知原因,HotTrack 属性在 TabControl 控件中不起作用,所以我实际上已经修复了它:)
当然,在调整大小时它不起作用,但对我来说没问题。
【讨论】:
多亏了包括 Hans Passant 在内的多个回答,我才能够制作一个切换版本,该版本仅在需要时处于该模式,在这种情况下:当标签控件因父表单而调整大小时。
这并不容易,因为我决定最好让它听窗体 resize begin 和 resize end...这就是我所做的,它适合我的需要,tabcontrol 在从 Form 调整大小时不再闪烁调整大小。
public class NoFlickerTabControl : TabControl
{
#region PInvoke Change Window Rendering Style Params
public static IntPtr SetWindowLong(HandleRef hWnd, int nIndex, IntPtr dwNewLong)
{
if (IntPtr.Size == 8)
{
return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
}
else
{
return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
}
}
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
private static extern int SetWindowLong32(HandleRef hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, IntPtr dwNewLong);
public enum WindowLongFlags : int
{
GWL_WNDPROC = -4,
GWL_HINSTANCE = -6,
GWL_HWNDPARENT = -8,
GWL_STYLE = -16,
GWL_EXSTYLE = -20,
GWL_USERDATA = -21,
GWL_ID = -12
}
#endregion
#region Tab Control Style!
public NoFlickerTabControl()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);
}
#region Events to use from Parent
private bool bNeedToLinkFormResizeEvents = true;
private void ParentForm_ResizeBegin(object sender, EventArgs e)
{
EnableWS_EX_COMPOSITED();
}
private void ParentForm_ResizeEnd(object sender, EventArgs e)
{
DisableWS_EX_COMPOSITED();
}
#endregion
#region Enable / Disabled WS_EX_COMPOSITED
private const int WS_EX_COMPOSITED = 0x02000000;
private void EnableWS_EX_COMPOSITED()
{
CreateParams cp = CreateParams;
cp.ExStyle |= WS_EX_COMPOSITED; // Turn on WS_EX_COMPOSITED
//Make our call.
HandleRef handleRef = new HandleRef(null, Handle);
IntPtr style = new IntPtr(cp.ExStyle);
SetWindowLong(handleRef, (int)WindowLongFlags.GWL_EXSTYLE, style);
}
private void DisableWS_EX_COMPOSITED()
{
CreateParams cp = CreateParams;
cp.ExStyle &= ~WS_EX_COMPOSITED; // Turn OFF WS_EX_COMPOSITED (in case it's been set)
//Make our call.
HandleRef handleRef = new HandleRef(null, Handle);
IntPtr style = new IntPtr(cp.ExStyle);
SetWindowLong(handleRef, (int)WindowLongFlags.GWL_EXSTYLE, style);
}
#endregion
protected override void WndProc(ref Message m)
{
int WM_MOUSEMOVE = 0x0200;
if (m.Msg == WM_MOUSEMOVE && !HotTrack)
{
return;
}
base.WndProc(ref m);
}
protected override void OnPaint(PaintEventArgs e)
{
if(Width <= 0 || Height <= 0)
{
return;
}
//Paint related, found it was best to do the check here when control finally gets Parent set by the program.
if (bNeedToLinkFormResizeEvents)
{
Form parentForm = FindForm();
if (parentForm != null)
{
bNeedToLinkFormResizeEvents = false;
parentForm.ResizeBegin += ParentForm_ResizeBegin;
parentForm.ResizeEnd += ParentForm_ResizeEnd;
}
}
//~~~~~~ DO THE PAINTING OF THE CONTROL NOW.
}
#endregion
}
【讨论】:
您可以尝试创建一个使用双缓冲的面板。从面板派生并将 DoubleBuffered 设置为 true:
public partial class DoubleBufferedPanel : Panel
{
public DoubleBufferedPanel()
{
InitializeComponent();
this.DoubleBuffered = true;
UpdateStyles();
}
}
【讨论】: