简体   繁体   English

如何停止闪烁的 C# winforms

[英]how to stop flickering C# winforms

I have a program that is essentially like a paint application.我有一个本质上类似于油漆应用程序的程序。 However, my program has some flickering issues.但是,我的程序有一些闪烁的问题。 I have the following line in my code (which should get rid of flickering - but doesn't):我的代码中有以下行(应该消除闪烁 - 但没有):

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

my code(minus the super and sub classes for the shapes is as follows:我的代码(减去形状的超类和子类如下:

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


    }
}

How do i stop the flickering?如何停止闪烁?

* UPDATE: *This code actually works great when i'm drawing directly on the form. *更新: *当我直接在表单上绘图时,此代码实际上非常有效。 However, when i try to draw on the panel, flickering becomes an issue但是,当我尝试在面板上绘图时,闪烁成为一个问题

For a "cleaner solution" and in order to keep using the base Panel, you could simply use Reflection to implement the double buffering, by adding this code to the form that holds the panels in which you want to draw in对于“更清洁的解决方案”并为了继续使用基本面板,您可以简单地使用反射来实现双缓冲,方法是将此代码添加到包含要在其中绘制的面板的表单中

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

Where "DrawingPanel" is the name of the panel that you want to do the double buffering.其中“DrawingPanel”是要进行双缓冲的面板的名称。

I know quite a lot of time has passed since the question was asked, but this might help somebody in the future.我知道自从提出这个问题以来已经过去了很多时间,但这可能会对将来的某人有所帮助。

Finally solved the flickering.终于解决了闪烁。 Since I was drawing on a panel instead of the form the line of code below will not solve the flickering:由于我是在面板上而不是在表单上绘图,因此下面的代码行无法解决闪烁问题:

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

SetStyle must be of type 'YourProject.YourProject' (or derived from it) hence, you have to create a class as such (so that you can use MyPanel which will be derived from SPaint.SPaint and hence allowing you to use doublebuffering directly for the panel - rather than the form): 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);
        }
    }
}

After you've done this(although you should really never edit the designer code unless you truly know what you're doing) you'll have to edit the Form.Designer.cs.完成此操作后(尽管您真的不应该编辑设计器代码,除非您真正知道自己在做什么),您必须编辑 Form.Designer.cs。 Inside this file you will find code that looks like this:在此文件中,您将找到如下所示的代码:

this.panelArea = new YourProject.MyPanel();

The above line needs to be changed to:上面这行需要改成:

this.panelArea = new MyPanel(); 

After I completed these steps, my paint program no longer flickers.完成这些步骤后,我的绘画程序不再闪烁。

For anyone else having the same issue, the problem is finally solved.对于有同样问题的其他人,问题终于解决了。

Enjoy!享受!

Copy and paste this into your project将其复制并粘贴到您的项目中

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

This enables double-buffering for all controls from the form level down, otherwise double buffering needs to be individually enabled for each one... you may want to fine tune double bufferring after this because blanketed double buffering may give unwanted side effects.这为表单级别以下的所有控件启用双缓冲,否则需要为每个控件单独启用双缓冲......您可能需要在此之后微调双缓冲,因为覆盖双缓冲可能会产生不需要的副作用。

I have had the same problem.我曾经也有过一样的问题。 I was never able to 100% rid myself of the flicker (see point 2), but I used this我从来没有能够 100% 摆脱闪烁(见第 2 点),但我使用了这个

protected override void OnPaint(PaintEventArgs e) {}

as well as

this.DoubleBuffered = true;

The main issue for flickering is making sure you闪烁的主要问题是确保您

  1. paint things it the right order!按正确的顺序画东西!
  2. make sure your draw function is < about 1/60th of a second确保您的绘图功能 < 约 1/60 秒

winforms invokes the OnPaint method each time the form needs to be redrawn.每次需要重绘窗体时,winforms 都会调用OnPaint方法。 There are many ways it can be devalidated, including moving a mouse cursor over the form can sometimes invoke a redraw event.有很多方法可以使它失效,包括将鼠标光标移动到表单上有时可以调用重绘事件。

And important note about OnPaint , is you don't start from scratch each time, you instead start from where you were, if you flood fill the background color, you are likely going to get flickering.关于OnPaint重要说明是,你不是每次都从头开始,而是从你所在的地方开始,如果你填充背景颜色,你可能会变得闪烁。

