高效使用OnPaint

发布于 2024-08-15 10:23:25 字数 505 浏览 10 评论 0原文

我正在 Visual Studio .Net 中使用 C# 进行编程。

我正在创建自己的控件,该控件根据从模数转换器 (ADC) 获得的值绘制波形。我将传入的点转换为 X 和 Y 点,以便在我的控制中正确绘制图形。

我的 OnPaint 方法中有一个循环,它遍历所有点并调用当前点和下一个点之间的 DrawLine 方法。

然而,这是非常低效的,因为其中一些图表有 8192 个点,而系统实际上有 9 个我想同时显示的 ADC。每次页面重绘时,所有图形都需要几乎一秒钟的时间来重绘(尤其是在调试期间)。

最重要的是,我的功能允许您放大和平移波浪以获得更好的视图(其作用很像谷歌地图),并且所有 9 个波浪一起放大和平移。

所有这些功能都非常“不稳定”,因为我在鼠标滚轮和鼠标移动上调用无效。基本上,这一切都有效,但并不像我希望的那么顺利。

我想知道是否有一种方法可以从数据创建预绘制对象,然后在绘图区域中绘制图片的放大和平移版本。

任何帮助将不胜感激,即使它只是为我指明了正确的方向。

I am programming in Visual Studio .Net and using C#.

I am creating my own control that draws a wave based on values I get from an analog to digital converter (ADC). I take the incoming points and convert them into X and Y points to properly draw the graph in my control.

I have a loop inside my OnPaint method that goes through all the points and calls the DrawLine method between the current point and the next point.

However, this is very inefficient as some of these graphs have 8192 points and the system actually has nine ADCs that I would like to show at the same time. Every time the page redraws it takes almost a second for all graphs to redraw (especially during debug).

On top of that, I have functionality that allows you to zoom in and pan across the waves to get a better view (acts a lot like google maps does) and all 9 waves zoom in and pan together.

All of this functionality is very "jerky" because I am calling invalidate on mousewheel and mousemove. Basically, it all works but not as smoothly as I would like.

I was wondering if there were a way to create a predrawn object from the data and then just draw a dilated and translated version of the picture in the draw area.

Any help would be greatly appreciated even if it is just pointing me in the right direction.

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

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

发布评论

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

