Эффективное использование OnPaint - PullRequest
2 голосов
/ 17 декабря 2009

Я программирую в Visual Studio .Net и использую C #.

Я создаю свой собственный элемент управления, который рисует волну на основе значений, которые я получаю от аналого-цифрового преобразователя (АЦП). Я беру входящие точки и конвертирую их в точки X и Y, чтобы правильно нарисовать график в моем элементе управления.

У меня внутри метода OnPaint есть цикл, который проходит через все точки и вызывает метод DrawLine между текущей точкой и следующей точкой.

Однако, это очень неэффективно, поскольку некоторые из этих графиков имеют 8192 точки, а система фактически имеет девять АЦП, которые я хотел бы показать одновременно. Каждый раз, когда страница перерисовывается, для перерисовки всех графиков требуется почти секунда (особенно во время отладки).

Кроме того, у меня есть функциональность, которая позволяет вам увеличивать и перемещаться по волнам, чтобы получить лучшее представление (во многом похоже на то, как это делают карты Google), а все 9 волн увеличивают и панорамируют вместе.

Вся эта функциональность очень "отрывистая", потому что я вызываю инвалидацию на колесах мыши и перемещении мыши. В принципе, все работает, но не так гладко, как хотелось бы.

Мне было интересно, есть ли способ создать предварительно нарисованный объект из данных, а затем просто нарисовать расширенную и переведенную версию изображения в области рисования.

Любая помощь будет принята с благодарностью, даже если она просто направит меня в правильном направлении.

Ответы [ 5 ]

6 голосов
/ 17 декабря 2009

Создайте растровый объект и нарисуйте его.

В вашем обработчике Paint просто перетащите растровое изображение на экран.

Это позволит вам отделить изменение масштаба от повторного рендеринга данных.

3 голосов
/ 17 декабря 2009

Вы можете установить 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);

Edit:

Хорошо, поскольку данные статичны, вы должны нарисовать изображение (до вашей OnPaint), а затем в OnPaint использовать перегрузку Graphics.DrawImage(), чтобы нарисовать правильную область исходного изображения на экране. Нет причин перерисовывать строки, если данные не меняются.

1 голос
/ 18 декабря 2009

У меня есть две точки для добавления:

  1. Вы говорите, что у вас 8192 очка. Ваша область рисования, вероятно, имеет не более 1000. Я полагаю, что вы могли бы «уменьшить разрешение» вашего графика, добавив только каждую десятую или около того строку.
  2. Вы можете использовать класс GraphicsPath для хранения всех необходимых линий и рисовать их все одновременно с помощью Graphics.DrawPath

Таким образом, вы избежите использования статического растрового изображения (чтобы разрешить масштабирование), в то же время получая некоторые улучшения производительности.

0 голосов
/ 18 декабря 2009

Просто рассчитайте видимый диапазон и нарисуйте только эти точки. Используйте двойную буферизацию. И, наконец, вы можете создать собственную реализацию многострочного рисования, используя необработанные растровые данные, например, используйте LockBits и записывайте цвета пикселей непосредственно в байты, формирующие изображение. Используйте InvalidateRect (..), чтобы перерисовать часть окна.

0 голосов
/ 17 декабря 2009

Вы можете нарисовать мультилинию. Я не уверен, как это выглядит в C #, но он должен быть там (это API на основе GDI / GDI +). Это позволяет вам указывать все точки за один раз и позволяет Windows немного оптимизировать вызов (меньше стеков / нажатий, чтобы оставаться внутри алгоритма отрисовки, а не возвращаться к вашему коду для каждой новой точки).

РЕДАКТИРОВАТЬ: но если ваши данные статичны, то использование двойного буферизованного / кэшированного изображения вашего вывода более эффективно, чем беспокойство о первоначальном отображении.

Вот ссылка: http://msdn.microsoft.com/en-us/library/system.windows.shapes.polyline.aspx

...