Finally your gfx object.最后你的 gfx 对象。 Inside OnPaint you will need to recreate the graphics object, but ONLY if the screen size has changed.OnPaint您需要重新创建图形对象,但前提是屏幕尺寸已更改。 recreating the object is very expensive, and it needs to be disposed before it is recreated (garbage collection doesn't 100% handle it correctly or so says documentation).重新创建对象非常昂贵,并且需要在重新创建之前对其进行处理(垃圾收集不能 100% 正确处理它,或者文档如此说)。 I created a class variable我创建了一个类变量

protected Graphics gfx = null;

and then used it locally in OnPaint like so, but this was because I needed to use the gfx object in other locations in my class.然后像这样在OnPaint本地使用它,但这是因为我需要在我班级的其他位置使用 gfx 对象。 Otherwise DO NOT DO THIS.否则不要这样做。 If you are only painting in OnPaint, then please use e.Graphics !!如果您只是在 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();

Hope this helps.希望这可以帮助。

Double buffering is not going to be of much help here I'm afraid.恐怕双缓冲在这里不会有太大帮助。 I ran into this a while ago and ended up adding a separate panel in a rather clumsy way but it worked for my application.不久前我遇到了这个问题,最终以一种相当笨拙的方式添加了一个单独的面板,但它适用于我的应用程序。

Make the original panel that you have ( panelArea ) a transparent area, and put it on top of a 2nd panel, which you call panelDraw for example.使您拥有的原始面板 ( panelArea ) 成为透明区域,并将其放在第二个面板的顶部,例如,您将其称为 panelDraw。 Make sure to have panelArea in front.确保前面有 panelArea。 I whipped this up and it got rid of the flickering, but left the shape that was being drawn smeared out so it's not a full solution either.我把它搅起来,它消除了闪烁,但留下了被绘制的形状,所以它也不是一个完整的解决方案。

A transparent panel can be made by overriding some paint actions from the original panel:可以通过覆盖原始面板中的一些绘制操作来制作透明面板:

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

The idea is to handle drawing the temporary shape during the MouseMove event of the 'panelArea' and ONLY repaint the 'panelDraw' on the MouseUp Event.这个想法是在'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();
}

I know this is really old question but maybe someone will find it useful.我知道这是一个很老的问题,但也许有人会发现它很有用。
I'd like to make little enhancement to viper's answer.我想对毒蛇的回答做一点改进。

You can make simple extension to Panel class and hide setting property through reflection.您可以对 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 });
    }
}

If your Panel variable's name is myPanel you can just call如果您的 Panel 变量的名称是 myPanel,您只需调用
myPanel.SetDoubleBuffered(); myPanel.SetDoubleBuffered();
and that's it.就是这样。 Code looks much cleaner.代码看起来干净多了。

In this condition you have to enable double buffer .在这种情况下,您必须启用双缓冲区。 Open current form and go to form properties and apply double buffer true;打开当前表单并转到表单属性并应用双缓冲区true; or you can also write this code .或者您也可以编写此代码。

this.DoubleBuffered = true;     

In form load.在表单加载中。

I'd advise overriding OnPaintBackground and handling the background erase yourself.我建议覆盖 OnPaintBackground 并自己处理背景擦除。 If you know you are painting the whole control you can just do nothing in OnPaintBackground (don't call the base method) and it will prevent the background colour being painted first如果你知道你正在绘制整个控件,你可以在 OnPaintBackground 中什么都不做(不要调用基本方法),它会阻止首先绘制背景颜色

here is the program of moving circle in .net, that doesn't flicker.这是.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();
        }
    }
}

if all of the above doesn't work you can always create your own double buffer link to Microsofts tutorial: https://docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-reduce-graphics-flicker-with-double-buffering-for-forms-and-controls如果以上所有方法都不起作用,您可以随时创建自己的 Microsoft 教程的双缓冲链接: https : //docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-reduce-图形闪烁与双缓冲形式和控件

hopes it works for you希望它对你有用

It's a bit of and old question, but just to be complete: There is yet another solution, that worked for me where the double-buffering did not.这是一个有点老的问题,但只是为了完整:还有另一个解决方案,它对我有用,而双缓冲没有。

