如果图像是背景,TabControl会闪烁

时间:2010-06-24 16:21:44

标签: winforms tabcontrol

我注意到如果我在面板中有一个带有图像背景的TabControl,当鼠标悬停在一个标签上时,它会闪烁并重绘。是否有解决方法来防止这种情况发生?

4 个答案:

答案 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();
      }
   }