我注意到如果我在面板中有一个带有图像背景的TabControl,当鼠标悬停在一个标签上时,它会闪烁并重绘。是否有解决方法来防止这种情况发生?
答案 0 :(得分:10)
我明白了。之所以发生这种情况,是因为TabControl通过要求父控件在自己的窗口中绘制自己来部分绘制自己。必要的是因为标签没有覆盖控件的整个宽度,所以它们“伸出”。如果BackgroundImage的绘制速度很慢,您会看到正在绘制的背景和在其上绘制的标签之间出现闪烁。
这很难解决,TabControl不支持任何类型的双缓冲。您只能通过使BackgroundImage高效绘制来最小化效果。您需要通过使图像与面板的ClientSize的大小完全相同来实现,这样就不必调整图像的大小。并使用PixelFormat32bppPArgb像素格式创建该位图,通常比其他格式快10倍。
有一种神奇的治疗方法,Windows有一个样式标志,可以为整个窗口启用双缓冲,包括其子控件。从XP开始支持,但已报告了一些副作用。将此代码粘贴到表单中,它修复了TabControl闪烁:
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}
但要注意TabControl的视觉样式渲染器与此样式标志有一个相当大的不兼容性。如果您的标签溢出并且您获得了选择箭头,那么它会变成香蕉并开始一遍又一遍地渲染标签,从而产生非常高的闪烁率。
答案 1 :(得分:0)
我尝试使用 CreateParams 解决方案,并引入了自己的问题。我需要动态添加和删除选项卡,并且在删除选项卡后,即使在Invalidate()方法之后,带有WS_EX_COMPOSITED的TabControl也不会重绘自身。只有当我将鼠标移动到标签区域时,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控件中不起作用,所以我实际修复了它:)
当然,它在调整大小时不起作用,但对我来说没问题。
答案 2 :(得分:0)
感谢包括Hans Passant在内的多个答案,我能够制作一个只在需要时才能处于该模式的切换版本,在这种情况下:由于父窗体调整选项卡控件的大小。
这并不容易,因为我决定最好让它听一下调整大小开始并调整结束...这就是我做了什么,它适合我的需要,不再是tabcontrol闪烁从调整大小调整大小。
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
}
答案 3 :(得分:-1)
您可以尝试创建使用双缓冲的面板。从面板派生并将DoubleBuffered设置为true:
public partial class DoubleBufferedPanel : Panel
{
public DoubleBufferedPanel()
{
InitializeComponent();
this.DoubleBuffered = true;
UpdateStyles();
}
}