【问题标题】:how to stop flickering C# winforms如何停止闪烁的 C# winforms
【发布时间】:2011-12-24 04:44:24
【问题描述】:

我有一个本质上类似于绘画应用程序的程序。但是,我的程序有一些闪烁的问题。我的代码中有以下行(应该摆脱闪烁 - 但没有):

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(); 
        }


    }
}

如何停止闪烁?

*更新:*当我直接在表单上绘图时,此代码实际上效果很好。但是,当我尝试在面板上绘图时,闪烁成为问题

【问题讨论】:

  • 你也设置this.DoubleBuffered = true;了吗?
  • @ Marc Gravell 我刚刚尝试添加 this.DoubleBuffered = true;它还在疯狂地闪烁:S
  • panelArea 是否充满了控件? Invalidate 递归工作,因此可能会踢出 panelArea 中的每个子控件以重新绘制自身
  • @Polity 不,panelArea 只是我用来绘制的面板。虽然它没有控件..
  • 您是否在所有控件上设置了 ClipSiblings 样式?闪烁可能是由控件上的多次重绘引起的(如果它们重叠)。

标签: c# drawing


【解决方案1】:

我也遇到了同样的问题。我永远无法 100% 摆脱闪烁(见第 2 点),但我使用了这个

protected override void OnPaint(PaintEventArgs e) {}

还有

this.DoubleBuffered = true;

闪烁的主要问题是确保你

  1. 按照正确的顺序绘制事物!
  2. 确保您的绘图函数是

每次需要重绘表单时,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();

希望这会有所帮助。

【讨论】:

  • 这并没有解决我的问题 :( .. 当我直接在表单上绘图时,此代码实际上效果很好。但是,当我尝试在面板上绘图时,闪烁成为一个问题。 ..
  • @BlueMonster 比尝试 Panel.DoubleBuffered = true;
  • 我建议创建一个面板类的子对象,从面板继承,然后覆盖该子对象的protected override void OnPaint(PaintEventArgs e) {} 新对象将显示在要放入表单的项目列表中。如果您已经创建了面板并且没有重新创建它的简单选项,您也可以手动编辑代码以更改引用类型。
  • 使用e.Graphicsthis.DoubleBuffered = true 会有所帮助。通过使用创建的Graphics,双缓冲不仅增加了闪烁效果,而且在大部分时间使Control(或Form)变白。
【解决方案2】:

我建议重写 OnPaintBackground 并自己处理背景擦除。如果你知道你正在绘制整个控件,你可以在 OnPaintBackground 中什么都不做(不要调用基本方法),它会阻止首先绘制背景颜色

【讨论】:

  • 重新阅读您的问题后,这可能无济于事...除了建议可能子类化面板并允许您覆盖 OnPaint 和 OnPaintBackground,并在那里进行绘图...跨度>
【解决方案3】:

您能否尝试使用计时器和布尔值来检查鼠标是否按下,并在该位置绘制,再次使用变量检查用户是否移动了他的鼠标,如果移动也绘制该位置等。

或者只是使用计时器检查鼠标是否按下(通过在鼠标按下时设置为 true 的布尔值)并绘制它,考虑到您可能只是尝试绘制一个像素,而不是像你有阴影等。 而不是使用实际的鼠标按下。因此,您每 1 秒检查一次而不是 0.0001,它不会闪烁。反之亦然,请根据自己的时间尝试。

【讨论】:

  • 在绘制窗体或控件的背景颜色(通常为白色)时发生闪烁,然后您的自定义代码运行以重绘受影响的区域。 “闪烁”是背景颜色的瞬间闪烁。解决闪烁需要双缓冲,让系统有一个地方可以得到想要的内容进行恢复,而不是绘制背景色。把你的绘图代码放在一个计时器上比什么都不做更糟糕(花费 cpu 周期没有好处)。依靠操作系统告诉您何时需要绘图,不要试图超越它。
【解决方案4】:

恐怕双缓冲在这里没有太大帮助。我不久前遇到了这个问题,最后以一种相当笨拙的方式添加了一个单独的面板,但它对我的应用程序有用。

使您拥有的原始面板 ( panelArea ) 成为透明区域,并将其放在第二个面板的顶部,例如您将其称为 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){}
}

