简体   繁体   English

有效使用OnPaint

[英]Efficient use of OnPaint

I am programming in Visual Studio .Net and using C#. 我在Visual Studio .Net中编程并使用C#。

I am creating my own control that draws a wave based on values I get from an analog to digital converter (ADC). 我正在创建自己的控件,根据从模数转换器(ADC)获得的值来绘制波形。 I take the incoming points and convert them into X and Y points to properly draw the graph in my control. 我将传入点转换为X和Y点,以便在我的控制中正确绘制图形。

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. 我的OnPaint方法中有一个循环遍历所有点并在当前点和下一个点之间调用DrawLine方法。

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. 然而,这是非常低效的,因为这些图中的一些具有8192个点并且系统实际上具有我想要同时显示的九个ADC。 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. 最重要的是,我有一些功能,允许你放大和平移波浪以获得更好的视图(行为很像谷歌地图)和所有9波放大和平移。

All of this functionality is very "jerky" because I am calling invalidate on mousewheel and mousemove. 所有这些功能都非常“生涩”,因为我在mousewheel和mousemove上调用invalidate。 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. 即使它只是指向正确的方向,任何帮助将不胜感激。

Create a Bitmap object, and draw to that. 创建一个Bitmap对象,并绘制到该对象。

In your Paint handler, just blit the Bitmap to the screen. 在Paint处理程序中,只需将Bitmap blit到屏幕即可。

That will allow you decouple changing the scale, from re-rendering the data. 这将允许您从重新渲染数据中分离更改比例。

You might set DoubleBuffered to true on your control / form. 您可以在控件/表单DoubleBuffered设置为true。 Or you could try using your own Image to create a double buffered effect. 或者您可以尝试使用自己的Image创建双缓冲效果。

My DoubleBufferedGraphics class: 我的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
}

Using it in an OnPaint : 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): ControlStyles我通常设置如果我正在使用它(你可能有不同的需求):

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. 好的,因为数据是静态的,你应该绘制一个Image(在OnPaint之前),然后在OnPaint中使用Graphics.DrawImage()重载将源图像的正确区域绘制到屏幕上。 No reason to redraw the lines if the data isn't changing. 如果数据没有变化,没有理由重新绘制线条。

I have two points to add: 我有两点要补充:

  1. You say you have 8192 points. 你说你有8192分。 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. 您的绘图区域可能不超过1000.我想您可以通过仅添加每10行左右来“降低图形的分辨率”。
  2. You could use GraphicsPath class to store all required lines and draw them all at once with Graphics.DrawPath 您可以使用GraphicsPath类来存储所有必需的行,并使用Graphics.DrawPath一次性绘制它们

This way you'll avoid using static bitmap (to allow zooming) while still getting some performance improvements. 这样您就可以避免使用静态位图(以允许缩放),同时仍然可以获得一些性能改进。

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). 我不确定在C#中看起来像什么,但它必须在那里(它是基于GDI / GDI +的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). 这允许您一次性指定所有点,并允许Windows稍微优化调用(减少堆栈推送/弹出以保持在绘制算法内部而不是返回到每个新点的代码)。

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 这是一个链接: http//msdn.microsoft.com/en-us/library/system.windows.shapes.polyline.aspx

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 eg use LockBits and write pixel colors directly into bytes forming the picture.Use InvalidateRect(..) to redraw a portion of the window. 最后,您可以使用原始位图数据创建自己的多线绘图实现,例如使用LockBits并将像素颜色直接写入形成图片的字节中。使用InvalidateRect(..)重绘窗口的一部分。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM