如何在不闪烁的情况下更改选项卡控件上的图像

How to change an image on tab control without flickering


我在 C# TabControl 的 header 上放了一张图片。我想在光标悬停在图像上时更改图像。

app image

现在我这样调用 Control.Refresh 方法:

TabControl tabControl1 = new TabControl();

[...]

private void tabControl1_MouseMove(object sender, MouseEventArgs e)
{
[...]

    tabControl1.Refresh();
}

private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
    e.Graphics.DrawImage(imagefile, arg1, arg2, arg3, arg4);

[...]
}

通过使用flag,在光标即将移到图像上并离开图像时调用Refresh方法,即不会连续多次调用Refresh方法。
这样,图像成功改变,但经常闪烁。
如何在不闪烁的情况下更改它?有什么想法吗?

为此您需要双缓冲区。

"Double Buffer is a technique where we draw all our graphic needs to an image stored in the memory (buffer) and after we are done with all our drawing needs we a draw a complete image from the memory onto the screen. This concentrates the drawing to the screen (an operation that badly effects the performance of the application) to a single operation rather than many small ones."

https://www.codeproject.com/kb/graphics/doublebuffering.aspx

TabControl 比较特殊,不能开启双缓冲。这是因为它请求活动选项卡的所有子元素将自己绘制到其内容上。不幸的是,即使您不想自定义页面绘图而只想自定义选项卡,这也会影响选项卡。

但是,您可以采取一些技巧来最大程度地减少闪烁:

1.永远不要使用 Refresh()

Refresh() 方法强制整个控件立即重新绘制,无论您此时正在做什么。相反,使用 Invalidate 将控件标记为无效,因此它将与其他无效控件一起在专门的刷新会话中重新绘制。

2。使完整的表格双缓冲

虽然TabControl不支持双缓冲,但是可以让整个Form双缓冲,这样可以减少闪烁。

只需覆盖表单中的 CreateParams 即可:

private const int WS_EX_COMPOSITED = 0x02000000;

protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= WS_EX_COMPOSITED;
        return cp;
    }
}

3。仅使无效区域无效

如果您使整个 TabControl 无效,它将与其子项一起完全重新绘制。但是您想使选项卡中的某些 16x16 像素矩形无效,以便您可以节省整个控件的刷新。

private int hoveredIndex = -1;
private Size imageSize = new Size(16, 16);

private void TabControl1_MouseMove(object sender, MouseEventArgs e)
{
    for (int i = 0; i < tabControl1.TabPages.Count; i++)
    {
        Rectangle rect = tabControl1.GetTabRect(i);
        Rectangle closeButton = GetImageLocation(rect);
        if (closeButton.Contains(e.Location))
        {
            if (hoveredIndex != i)
            {
                hoveredIndex = i;
                tabControl1.Invalidate(rect);
            }
        }
        else if (hoveredIndex == i)
        {
            tabControl1.Invalidate(tabControl1.GetTabRect(hoveredIndex));
            hoveredIndex = -1;
        }
    }
}

private void TabControl1_MouseLeave(object sender, EventArgs e)
{
    if (hoveredIndex != -1)
    {
        tabControl1.Invalidate(tabControl1.GetTabRect(hoveredIndex));
        hoveredIndex = -1;
    }
}

private Rectangle GetImageLocation(Rectangle rect)
{
    return new Rectangle(rect.Right - imageSize.Width, rect.Top, imageSize.Width, imageSize.Height);
}

private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
    Image image = e.Index == hoveredIndex ? imageHovered : imageNormal;
    e.Graphics.DrawImage(image, GetImageLocation(e.Bounds));
}