我有一个基本上像绘画应用程序的程序。但是,我的程序有一些闪烁的问题。我的代码中有以下行(它应该摆脱闪烁 - 但不是):
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();
}
}
}
如何阻止闪烁?
* 更新: *当我直接在表单上绘图时,此代码实际上很有效。但是,当我尝试在面板上绘图时,闪烁成为一个问题
答案 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;
闪烁的主要问题是确保你
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();
}
此解决方案的缺点是它可能会使您的代码混乱。此外,我不确定每次设置上下文是否是一个好主意,还是预先创建一个并继续使用该上下文是否足够?
答案 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,它不会闪烁。反之亦然,请按照自己的时间进行尝试。