As it turns out Microsoft offers the BufferedGraphics class as a solution.事实证明,Microsoft 提供了BufferedGraphics类作为解决方案。 Nice thing about this class is that it enables you to copy one Graphics object to another, so except from setting up a temporary Graphics object and eventually copying it to the final destination, one can use pretty much the same code that one would have done when the flickering should not have been a problem:此类的好处在于它使您能够将一个 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();
}

Downside of this solution that it might clutter your code.此解决方案的缺点是它可能会使您的代码混乱。 Furthermore I'm not sure whether it is a good idea to setup the context each time, or whether it would suffice to create one in advance and keep using that context.此外,我不确定每次设置上下文是否是一个好主意,或者提前创建一个并继续使用该上下文是否就足够了。

For more information see https://docs.microsoft.com/en-us/dotnet/api/system.drawing.bufferedgraphicscontext?view=dotnet-plat-ext-3.1有关详细信息,请参阅https://docs.microsoft.com/en-us/dotnet/api/system.drawing.bufferedgraphicscontext?view=dotnet-plat-ext-3.1

If memory is tight (so you don't want the memory cost of double-buffering), one possible way to REDUCE, though not eliminate, flicker, is to set background color to the dominant color in your current scene.如果内存紧张(因此您不希望双缓冲的内存成本),减少(虽然不能消除)闪烁的一种可能方法是将背景颜色设置为当前场景中的主色。

Why this helps: flicker is a momentary flash of the background color, which the OS draws before drawing child controls or your custom drawing code.为什么这有帮助:闪烁是背景颜色的瞬间闪烁,操作系统在绘制子控件或您的自定义绘制代码之前绘制。 If that flash is a color that is closer to the final color to be displayed, it will be less noticeable.如果该闪光是一种更接近要显示的最终颜色的颜色,它将不太明显。

If you are not sure what color to start with, start with 50% gray, because this is an average of black and white, so will be closer to most colors in your scene.如果您不确定从什么颜色开始,请从 50% 的灰色开始,因为这是黑色和白色的平均值,因此会更接近场景中的大多数颜色。

myFormOrControl.BackColor = Color.Gray;

Try to insert drawing logic in current form's尝试在当前表单中插入绘图逻辑

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

method.方法。 In this case you should use parameter e to get Graphics object.在这种情况下,您应该使用参数 e 来获取 Graphics 对象。 Use e.Graphics property.使用 e.Graphics 属性。 Then you should invoke Invalidate() method for this form whenever form must be redrawn.然后,无论何时必须重绘表单,您都应该为此表单调用 Invalidate() 方法。 PS: DoubleBuffered must be set to true. PS:DoubleBuffered 必须设置为 true。

Drawing onto a Label instead of a Panel, solved the problem for me.绘制到标签而不是面板上,为我解决了这个问题。

No need to use DoubleBuffering or anything either.不需要使用 DoubleBuffering 或任何东西。

You can remove the text from the label, set AutoSize to false, then Dock it or set the Size and use it as for the Panel.您可以从标签中删除文本,将 AutoSize 设置为 false,然后将其停靠或设置大小并将其用作面板。

Best wishes,最好的祝愿,

显示表单时只需执行this.Refresh()

Can you try using a timer and boolean to check if mouse is down, and paint in that spot, using a variable again check if user has moved his mouse, if moved paint that spot too etc.您可以尝试使用计时器和布尔值来检查鼠标是否已按下,并在该位置绘画,再次使用变量检查用户是否移动了鼠标,是否也移动了该位置,等等。

Or just check if mouse down(via boolean that sets true when mouse is down) using a timer and paint it considering you are probably trying to just paint one pixel, not like you have shadow etc. Instead of using actual mousedown.或者只是使用计时器检查鼠标是否按下(通过在鼠标按下时设置为 true 的布尔值)并绘制它,考虑到您可能只是尝试绘制一个像素,而不是像您有阴影等。而不是使用实际的鼠标按下。 So you check every 1 second instead of 0.0001 and it wont flicker.所以你每 1 秒检查一次而不是 0.0001 并且它不会闪烁。 Or vice-versa, try it with your own times.反之亦然,根据自己的时间尝试一下。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM