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

发布于 2024-09-06 14:10:43 字数 79 浏览 4 评论 0原文

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

I have noticed that if I have a TabControl in a Panel that has an Image Background, when the mouse hovers over a tab it blinks and redraws. Is there a workaround to prevent this from happening?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

何以心动 2024-09-13 14:10:43

我看到了。发生这种情况是因为 TabControl 通过要求父控件在其自己的窗口内绘制自身来部分地绘制自身。这是必要的,因为选项卡没有覆盖控件的整个宽度,它们“突出”。如果背景图像绘制速度很慢,您会看到正在绘制的背景和在其上绘制的选项卡之间闪烁。

这将很难修复,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 的视觉样式渲染器与此样式标志有一个相当大的不兼容性。如果您的选项卡溢出并且出现选择箭头,那么它就会变得疯狂并开始一遍又一遍地渲染选项卡,从而产生非常高的闪烁率。

I see it. It happens because TabControl draws itself partly by asking the parent control to draw itself inside its own window. Necessary because the tabs don't cover the full width of the control, they "stick out". If the BackgroundImage is slow to draw, you'll see a flicker between the background being drawn and the tabs drawn on top of that.

This is going to be hard to fix, TabControl doesn't support any kind of double buffering. You can only minimize the effect by making the BackgroundImage efficient to draw. You need to do so by making the image exactly the size of the panel's ClientSize so that the image doesn't have to be resized. And create that bitmap with the PixelFormat32bppPArgb pixel format, it is usually 10 times faster than the other ones.

There's one magic cure available, windows have a style flag that enables double-buffering for the entire window, including its child controls. Supported since XP but some side-effects have been reported. Paste this code into your form, it fixes the TabControl flicker:

    protected override CreateParams CreateParams {
        get {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
            return cp;
        }
    }

But beware that the visual style renderer for TabControl has one rather major incompatibility with this style flag. If your tabs overflow and you get the selection arrows then it goes bananas and starts rendering the tabs over and over again, producing a very high rate of flicker.

┼── 2024-09-13 14:10:43

我尝试过使用 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 控件中不起作用,所以我实际上已经修复了它:)

当然,它在调整大小期间不起作用,但对我来说没问题。

I've tried solution with CreateParams and it introduces its own problems. I need to add and delete tabs dynamically, and TabControl with WS_EX_COMPOSITED doesn't redraw itself after I delete a tab, even after Invalidate() method. Only when I move the mouse into tabs area, TabControl starts to redraw itself and in a very weird manner.

So finally I came to this solution:

public class TabControlX : TabControl
{
    protected override void WndProc( ref Message m )
    {
        if( m.Msg == WinAPI.WM_MOUSEMOVE && !HotTrack )
            return;

        base.WndProc(ref m);
    }
}

Where

public const int WM_MOUSEMOVE = 0x0200;

For some unknown reasons HotTrack property doesn't work in TabControl control, so I've actually fixed it :)

Of course, it doesn't work during resizing, but it OK with me.

箹锭⒈辈孓 2024-09-13 14:10:43

感谢包括 Hans Passant 在内的多个答案,我能够制作一个切换版本,该版本仅在需要时才处于该模式,在本例中:当由于父窗体而调整选项卡控件大小时。

这并不容易,因为我决定最好让它监听表单调整大小开始和调整大小结束...这就是我所做的,它满足我的需要,在调整大小的表单中,选项卡控件不再在调整大小时闪烁。

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
}

Thanks to multiple answers including from Hans Passant, I was able to make a toggled version that would only be in that mode when it needed to be, in this case: when the tab control was being resized because of the parent form.

It wasn't easy as I decided it was best to have it listen to the form resize begin and resize end... Here's what I did and it suits my needs, no longer does tabcontrol flicker on resize from the Form being resized.

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
}
好听的两个字的网名 2024-09-13 14:10:43

您可以尝试创建一个使用双缓冲的面板。从面板派生并将 DoubleBuffered 设置为 true:

   public partial class DoubleBufferedPanel : Panel
   {
      public DoubleBufferedPanel()
      {
         InitializeComponent();

         this.DoubleBuffered = true;

         UpdateStyles();
      }
   }

You can try creating a panel that uses double buffering. Derive from panel and set DoubleBuffered to true:

   public partial class DoubleBufferedPanel : Panel
   {
      public DoubleBufferedPanel()
      {
         InitializeComponent();

         this.DoubleBuffered = true;

         UpdateStyles();
      }
   }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文