如何停止闪烁的C#winforms
我有一个基本上像绘画应用程序的程序。 但是,我的程序有一些闪烁的问题。 我有我的代码中的以下行(应该摆脱闪烁 – 但不):
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 = eX; startPoint.Y = eY; drawSPaint = true; } private void panelArea_MouseMove(object sender, MouseEventArgs e) { if (e.Button == System.Windows.Forms.MouseButtons.Left) { if (eX > startPoint.X) { rect.X = startPoint.X; rect.Width = eX - startPoint.X; } else { rect.X = eX; rect.Width = startPoint.X - eX; } if (eY > startPoint.Y) { rect.Y = startPoint.Y; rect.Height = eY - startPoint.Y; } else { rect.Y = eY; rect.Height = startPoint.Y - eY; } panelArea.Invalidate(); } } private void panelArea_MouseUp(object sender, MouseEventArgs e) { endPoint.X = eX; endPoint.Y = eY; 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(); } } }
我该如何停止闪烁?
* 更新: *这个代码实际上很好,当我直接在窗体上绘制。 但是,当我尝试在面板上绘制时,闪烁成为一个问题
终于解决了闪烁。 由于我是在一个面板而不是forms下面的代码行不会解决闪烁:
this.SetStyle( ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
SetStyle的types必须是“YourProject.YourProject”(或派生自它),因此,您必须创build一个类,以便您可以使用MyPanel,它将从SPaint.SPaint派生,因此可以直接使用doublebuffering面板 – 而不是forms):
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); } } }
完成这个之后(尽pipe除非你真的知道你在做什么,但是你真的不应该编辑devise器代码),你将不得不编辑Form.Designer.cs。 在这个文件里面你会看到如下代码:
this.panelArea = new YourProject.MyPanel();
以上行需要更改为:
this.panelArea = new MyPanel();
完成这些步骤后,我的绘画程序不再闪烁。
对于有同样问题的任何人,问题终于解决了。
请享用!
对于“更干净的解决scheme”,为了继续使用基本面板,您可以简单地使用reflection来实现双缓冲,方法是将此代码添加到包含要在其中绘制的面板的窗体
typeof(Panel).InvokeMember("DoubleBuffered", BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic, null, DrawingPanel, new object[] { true });
其中“DrawingPanel”是您要执行双缓冲的面板的名称。
自问题提出以来,我相当了解了很多时间,但这对未来可能有帮助。
复制并粘贴到您的项目
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;
闪烁的主要问题是确保你
- 绘制正确的顺序!
- 确保你的绘图function<约1/60秒
每次窗体需要重绘时,winforms都会调用OnPaint
方法。 有很多方法可以失效,包括在表单上移动鼠标光标有时可以调用重绘事件。
关于OnPaint
重要注意事项是,你不是每次从头开始,而是从你所在的地方开始,如果你填充背景颜色,你很可能会闪烁。
最后你的gfx对象。 在OnPaint
您将需要重新创buildgraphics对象,但只有在屏幕尺寸已更改OnPaint
需要。 重新创build对象是非常昂贵的,它需要在重新创build之前进行处理(垃圾收集不能100%正确处理或者说文档)。 我创build了一个类variables
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)成为一个透明的区域,并把它放在第二个面板上,例如你调用panelDraw。 确保前面有panelArea。 我把它掀起来,它摆脱了闪烁,但留下了正在被绘制的形状,所以它也不是一个完整的解决scheme。
可以通过覆盖原始面板上的一些绘画动作来制作透明面板:
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 = eX; endPoint.Y = eY; 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(); }
我build议覆盖OnPaintBackground并处理背景擦除自己。 如果你知道你正在绘制整个控件,那么你可以在OnPaintBackground中不做任何事情(不要调用基本方法),它会阻止背景颜色被首先绘制
我知道这是一个很老的问题,但也许有人会觉得它有用。
我想对v蛇的答案做一点改进。
您可以对Panel类进行简单的扩展,并通过reflection来隐藏设置属性。
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 }); } }
如果你的Panelvariables的名字是myPanel,你可以直接调用
myPanel.SetDoubleBuffered();
就是这样。 代码看起来更干净。
在这种情况下,你必须启用双缓冲区。 打开当前表单并转到表单属性并应用双缓冲区true; 或者你也可以写这个代码。
this.DoubleBuffered = true;
在表单加载。
如果内存紧张(所以你不需要双缓冲的内存成本),一个可能的方法来减less,但不消除,闪烁,是设置背景颜色为当前场景中的主色。
为什么这有助于:闪烁是背景色的瞬间闪烁,操作系统在绘制子控件或自定义绘图代码之前绘制。 如果该闪光灯是接近要显示的最终颜色的颜色,则不太明显。
如果您不确定以什么颜色开始,以50%灰色开始,因为这是黑色和白色的平均值,所以会更接近场景中的大部分颜色。
myFormOrControl.BackColor = Color.Gray;
尝试在当前窗体中插入绘图逻辑
protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); }
方法。 在这种情况下,您应该使用参数e来获取Graphics对象。 使用e.Graphics属性。 然后你应该为这个表单调用Invalidate()方法,只要表单必须重绘。 PS: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(); } } }
如果上述全部不起作用,您可以随时创build自己的双缓冲区链接到微软教程: https : //docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-reduce-graphics闪烁与-双缓冲换forms和-控制
希望它适合你
你可以尝试使用计时器和布尔值来检查鼠标是否closures,并在该位置绘制,再次使用variables检查用户是否移动了鼠标,如果移动绘制该位置等。
或者只是使用计时器检查是否鼠标closures(通过布尔值设置为true,当鼠标closures时),并考虑到您可能正在尝试绘制一个像素,而不是像阴影等,而不是使用实际的mousedown。 所以你检查每1秒而不是0.0001,它不会闪烁。 或者反过来,用你自己的时间来尝试一下。