这个想法是在“panelArea”的 MouseMove 事件期间处理临时形状的绘制,并且仅在 MouseUp 事件上重新绘制“panelDraw”。

// 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();
}

【讨论】:

    【解决方案5】:

    终于解决了闪烁的问题。由于我是在面板而不是表单上绘图,因此下面的代码行将无法解决闪烁问题:

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

    SetStyle 必须是 'YourProject.YourProject' 类型(或派生自它),因此,您必须创建一个这样的类(以便您可以使用从 SPaint.SPaint 派生的 MyPanel,从而允许您使用直接为面板进行双缓冲 - 而不是表单):

    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);
            }
        }
    }
    

    完成此操作后(尽管您真的不应该编辑设计器代码,除非您真的知道自己在做什么),您将不得不编辑 Form.Designer.cs。在此文件中,您会发现如下代码:

    this.panelArea = new YourProject.MyPanel();
    

    上面一行需要改成:

    this.panelArea = new MyPanel(); 
    

    完成这些步骤后,我的绘图程序不再闪烁。

    对于遇到相同问题的其他人,问题终于解决了。

    享受吧!

    【讨论】:

    • 但它不再出现在设计器中。无论如何要修复它?
    • @WingerSendon - 为避免设计器问题,请使用“代码”解决方案,例如 viper's,在运行时设置双缓冲。
    【解决方案6】:

    对于“更清洁的解决方案”并为了继续使用基本面板,您可以简单地使用反射来实现双缓冲,方法是将此代码添加到包含您要在其中绘制的面板的表单中

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

    其中“DrawingPanel”是您要进行双缓冲的面板的名称。

    我知道自从提出问题以来已经过去了很长时间,但这可能会对将来的某些人有所帮助。

    【讨论】:

    • 这是一个很好的解决方案!实现起来非常简单,并且完全按照需要工作
    • @musefan 我很高兴为您提供帮助 :D 我很高兴比原始问题晚 2 年发布的解决方案仍然能够帮助人们!这就是为什么堆栈溢出这么好! :)
    • 它对我来说仍然闪烁。我在哪里添加此代码? PS。在这个答案中应该说“你需要在顶部使用'using System.Reflection'”。
    • 在开始使用之前,您必须为要停止闪烁的面板调用它。您可以在主窗体(或将具有面板的窗体)的 Load 事件中调用它。你在哪里叫它?
    • @RotaryHeart 请记住,您不应该在绘制方法中使用它,因为它会在每次重新绘制控件时运行,并且反射很昂贵。您只需为每个控件设置一次。我不确定这是否是您所做的,从阅读您的评论来看,这似乎是您的解决方案。
    【解决方案7】:

    我知道这确实是个老问题,但也许有人会觉得它很有用。
    我想对 viper 的回答做一点改进。

    您可以对 Panel 类进行简单的扩展,并通过反射隐藏设置属性。

    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 });
        }
    }
    

    如果您的 Panel 变量的名称是 myPanel,您可以调用
    myPanel.SetDoubleBuffered();
    就是这样。代码看起来更干净。

    【讨论】:

      【解决方案8】:

      复制并粘贴到您的项目中

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

      这将为表单级别以下的所有控件启用双缓冲,否则需要为每个控件单独启用双缓冲...在此之后您可能需要微调双缓冲,因为覆盖双缓冲可能会产生不必要的副作用.

      【讨论】:

      • 这是一种真正有效的解决方案。
      【解决方案9】:

      在这种情况下,您必须启用双缓冲。 打开当前表单并转到表单属性并应用双缓冲区 true; 或者您也可以编写此代码。

      this.DoubleBuffered = true;     
      

      在表单加载中。

      【讨论】:

        【解决方案10】:

        如果内存很紧(所以您不希望双缓冲的内存成本),一种可能的方法是减少(虽然不能消除)闪烁,但将背景颜色设置为当前场景中的主要颜色。

        为什么会有帮助:闪烁是背景颜色的瞬间闪烁,操作系统会在绘制子控件或您的自定义绘图代码之前绘制它。如果该闪光是一种更接近最终要显示的颜色的颜色,它就不会那么明显了。

        如果您不确定从什么颜色开始,请从 50% 的灰色开始,因为这是黑白的平均值,因此会更接近您场景中的大多数颜色。

        myFormOrControl.BackColor = Color.Gray;
        

        【讨论】:

          【解决方案11】:

          尝试在当前表单中插入绘图逻辑

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

          方法。在这种情况下,您应该使用参数 e 来获取 Graphics 对象。使用 e.Graphics 属性。然后,每当必须重绘表单时,您都应该为此表单调用 Invalidate() 方法。 PS:DoubleBuffered 必须设置为 true。

          【讨论】:

            【解决方案12】:

            这是.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();
                    }
                }
            }
            

            【讨论】:

              【解决方案13】:

              如果以上所有方法都不起作用,您可以随时创建自己的双缓冲区 微软教程链接:https://docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-reduce-graphics-flicker-with-double-buffering-for-forms-and-controls

              希望对你有用

              【讨论】:

                【解决方案14】:

                当显示表格时,只需执行this.Refresh()

                【讨论】:

                • 请问?这没有回答问题。
                【解决方案15】:

                这是一个有点老的问题,但只是为了完整:还有另一种解决方案,它对我有用,而双缓冲却没有。

                事实证明,Microsoft 提供了 BufferedGraphics 类作为解决方案。这个类的好处是它使您能够将一个 Graphics 对象复制到另一个,因此除了设置一个临时 Graphics 对象并最终将其复制到最终目的地之外,人们可以使用几乎相同的代码闪烁应该不是问题:

                private void Indicator_Paint(object sender, PaintEventArgs e)
                {
                    Control pbIndicator = (Control)sender;
                    Rectangle targetRect = pbIndicator.ClientRectangle;
                
                    Image img = Bitmap.FromFile("bitmap.bmp");
                
                    BufferedGraphicsContext ctx = new BufferedGraphicsContext();
                    BufferedGraphics bg = ctx.Allocate(e.Graphics, targetRect);
                
                    // Do the graphic stuff 
                    bg.Graphics.Clear(this.BackColor);
                    bg.Graphics.DrawImage(img, 0, 0);
                    // etcetera
                
                    bg.Render(e.Graphics);
                    bg.Dispose();
                    ctx.Dispose();
                }
                

                此解决方案的缺点是它可能会使您的代码混乱。此外,我不确定每次设置上下文是否是一个好主意,或者提前创建一个并继续使用该上下文是否足够。

                欲了解更多信息,请参阅https://docs.microsoft.com/en-us/dotnet/api/system.drawing.bufferedgraphicscontext?view=dotnet-plat-ext-3.1

                【讨论】:

                  【解决方案16】:

                  在标签而不是面板上绘图,为我解决了这个问题。

                  不需要使用双缓冲或任何东西。

                  您可以从标签中删除文本,将 AutoSize 设置为 false,然后将其停靠或设置大小并将其用作面板。

                  祝你好运,

                  【讨论】:

                  • 注意:从 PaintEventArgs 对 Graphics 对象调用 Dispose() 后引发 ArgumentException。当 Label 是父控件中的第一个(最高顺序)控件时,就会发生这种情况。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-12-29
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-12-23
                  • 1970-01-01
                  • 2015-03-24
                  相关资源
                  最近更新 更多