как остановить мерцание c# winforms

у меня есть программа, которая по существу похожа на приложение paint. Однако у моей программы есть некоторые мерцающие проблемы. У меня есть следующая строка в моем коде (которая должна избавиться от мерцания - но не делает):

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

мой код (минус супер и подклассы для фигур выглядит следующим образом:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Paint
{
    public partial class Paint : Form
    {
        private Point startPoint;
        private Point endPoint;
        private Rectangle rect = new Rectangle();
        private Int32 brushThickness = 0;
        private Boolean drawSPaint = false;
        private List<Shapes> listOfShapes = new List<Shapes>();
        private Color currentColor;
        private Color currentBoarderColor;
        private Boolean IsShapeRectangle = false;
        private Boolean IsShapeCircle = false;
        private Boolean IsShapeLine = false;

        public SPaint()
        {

            InitializeComponent();
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);

            currentColor = Color.Red;
            currentBoarderColor = Color.DodgerBlue;
            IsShapeRectangle = true; 
        }

        private void panelArea_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = panelArea.CreateGraphics();

            if (drawSPaint == true)
            {

                Pen p = new Pen(Color.Blue);
                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;

                if (IsShapeRectangle == true)
                {
                    g.DrawRectangle(p, rect);
                }
                else if (IsShapeCircle == true)
                {
                    g.DrawEllipse(p, rect);
                }
                else if (IsShapeLine == true)
                {
                    g.DrawLine(p, startPoint, endPoint);
                }
            }
            foreach (Shapes shape in listOfShapes)
            {

                shape.Draw(g);

            }
        }

        private void panelArea_MouseDown(object sender, MouseEventArgs e)
        {

            startPoint.X = e.X;
            startPoint.Y = e.Y;

            drawSPaint = true;
        }

        private void panelArea_MouseMove(object sender, MouseEventArgs e)
        {


            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {

                if (e.X > startPoint.X)
                {
                    rect.X = startPoint.X;
                    rect.Width = e.X - startPoint.X;
                }
                else
                {
                    rect.X = e.X;
                    rect.Width = startPoint.X - e.X;
                }
                if (e.Y > startPoint.Y)
                {
                    rect.Y = startPoint.Y;
                    rect.Height = e.Y - startPoint.Y;
                }
                else
                {
                    rect.Y = e.Y;
                    rect.Height = startPoint.Y - e.Y;
                }


                panelArea.Invalidate();

            }

        }

        private void panelArea_MouseUp(object sender, MouseEventArgs e)
        {

            endPoint.X = e.X;
            endPoint.Y = e.Y;

            drawSPaint = false;

            if (rect.Width > 0 && rect.Height > 0)
            {
                if (IsShapeRectangle == true)
                {
                    listOfShapes.Add(new TheRectangles(rect, currentColor, currentBoarderColor, brushThickness));
                }
                else if (IsShapeCircle == true)
                {
                    listOfShapes.Add(new TheCircles(rect, currentColor, currentBoarderColor, brushThickness));
                }
                else if (IsShapeLine == true)
                {
                    listOfShapes.Add(new TheLines(startPoint, endPoint, currentColor, currentBoarderColor, brushThickness));
                }

                panelArea.Invalidate();
            }
        }


        private void rectangleToolStripMenuItem_Click(object sender, EventArgs e)
        {
            IsShapeRectangle = true;
            IsShapeCircle = false;
            IsShapeLine = false; 
        }

        private void ellipseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            IsShapeRectangle = false;
            IsShapeCircle = true;
            IsShapeLine = false; 
        }

        private void lineToolStripMenuItem_Click(object sender, EventArgs e)
        {
            IsShapeCircle = false;
            IsShapeRectangle = false;
            IsShapeLine = true; 
        }

        private void ThicknessLevel0_Click(object sender, EventArgs e)
        {
            brushThickness = 0; 
        }

        private void ThicknessLevel2_Click(object sender, EventArgs e)
        {
            brushThickness = 2; 
        }

        private void ThicknessLevel4_Click(object sender, EventArgs e)
        {
            brushThickness = 4; 
        }

        private void ThicknessLevel6_Click(object sender, EventArgs e)
        {
            brushThickness = 6; 
        }

        private void ThicknessLevel8_Click(object sender, EventArgs e)
        {
            brushThickness = 8; 
        }

        private void ThicknessLevel10_Click(object sender, EventArgs e)
        {
            brushThickness = 10; 
        }

        private void ThicknessLevel12_Click(object sender, EventArgs e)
        {
            brushThickness = 12; 
        }

        private void ThicknessLevel14_Click(object sender, EventArgs e)
        {
            brushThickness = 14; 
        }

        private void FillColour_Click(object sender, EventArgs e)
        {

            ColorDialog fillColourDialog = new ColorDialog();
            fillColourDialog.ShowDialog();
            currentColor = fillColourDialog.Color;
            panelArea.Invalidate(); 
        }

        private void button1_Click(object sender, EventArgs e)
        {

            ColorDialog fillColourDialog = new ColorDialog();
            fillColourDialog.ShowDialog();
            currentBoarderColor = fillColourDialog.Color;
            panelArea.Invalidate(); 
        }


    }
}