评论(5

盛夏已如深秋| 2024-08-22 10:23:25

创建一个 Bitmap 对象,然后绘制到该对象。

在您的 Paint 处理程序中,只需将位图传输到屏幕即可。

这将允许您将更改比例与重新渲染数据分离。

Create a Bitmap object, and draw to that.

In your Paint handler, just blit the Bitmap to the screen.

That will allow you decouple changing the scale, from re-rendering the data.

离不开的别离 2024-08-22 10:23:25

您可以在控件/表单上将 DoubleBuffered 设置为 true。或者您可以尝试使用自己的图像来创建双缓冲效果。

我的 DoubleBufferedGraphics 类:

public class DoubleBufferedGraphics : IDisposable
{
    #region Constructor
    public DoubleBufferedGraphics() : this(0, 0) { }

    public DoubleBufferedGraphics(int width, int height)
    {
        Height = height;
        Width = width;
    }
    #endregion

    #region Private Fields
    private Image _MemoryBitmap;
    #endregion

    #region Public Properties
    public Graphics Graphics { get; private set; }

    public int Height { get; private set; }

    public bool Initialized
    {
        get { return (_MemoryBitmap != null); }
    }

    public int Width { get; private set; }
    #endregion

    #region Public Methods
    public void Dispose()
    {
        if (_MemoryBitmap != null)
        {
            _MemoryBitmap.Dispose();
            _MemoryBitmap = null;
        }

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

    public void Initialize(int width, int height)
    {
        if (height > 0 && width > 0)
        {
            if ((height != Height) || (width != Width))
            {
                Height = height;
                Width = width;

                Reset();
            }
        }
    }

    public void Render(Graphics graphics)
    {
        if (_MemoryBitmap != null)
        {
            graphics.DrawImage(_MemoryBitmap, _MemoryBitmap.GetRectangle(), 0, 0, Width, Height, GraphicsUnit.Pixel);
        }
    }

    public void Reset()
    {
        if (_MemoryBitmap != null)
        {
            _MemoryBitmap.Dispose();
            _MemoryBitmap = null;
        }

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

        _MemoryBitmap = new Bitmap(Width, Height);
        Graphics = Graphics.FromImage(_MemoryBitmap);
    }

    /// <summary>
    /// This method is the preferred method of drawing a background image.
    /// It is *MUCH* faster than any of the Graphics.DrawImage() methods.
    /// Warning: The memory image and the <see cref="Graphics"/> object
    /// will be reset after calling this method. This should be your first
    /// drawing operation.
    /// </summary>
    /// <param name="image">The image to draw.</param>
    public void SetBackgroundImage(Image image)
    {
        if (_MemoryBitmap != null)
        {
            _MemoryBitmap.Dispose();
            _MemoryBitmap = null;
        }

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

        _MemoryBitmap = image.Clone() as Image;

        if (_MemoryBitmap != null)
        {
            Graphics = Graphics.FromImage(_MemoryBitmap);
        }
    }
    #endregion
}

OnPaint 中使用它:

protected override void OnPaint(PaintEventArgs e)
{
    if (!_DoubleBufferedGraphics.Initialized)
    {
        _DoubleBufferedGraphics.Initialize(Width, Height);
    }

    _DoubleBufferedGraphics.Graphics.DrawLine(...);

    _DoubleBufferedGraphics.Render(e.Graphics);
}

ControlStyles 如果我使用它,我通常会设置它(您可能有不同的需求):

SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.DoubleBuffer, true);

编辑:

好吧,由于数据是静态的,您应该绘制到图像(在 OnPaint 之前),然后在 OnPaint 中使用 Graphics.DrawImage() 重载将源图像的正确区域绘制到屏幕上。如果数据没有改变,就没有理由重新绘制线条。

You might set DoubleBuffered to true on your control / form. Or you could try using your own Image to create a double buffered effect.

My DoubleBufferedGraphics class:

public class DoubleBufferedGraphics : IDisposable
{
    #region Constructor
    public DoubleBufferedGraphics() : this(0, 0) { }

    public DoubleBufferedGraphics(int width, int height)
    {
        Height = height;
        Width = width;
    }
    #endregion

    #region Private Fields
    private Image _MemoryBitmap;
    #endregion

    #region Public Properties
    public Graphics Graphics { get; private set; }

    public int Height { get; private set; }

    public bool Initialized
    {
        get { return (_MemoryBitmap != null); }
    }

    public int Width { get; private set; }
    #endregion

    #region Public Methods
    public void Dispose()
    {
        if (_MemoryBitmap != null)
        {
            _MemoryBitmap.Dispose();
            _MemoryBitmap = null;
        }

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

    public void Initialize(int width, int height)
    {
        if (height > 0 && width > 0)
        {
            if ((height != Height) || (width != Width))
            {
                Height = height;
                Width = width;

                Reset();
            }
        }
    }

    public void Render(Graphics graphics)
    {
        if (_MemoryBitmap != null)
        {
            graphics.DrawImage(_MemoryBitmap, _MemoryBitmap.GetRectangle(), 0, 0, Width, Height, GraphicsUnit.Pixel);
        }
    }

    public void Reset()
    {
        if (_MemoryBitmap != null)
        {
            _MemoryBitmap.Dispose();
            _MemoryBitmap = null;
        }

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

        _MemoryBitmap = new Bitmap(Width, Height);
        Graphics = Graphics.FromImage(_MemoryBitmap);
    }

    /// <summary>
    /// This method is the preferred method of drawing a background image.
    /// It is *MUCH* faster than any of the Graphics.DrawImage() methods.
    /// Warning: The memory image and the <see cref="Graphics"/> object
    /// will be reset after calling this method. This should be your first
    /// drawing operation.
    /// </summary>
    /// <param name="image">The image to draw.</param>
    public void SetBackgroundImage(Image image)
    {
        if (_MemoryBitmap != null)
        {
            _MemoryBitmap.Dispose();
            _MemoryBitmap = null;
        }

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

        _MemoryBitmap = image.Clone() as Image;

        if (_MemoryBitmap != null)
        {
            Graphics = Graphics.FromImage(_MemoryBitmap);
        }
    }
    #endregion
}

Using it in an OnPaint:

protected override void OnPaint(PaintEventArgs e)
{
    if (!_DoubleBufferedGraphics.Initialized)
    {
        _DoubleBufferedGraphics.Initialize(Width, Height);
    }

    _DoubleBufferedGraphics.Graphics.DrawLine(...);

    _DoubleBufferedGraphics.Render(e.Graphics);
}

ControlStyles I generally set if I'm using it (you may have different needs):

SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.DoubleBuffer, true);

Edit:

Ok since the data is static you should paint to an Image (before your OnPaint) and then in the OnPaint use the Graphics.DrawImage() overload to draw the correct region of your source image to the screen. No reason to redraw the lines if the data isn't changing.

往事随风而去 2024-08-22 10:23:25

我补充两点:

  1. 你说你有8192分。您的绘图区域可能不超过 1000。我想您可以通过仅添加每 10 行左右的行来“降低图形的分辨率”。
  2. 您可以使用 GraphicsPath 类来存储所有必需的线条,并使用 Graphics.DrawPath 一次绘制它们。

这样,您将避免使用静态位图(以允许缩放),同时仍然获得一些性能改进。

I have two points to add:

  1. You say you have 8192 points. Your drawing area probably has no more then 1000. I suppose you could "lower the resolution" of your graph by adding only every 10th or so line.
  2. You could use GraphicsPath class to store all required lines and draw them all at once with Graphics.DrawPath

This way you'll avoid using static bitmap (to allow zooming) while still getting some performance improvements.

江南烟雨〆相思醉 2024-08-22 10:23:25

你可以画一条多线。我不确定它在 C# 中是什么样子,但它必须存在(它是基于 GDI/GDI+ 的 API)。这允许您一次性指定所有点,并允许 Windows 稍微优化调用(更少的堆栈推送/弹出保留在绘制算法中,而不是返回每个新点的代码)。

编辑:但是如果您的数据是静态的,那么使用输出的双缓冲/缓存图像比担心它的初始绘制更有效。

这是一个链接:http://msdn.microsoft。 com/en-us/library/system.windows.shapes.polyline.aspx

You could draw a multiline. I'm not sure what that looks like in C#, but it has to be there (Its a GDI/GDI+ based API). That allows you specify all the points in one go, and allows Windows to optimize the call a bit (less stack push/pop to stay inside of the draw algorithm rather than returning to your code for each new point).

EDIT: but if your data is static, then using a double buffered / cached image of your output is more efficient than worrying about the initial drawing of it.

Here's a link: http://msdn.microsoft.com/en-us/library/system.windows.shapes.polyline.aspx

倾其所爱 2024-08-22 10:23:25

只需计算可见范围并仅绘制这些点即可。
使用双缓冲。最后,您可以使用原始位图数据创建自己的多线绘制实现,例如使用 LockBits 并将像素颜色直接写入形成图片的字节中。使用 InvalidateRect(..) 重绘窗口的一部分。

Just calculate the visible range and draw only these points.
Use double buffering. And finally you can create you own realizaion of multiline drawing using raw bitmap data e.g. use LockBits and write pixel colors directly into bytes forming the picture.Use InvalidateRect(..) to redraw a portion of the window.

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