6

我自己的使用位图的用户控件有两个问题:

  1. 如果通过 .NET 的“刷新”方法重绘,它会闪烁。
  2. 它的性能很差。

该控件由三个位图组成:

  • 静态背景图像。
  • 一个旋转的转子。
  • 另一个取决于转子角度的图像。

所有使用的位图的分辨率均为 500x500 像素。控件的工作方式如下: https ://www.dropbox.com/s/t92gucestwdkx8z/StatorAndRotor.gif (这是一个 gif 动画)

用户控件应在每次获得新的转子角度时自行绘制。因此,它有一个公共属性“RotorAngle”,如下所示:

public double RotorAngle
{
    get { return mRotorAngle; }
    set
    {
        mRotorAngle = value;
        Refresh();
    }
}

Refresh引发Paint事件。事件OnPaint处理程序如下所示:

private void StatorAndRotor2_Paint(object sender, PaintEventArgs e)
{
    // Draw the three bitmaps using a rotation matrix to rotate the rotor bitmap.
    Draw((float)mRotorAngle);
}

但是,当我使用此代码(在其他自己的用户控件中运行良好)时,如果控件通过SetStyle(ControlStyles.OptimizedDoubleBuffer, true). 如果我不将此标志设置为 true,则重绘时控件会闪烁。

在控制构造函数中我设置:

SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ContainerControl, false);
// User control is not drawn if "OptimizedDoubleBuffer" is true.
// SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.SupportsTransparentBackColor, true);

首先,我认为它会闪烁,因为每次绘制控件时都会清除背景。因此,我设置SetStyle(ControlStyles.AllPaintingInWmPaint, true). 但这没有帮助。

那么,为什么会闪烁呢?其他控件在此设置下工作得很好。以及为什么不绘制如果SetStyle(ControlStyles.OptimizedDoubleBuffer, true)

Draw我发现如果在更改属性后直接调用我的方法,控件不会闪烁RotorAngle

public float RotorAngle
{
    get { return mRotorAngle; }
    set
    {
        mRotorAngle = value;
        Draw(mRotorAngle);
    }
}

但这会导致性能非常差,尤其是在全屏模式下。不可能每 20 毫秒更新一次控件。你可以自己试试。我将在下面附上完整的 Visual Studio 2008 解决方案。

那么,为什么会出现如此糟糕的表现呢?每 20 毫秒更新一次其他(自己的)控件是没有问题的。真的只是因为位图吗?

我创建了一个简单的 Visual Studio 2008 解决方案来演示这两个问题: https ://www.dropbox.com/s/mckmgysjxm0o9e0/WinFormsControlsTest.zip (289,3 KB)

目录中有一个可执行文件bin\Debug

谢谢你的帮助。

4

5 回答 5

4

首先,根据 LarsTech 的回答,您应该Graphics使用PaintEventArgs. CreateGraphics()通过在处理程序内部调用Paint,您将OptimizedDoubleBuffer无法正常工作。

其次,在您的 SetStyle 块中,添加:

SetStyle( ControlStyles.Opaque, true );

...以防止基类 Control 在调用您的 Paint 处理程序之前填充背景颜色。

我在您的示例项目中对此进行了测试,它似乎消除了闪烁。

于 2012-05-14T15:24:04.393 回答
4

不要使用CreateGraphics,而是使用从绘制事件传递给您的 Graphic 对象:

我像这样更改它并添加了一个清晰的因为调整大小会显示重影图像:

private void Draw(float rotorAngle, Graphics graphics)
{
  graphics.Clear(SystemColors.Control);
  graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;

  // yada-yada-yada

  // do not dispose since you did not create it:
  // graphics.Dispose();
}

调用自:

private void StatorAndRotor2_Paint(object sender, PaintEventArgs e)
{
  Draw((float)mRotorAngle, e.Graphics);
}

在构造函数中,打开双缓冲,但我认为不需要透明度:

SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ContainerControl, false);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.ResizeRedraw, true);
//SetStyle(ControlStyles.SupportsTransparentBackColor, true);
于 2012-05-14T15:25:52.307 回答
2

在 Paint 事件上在屏幕上绘图是一项繁重的操作。在内存缓冲区上进行绘制相对来说非常快。

双缓冲将提高性能。与其在绘制图形上绘图,不如创建一个新图形,然后在其上进行所有绘制。

位图绘制完成后,将整个位图复制到从 PaintEventArgs 接收的 e.Graphics

使用 GDI+ 和 C# 进行无闪烁绘图

于 2012-05-14T15:17:32.483 回答
0

对于我的回答,我从https://stackoverflow.com/a/2608945/455904获得灵感,并从上面的LarsTechs回答中获得了一些东西。

为避免必须在所有 OnPaint 上重新生成完整图像,您可以使用变量来保存生成的图像。

private Bitmap mtexture;

使用 Draw() 生成纹理

private void Draw(float rotorAngle)
{
    using (var bufferedGraphics = Graphics.FromImage(mtexture))
    {
        Rectangle imagePosition = new Rectangle(0, 0, Width, Height);
        bufferedGraphics.DrawImage(mStator, imagePosition);
        bufferedGraphics.DrawImage(RotateImage(mRotor, mRotorAngle), imagePosition);

        float normedAngle = mRotorAngle % cDegreePerFullRevolution;

        if (normedAngle < 0)
            normedAngle += cDegreePerFullRevolution;

        if (normedAngle >= 330 || normedAngle <= 30)
            bufferedGraphics.DrawImage(mLED101, imagePosition);
        if (normedAngle > 30 && normedAngle < 90)
            bufferedGraphics.DrawImage(mLED001, imagePosition);
        if (normedAngle >= 90 && normedAngle <= 150)
            bufferedGraphics.DrawImage(mLED011, imagePosition);
        if (normedAngle > 150 && normedAngle < 210)
            bufferedGraphics.DrawImage(mLED010, imagePosition);
        if (normedAngle >= 210 && normedAngle <= 270)
            bufferedGraphics.DrawImage(mLED110, imagePosition);
        if (normedAngle > 270 && normedAngle < 330)
            bufferedGraphics.DrawImage(mLED100, imagePosition);
    }
}

让 OnPaint 的覆盖在您的控件上绘制纹理

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    Rectangle imagePosition = new Rectangle(0, 0, Width, Height);
    e.Graphics.DrawImage(mtexture, imagePosition);
}

需要时覆盖 OnInvalidated() 到 Draw() 纹理

protected override void OnInvalidated(InvalidateEventArgs e)
{
    base.OnInvalidated(e);

    if (mtexture != null)
    {
        mtexture.Dispose();
        mtexture = null;
    }

    mtexture = new Bitmap(Width, Height);
    Draw(mRotorAngle);
}

而不是调用 Draw 使图像无效。这将导致它使用 OnInvalidated 和 OnPaint 重新绘制。

public float RotorAngle
{
    get { return mRotorAngle; }
    set
    {
        mRotorAngle = value;
        Invalidate();
    }
}

我希望我把所有的东西都放在那里了:)

于 2012-05-14T16:05:17.543 回答
0

非常感谢您的帮助。

闪退就解决了。:)

现在,我按照LarsTech 的建议使用Graphics来自PaintEventArgs.

感谢lnmx提示CreateGraphics()内部Paint处理程序阻止OptimizedDoubleBuffer. 这解释了即使OptimizedDoubleBuffer启用了闪烁问题。我不知道,我也没有在MSDN Library找到这个。在我之前的控件中,我还使用了Graphics来自PaintEventArgs.

谢萨洛的努力。今天我会测试你的代码,我会给出反馈。我希望这会提高性能,因为仍然存在性能问题——尽管双缓冲正确。

我的原始代码中还有另一个性能问题。

改变了

graphics.DrawImage(mStator, imagePosition);
graphics.DrawImage(RotateImage(mRotor, rotorAngle), imagePosition);

graphics.DrawImage(mStator, imagePosition);
Bitmap rotatedImage = RotateImage(mRotor, rotorAngle);
graphics.DrawImage(rotatedImage, imagePosition);
rotatedImage.Dispose(); // Important, otherwise the RAM will be flushed with bitmaps.
于 2012-05-15T06:37:23.383 回答