如何停止闪烁C#winforms

时间:2011-11-08 06:03:20

标签: c# drawing

我有一个基本上像绘画应用程序的程序。但是,我的程序有一些闪烁的问题。我的代码中有以下行(它应该摆脱闪烁 - 但不是):

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


    }
}

如何阻止闪烁?

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

16 个答案:

答案 0 :(得分:55)

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

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

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

我知道问题被提出后已经过了很多时间,但这可能会对将来有所帮助。

答案 1 :(得分:52)

终于解决了闪烁问题。由于我在面板而不是表格上绘图,下面的代码行不能解决闪烁问题:

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

SetStyle必须是'YourProject.YourProject'类型(或从它派生),因此,你必须创建一个类(这样你就可以使用MyPanel,它将派生自SPaint.SPaint,因此允许你使用直接为面板双倍缓冲 - 而不是形式):

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

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

对于其他任何有同样问题的人来说,问题终于解决了。

享受!

答案 2 :(得分:26)

将其复制并粘贴到您的项目中

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

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

答案 3 :(得分:14)

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

protected override void OnPaint(PaintEventArgs e) {}

以及

this.DoubleBuffered = true;

闪烁的主要问题是确保你

  1. 用正确的顺序画它!
  2. 确保你的绘图功能是&lt;大约1/60秒
  3. 每次需要重绘表单时,

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

    希望这有帮助。

答案 4 :(得分:3)

我担心双缓冲在这里不会有太大帮助。我刚刚遇到这个问题,最后以一种相当笨拙的方式添加了一个单独的面板,但它适用于我的应用程序。

使您拥有的原始面板(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 :(得分:3)

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

您可以对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();
就是这样。代码看起来更清晰。

答案 6 :(得分:2)

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

答案 7 :(得分:2)

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

this.DoubleBuffered = true;     

表单加载。

答案 8 :(得分:1)

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

答案 9 :(得分:1)

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

希望它适合你

答案 10 :(得分:1)

这是一个古老的问题,但请完整说明:还有另一种解决方案,对我有用,而双重缓冲却没有。

事实证明,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

答案 11 :(得分:0)

如果内存紧张(所以你不想要双缓冲的内存成本),减少闪烁的一种可能方法是将背景颜色设置为当前场景中的主色

为什么这会有所帮助:闪烁是背景颜色的瞬间闪现,操作系统在绘制子控件或自定义绘图代码之前绘制。如果闪光灯的颜色更接近要显示的最终颜色,则不太明显。

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

myFormOrControl.BackColor = Color.Gray;

答案 12 :(得分:0)

尝试以当前形式插入绘图逻辑

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

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

答案 13 :(得分:0)

在显示表单时只需this.Refresh()即可。

答案 14 :(得分:0)

用标签代替面板来解决问题。

无需使用DoubleBuffering或任何其他方法。

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

最良好的祝愿

答案 15 :(得分:-2)

你能尝试使用计时器和布尔来检查鼠标是否已经关闭,并在该点上绘画,再次使用变量检查用户是否移动了他的鼠标,如果移动了该点也是等等。

或者只是检查鼠标是否向下(通过boolean在鼠标停止时设置为true)使用计时器并绘制它,考虑到你可能只想绘制一个像素,而不是像你有阴影等。而不是使用实际的mousedown。所以你每1秒检查一次,而不是0.0001,它不会闪烁。反之亦然,请按照自己的时间进行尝试。