如何停止闪烁的C#Winforms


76

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

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


    }
}

如何停止闪烁?

*更新: *当我直接在窗体上绘制时,此代码实际上非常有用。但是,当我尝试在面板上绘画时,闪烁成为一个问题


7
你也准备好了吗this.DoubleBuffered = true;
Marc Gravell

1
@ Marc Gravell我刚刚尝试添加this.DoubleBuffered = true; 而且它仍然像疯了似的闪烁:S
BigBug

panelArea是否充满控件?Invalidate以递归方式进行工作,因此可能会踢入panelArea中的每个子控件以重新绘制自身
Polity

@政客,panelArea只是我用来绘制的面板。它没有控制,但..
BigBug

1
+1,坦率地说,SetStyle函数帮助我清除了闪烁。C#比C ++更好!
Neophile

Answers:


69

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

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

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

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


3
这是一个很好的解决方案!实施非常简单,并且完全可以
按需

5
@musefan我很高兴为您提供帮助:DI很高兴在比原始问题晚2年后发布的解决方案仍然能够为人们提供帮助!这就是为什么堆栈溢出是如此之好!:)
viper

1
它仍然为我闪烁。我在哪里添加此代码?PS。在这个答案中应该说“您需要在顶部使用'System.Reflection'”。
约翰·克特吉克

2
在开始使用之前,必须为要停止闪烁的面板调用此命令。您可以在主窗体(或将带有面板的窗体)的Load事件中调用它。你在哪叫
毒蛇

3
@RotaryHeart请记住,您不应在paint方法中使用此方法,因为它将在每次重绘控件时运行,并且反射很昂贵。您只需为每个控件设置一次即可。我不确定这是否就是您所做的,从阅读您的评论看来,这似乎是您的解决方案。
毒蛇

64

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

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

SetStyle必须为“ YourProject.YourProject”类型(或从其派生),因此,您必须像这样创建一个类(以便可以使用将从SPaint.SPaint派生的MyPanel,从而允许直接将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);
        }
    }
}

完成此操作之后(尽管除非您真正知道自己在做什么,否则实际上永远不要编辑设计器代码),则必须编辑Form.Designer.cs。在此文件中,您将找到如下代码:

this.panelArea = new YourProject.MyPanel();

上一行需要更改为:

this.panelArea = new MyPanel(); 

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

对于具有相同问题的其他任何人,该问题最终得以解决。

请享用!


但是它不再出现在设计器中。反正要解决吗?
温格·森顿

@WingerSendon-为避免设计者问题,请使用“代码”解决方案,例如viper's,该解决方案在运行时设置双缓冲。
ToolmakerSteve

42

复制并粘贴到您的项目中

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

这样就可以从窗体级别开始对所有控件进行双重缓冲,否则需要为每个控件单独启用双重缓冲...在此之后,您可能需要微调双重缓冲,因为覆盖双重缓冲可能会带来不良的副作用。


8
为了使您的答案更有用,请考虑添加简短说明,说明为什么一段特定的代码可能有助于回答该问题。
克里斯(Kris)2014年

2
看起来这可能是来源,也已发布在SO上...给出此工作原理的基本解释是,因为它启用了从窗体级别开始的所有控件的双缓冲,否则需要为每一个...在此之后,您可能需要微调双缓冲,因为覆盖式双缓冲可能会产生有害的副作用
u8it

为什么这样做有效,而不是接受的答案?
aswzen

@伊凡10岁以上?哇:D
Momoro

16

我曾经也有过一样的问题。我从未能够100%摆脱闪烁(请参阅第2点),但是我使用了

protected override void OnPaint(PaintEventArgs e) {}

以及

this.DoubleBuffered = true;

闪烁的主要问题是确保您

  1. 油漆正确的顺序!
  2. 确保您的绘图功能小于大约1/60秒

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;
Burimi

我建议创建一个面板类的子对象,从该面板继承,然后覆盖该子protected override void OnPaint(PaintEventArgs e) {}对象的。新对象将显示在您的项目列表中,以放入表单中。如果您已经创建了面板并且没有简单的重新创建面板的选项,那么您也可以手动编辑代码以更改引用类型。
ohmusama 2011年

e.Graphics与一起使用帮助this.DoubleBuffered = true。通过使用created Graphics,双缓冲区不仅可以增加闪烁效果,而且在大多数情况下都可以使Control(或Form)白色。
喵猫2012年

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

3

我建议您重写OnPaintBackground并处理背景以自己擦除。如果您知道要绘制整个控件,则可以在OnPaintBackground中什么都不做(不要调用base方法),这样可以防止先绘制背景色


1
重新阅读您的问题后,这可能无济于事...除了建议子类化面板并允许您覆盖OnPaint和OnPaintBackground,然后在此处进行绘制...
Matt

3

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

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


3

在这种情况下,您必须启用double buffer。打开当前表单并进入表单属性并应用true的double buffer;或者您也可以编写此代码。

this.DoubleBuffered = true;     

形式加载。


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


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/zh-cn/dotnet/api/system.drawing.bufferedgraphicscontext?view=dotnet-plat-ext-3.1


0

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

为何有用:闪烁是背景颜色的瞬时闪烁,操作系统在绘制子控件或自定义绘制代码之前先进行绘制。如果该闪光灯的颜色更接近于要显示的最终颜色,则它将不那么明显。

如果不确定要使用哪种颜色,请以50%的灰度开始,因为这是黑白的平均值,因此会更接近场景中的大多数颜色。

myFormOrControl.BackColor = Color.Gray;

0

尝试将绘图逻辑插入当前表单的

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

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


0

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


对不起?这不能回答问题。
莫莫罗

0

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

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

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

最好的祝愿,


注意:从PaintEventArgs中对Graphics对象调用Dispose()之后,引发ArgumentException。当Label是父级中的第一个(最高顺序)控件时,会发生这种情况。
ThrowNewRandomException

-2

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

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


1
当绘制窗体或控件的背景颜色(通常为白色),然后运行您的自定义代码以重绘受影响的区域时,就会发生闪烁。“闪烁”是背景色的瞬时闪烁。解决闪烁需要双重缓冲,以便系统具有一个可以从中获取所需内容的位置,而不是绘制背景色。将绘图代码放在计时器上比什么都不做更糟(延长cpu周期无济于事)。依靠操作系统告诉您何时需要绘制,不要试图超越它。
制造商
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.