Как убрать мерцание?

*обновление:*этот код на самом деле отлично работает, когда я рисую непосредственно на форме. Однако, когда я попробуйте нарисовать на панели, мерцание становится проблемой

13 ответов


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

    typeof(Panel).InvokeMember("DoubleBuffered", 
    BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic, 
    null, DrawingPanel, new object[] { true });

где "DrawingPanel" - это имя панели, которую вы хотите выполнить двойную буферизацию.

Я знаю, что прошло довольно много времени с тех пор, как был задан вопрос, но это может помочь кому-то в будущем.


наконец-то решил мерцание. Поскольку я рисовал на панели вместо формы, строка кода ниже не решит мерцание:

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

SetStyle должен иметь тип ' YourProject.YourProject' (или производный от него) следовательно, вы должны создать класс как таковой (чтобы вы могли использовать MyPanel, который будет производным от SPaint.SPaint и, следовательно, позволяет использовать doublebuffering непосредственно для панели-а не для формы):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SPaint; 

namespace YourProject
{
    public class MyPanel : System.Windows.Forms.Panel
    {
        public MyPanel()
        {
            this.SetStyle(
                System.Windows.Forms.ControlStyles.UserPaint | 
                System.Windows.Forms.ControlStyles.AllPaintingInWmPaint | 
                System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer, 
                true);
        }
    }
}

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

this.panelArea = new YourProject.MyPanel();

вышеуказанная строка должна быть изменена на:

this.panelArea = new MyPanel(); 

после того, как я завершил эти шаги, моя программа рисования больше не мерцает.

для тех, кто имеет ту же проблему, проблема, наконец, решена.

наслаждайтесь!


скопируйте и вставьте это в ваш проект

protected override CreateParams CreateParams
{
    get
    {
        CreateParams handleParam = base.CreateParams;
        handleParam.ExStyle |= 0x02000000;   // WS_EX_COMPOSITED       
        return handleParam;
    }
}

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


у меня была такая же проблема. Я никогда не мог на 100% избавиться от мерцания (см. пункт 2), но я использовал это

protected override void OnPaint(PaintEventArgs e) {}

а также

this.DoubleBuffered = true;

основной проблемой для мерцания является то, что вы

  1. покрасить вещи в правильном порядке!
  2. убедитесь, что ваша функция draw

winforms вызывает OnPaint метод каждый раз, когда форма должна быть перерисована. Есть много способы его девальвации, включая перемещение курсора мыши по форме, иногда могут вызывать событие перерисовки.

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

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

protected Graphics gfx = null;

а затем использовал его локально в OnPaint как так, но это было потому, что мне нужно было использовать объект gfx в других местах в моем классе. В противном случае не делайте этого. Если вы рисуете только в OnPaint, пожалуйста, используйте e.Graphics!!

// clean up old graphics object
gfx.Dispose();

// recreate graphics object (dont use e.Graphics, because we need to use it 
// in other functions)
gfx = this.CreateGraphics();

надеюсь, что это помогает.


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

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

прозрачная панель может быть сделана путем переопределения некоторых действий краски из исходной панели:

public class ClearPanel : Panel
{
    public ClearPanel(){}

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams createParams = base.CreateParams;
            createParams.ExStyle |= 0x00000020;
            return createParams;
        }
    }

    protected override void OnPaintBackground(PaintEventArgs e){}
}

идея состоит в том, чтобы обрабатывать рисование временной фигуры во время события MouseMove "panelArea" и только перерисовывать "panelDraw" на событии MouseUp.

// Use the panelDraw paint event to draw shapes that are done
void panelDraw_Paint(object sender, PaintEventArgs e)
{
    Graphics g = panelDraw.CreateGraphics();

    foreach (Rectangle shape in listOfShapes)
    {
        shape.Draw(g);
    }
}

// Use the panelArea_paint event to update the new shape-dragging...
private void panelArea_Paint(object sender, PaintEventArgs e)
{
    Graphics g = panelArea.CreateGraphics();

    if (drawSETPaint == true)
    {
        Pen p = new Pen(Color.Blue);
        p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;

        if (IsShapeRectangle == true)
        {
            g.DrawRectangle(p, rect);
        }
        else if (IsShapeCircle == true)
        {
            g.DrawEllipse(p, rect);
        }
        else if (IsShapeLine == true)
        {
            g.DrawLine(p, startPoint, endPoint);
        }
    }
}

private void panelArea_MouseUp(object sender, MouseEventArgs e)
{

    endPoint.X = e.X;
    endPoint.Y = e.Y;

    drawSETPaint = false;

    if (rect.Width > 0 && rect.Height > 0)
    {
        if (IsShapeRectangle == true)
        {
            listOfShapes.Add(new TheRectangles(rect, currentColor, currentBoarderColor, brushThickness));
        }
        else if (IsShapeCircle == true)
        {
            listOfShapes.Add(new TheCircles(rect, currentColor, currentBoarderColor, brushThickness));
        }
        else if (IsShapeLine == true)
        {
            listOfShapes.Add(new TheLines(startPoint, endPoint, currentColor, currentBoarderColor, brushThickness));
        }

        panelArea.Invalidate();
    }

    panelDraw.Invalidate();
}

Я знаю, что это очень старый вопрос, но может кто-то найдет его полезным.
Я бы хотел немного улучшить ответ Вайпера.

вы можете сделать простое расширение класса панели и скрыть свойство настройки через отражение.

public static class MyExtensions {

    public static void SetDoubleBuffered(this Panel panel) {
        typeof(Panel).InvokeMember(
           "DoubleBuffered",
           BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
           null,
           panel,
           new object[] { true });
    }
}

Если имя переменной панели-myPanel, вы можете просто вызвать
myPanel.SetDoubleBuffered();
и это все. Код выглядит намного чище.


Я бы посоветовал переопределить OnPaintBackground и обработать фон, чтобы стереть себя. Если вы знаете, что рисуете весь элемент управления, вы можете просто ничего не делать в OnPaintBackground (не вызывайте базовый метод), и это предотвратит окрашивание цвета фона первым


в этом условии вы должны включить двойной буфер . Откройте текущую форму и перейдите к свойствам формы и примените double buffer true; или вы также можете написать этот код .

this.DoubleBuffered = true;     

в загрузить форма.


вот программа перемещения круга в .net, которая не мерцает.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
namespace CircleMove
{
    /// <summary>
    /// Description of MainForm.
    /// </summary>
    public partial class MainForm : Form
    {
        int x=0,y=0;
        Thread t;

        public MainForm()
        {

            //
            // The InitializeComponent() call is required for Windows Forms designer support.
            //
            InitializeComponent();

            //
            // TODO: Add constructor code after the InitializeComponent() call.
            //
        }
        void MainFormPaint(object sender, PaintEventArgs e)
        {
            Graphics g=e.Graphics;
            Pen p=new Pen(Color.Orange);
            Brush b=new SolidBrush(Color.Red);
        //  g.FillRectangle(b,0,0,100,100);
            g.FillEllipse(b,x,y,100,100);
        }
        void MainFormLoad(object sender, EventArgs e)
        {
            t=new Thread(  new ThreadStart(

                ()=>{
                    while(true)
                    {
                        Thread.Sleep(10);
                        x++;y++;
                        this.Invoke(new Action(
                            ()=>{

                                this.Refresh();
                                this.Invalidate();
                                this.DoubleBuffered=true;
                                }
                                            )
                                        );
                    }
                    }
                                            )

                        );

            t.Start();
        }
    }
}

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

почему это помогает: мерцание-это мгновенная вспышка цвета фона, который ОС рисует перед рисованием дочерних элементов управления или пользовательского кода рисования. Если эта вспышка является цветом, который ближе к окончательному цвету, который будет отображаться, он будет меньше заметный.

Если вы не уверены, с какого цвета начать, начните с 50% серого, потому что это среднее черно-белое, поэтому будет ближе к большинству цветов в вашей сцене.

myFormOrControl.BackColor = Color.Gray;

попробуйте вставить логику рисования в текущую форму

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
}

метод. В этом случае для получения графического объекта следует использовать параметр e. Используйте e.Графическое свойство. Затем вы должны вызвать метод invalidate() для этой формы, когда форма должна быть перерисована. PS: DoubleBuffered должно быть установлено значение true.


Если все вышеперечисленное не сработает, вы всегда можете создать свой собственный двойной буфер ссылка на учебник Microsofts: https://docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-reduce-graphics-flicker-with-double-buffering-for-forms-and-controls

надеется, что это сработает для вас


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

или просто проверьте, если мышь вниз (через boolean, который устанавливает true, когда мышь не работает), используя таймер и покрасить его, учитывая, что вы, вероятно, пытаетесь просто нарисовать один пиксель, а не как у вас есть тень и т. д. вместо использования фактического mousedown. Таким образом, вы проверяете каждую 1 секунду вместо 0.0001 и он не будет мерцать. Или наоборот, попробуйте сделать это в свое время.