Лучшие практики для OnPaint, Invalidate, Clipping и Regions - PullRequest
4 голосов
/ 22 сентября 2011

У меня есть пользовательский элемент управления с полностью настраиваемой графикой многих объектов, которые рисуют сами (вызываемые из OnPaint), с фоном большого растрового изображения.У меня есть встроенные функции масштабирования и панорамирования, и все координаты объектов, нарисованных на холсте, находятся в растровых координатах .

Поэтому, если мой пользовательский элемент управления имеет ширину 1000 пикселей,Растровое изображение имеет ширину 1500 пикселей, и я увеличен на 200%, тогда в любой момент времени я буду смотреть только на 1/3 ширины растрового изображения.И объект, у которого есть прямоугольник, начинающийся в точке 100,100 на растровом изображении, будет отображаться в точке 200,200 на экране при условии, что вы прокрутились в крайнее левое положение.

В основном мне нужно создать эффективный способперерисовывать только то, что нужно перерисовать.Например, если я перемещаю объект, я могу добавить старый прямоугольник клипа этого объекта в область и объединить новый прямоугольник клипа этого объекта с той же самой областью, а затем вызвать Invalidate (region), чтобы перерисовать эти две области.

Однако, делая это таким образом, я должен постоянно преобразовывать координаты растрового изображения объектов в координаты экрана, прежде чем передать их в Invalidate.Я должен всегда предполагать, что ClipRectangle в PaintEventArgs находится в экранных координатах, когда другие окна делают недействительными мои.

Есть ли способ, которым я могу использовать возможности Region.Transform и Region.Translate, чтобы я сделалне нужно конвертировать из растрового изображения в экранные координаты?Таким образом, это не помешает получению PaintEventArgs в координатах экрана?Должен ли я использовать несколько регионов или есть лучший способ сделать все это?

Пример кода для того, что я делаю сейчас:

invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle));

SelectedItem.UpdateEndPoint(endPoint);

invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle));

this.Invalidate(invalidateRegion);

И вOnPaint () ...

protected override void OnPaint(PaintEventArgs e)
{
    invalidateRegion.Union(e.ClipRectangle);

    e.Graphics.SetClip(invalidateRegion, CombineMode.Union);
    e.Graphics.Clear(SystemColors.AppWorkspace);

    e.Graphics.TranslateTransform(AutoScrollPosition.X + CanvasBounds.X, AutoScrollPosition.Y + CanvasBounds.Y);

    DrawCanvas(e.Graphics, _ratio);

    e.Graphics.ResetTransform();

    e.Graphics.ResetClip();

    invalidateRegion.MakeEmpty();
}

1 Ответ

9 голосов
/ 19 ноября 2012

Поскольку многие люди просматривают этот вопрос, я продолжу и отвечу на него, насколько мне известно.

Класс Graphics, поставляемый с PaintEventArgs, всегда жестко обрезается запросом на аннулирование. Обычно это делается операционной системой, но это может быть сделано вашим кодом.

Вы не можете сбросить этот клип или выйти за эти границы клипа, но вам это не нужно. Во время рисования вам, как правило, не нужно заботиться о том, как оно обрезается, если вам не нужно отчаянно максимизировать производительность.

Графический класс использует стек контейнеров для применения отсечения и преобразований. Вы можете расширить этот стек самостоятельно, используя Graphics.BeginContainer и Graphics.EndContainer. Каждый раз, когда вы начинаете контейнер, любые изменения, которые вы вносите в Transform или Clip, являются временными и применяются после любого предыдущего Transform или Clip, который был настроен до BeginContainer. По сути, когда вы получаете событие OnPaint, оно уже обрезано, и вы находитесь в новом контейнере, поэтому вы не можете видеть клип (ваш регион Clip или ClipRect будет отображаться как бесконечный), и вы не сможете вырваться из него. границы клипа.

Когда состояние ваших визуальных объектов изменяется (например, при событиях мыши или клавиатуры или при реагировании на изменения данных), обычно достаточно просто вызвать Invalidate (), который перекрасит весь элемент управления. Windows будет вызывать OnPaint в моменты низкой загрузки процессора. Каждый вызов Invalidate () обычно не всегда соответствует событию OnPaint. Invalidate может быть вызван несколько раз до следующего рисования. Таким образом, если 10 свойств в вашей модели данных изменяются одновременно, вы можете безопасно вызывать Invalidate 10 раз при каждом изменении свойств, и вы, скорее всего, вызовете только одно событие OnPaint.

Я заметил, что вы должны быть осторожны с использованием Update () и Refresh (). Это сразу вызывает синхронную OnPaint. Они полезны для рисования во время однопоточной операции (возможно, обновление индикатора выполнения), но использование их в неподходящее время может привести к чрезмерному и ненужному рисованию.

Если вы хотите использовать прямоугольники клипа для улучшения производительности при перерисовке сцены, вам не нужно самостоятельно отслеживать агрегированную область клипа. Windows сделает это за вас. Просто аннулируйте прямоугольник или область, которая требует аннулирования, и раскрасьте как обычно. Например, если объект, который вы рисуете, перемещается, каждый раз, когда вы хотите сделать недействительными его старые и новые границы, вы перекрашиваете фон в том виде, в котором он был изначально, в дополнение к рисованию в новом месте. Вы также должны учитывать размеры хода пера и т. Д.

И, как упоминал Ганс Пассант, всегда используйте 32bppPArgb в качестве растрового формата для изображений с высоким разрешением. Вот фрагмент кода о том, как загрузить изображение с «высокой производительностью»:

public static Bitmap GetHighPerformanceBitmap(Image original)
{
    Bitmap bitmap;

    bitmap = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppPArgb);
    bitmap.SetResolution(original.HorizontalResolution, original.VerticalResolution);

    using (Graphics g = Graphics.FromImage(bitmap))
    {
        g.DrawImage(original, new Rectangle(new Point(0, 0), bitmap.Size), new Rectangle(new Point(0, 0), bitmap.Size), GraphicsUnit.Pixel);
    }

    return bitmap;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...