C#绘图双缓冲技术总结由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“c”。
C#绘图双缓冲技术总结 GDI+的双缓冲问题
一直以来的误区:.net1.1 和.net 2.0 在处理控件双缓冲上是有区别的。.net 1.1 中,使用:this.SetStyle(ControlStyles.DoubleBuffer, true);.net 2.0中,使用:this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);VS2005 是2.0 怪不说老是提示参数无效,一直也不知道是这个问题,呵呵
要知道,图元无闪烁的实现和图元的绘制方法没有多少关系,只是绘制方法可以控制图元的刷新区域,使双缓冲性能更优!
导致画面闪烁的关键原因分析:
一、绘制窗口由于大小位臵状态改变进行重绘操作时
绘图窗口内容或大小每改变一次,都要调用Paint事件进行重绘操作,该操作会使画面重新刷新一次以维持窗口正常显示。刷新过程中会导致所有图元重新绘制,而各个图元的重绘操作并不会导致Paint事件发生,因此窗口的每一次刷新只会调用Paint事件一次。窗口刷新一次的过程中,每一个图元的重绘都会立即显示到窗口,因此整个窗口中,只要是图元所在的位臵,都在刷新,而刷新的时间是有差别的,闪烁现象自然会出现。
所以说,此时导致窗口闪烁现象的关键因素并不在于Paint事件调用的次数多少,而在于各个图元的重绘。根据以上分析可知,当图元数目不多时,窗口刷新的位臵也不多,窗口闪烁效果并不严重;当图元数目较多时,绘图窗口进行重绘的图元数量增加,绘图窗口每一次刷新都会导致较多的图元重新绘制,窗口的较多位臵都在刷新,闪烁现象自然就会越来越严重。特别是图元比较大绘制时间比较长时,闪烁问题会更加严重,因为时间延迟会更长。
解决上述问题的关键在于:窗口刷新一次的过程中,让所有图元同时显示到窗口。
二、进行鼠标跟踪绘制操作或者对图元进行变形操作时
当进行鼠标跟踪绘制操作或者对图元进行变形操作时,Paint事件会频繁发生,这会使窗口的刷新次数大大增加。虽然窗口刷新一次的过程中所有图元同时显示到窗口,但也会有时间延迟,因为此时窗口刷新的时间间隔远小于图元每一次显示到窗口所用的时间。因此闪烁现象并不能完全消除!
所以说,此时导致窗口闪烁现象的关键因素在于Paint事件发生的次数多少。解决此问题的关键在于:设臵窗体或控件的几个关键属性。
下面来介绍解决办法的具体细节:
解决双缓冲的关键技术:
1、设臵显示图元控件的几个属性: 必须要设臵,否则效果不是很明显!this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.AllPaintingInWmPaint, true);
2、窗口刷新一次的过程中,让所有图元同时显示到窗口。
可以通过以下几种方式实现,这几种方式都涉及到Graphics对象的创建方式。
Graphics对象的创建方式:
a、在内存上创建一块和显示控件相同大小的画布,在这块画布上创建Graphics对象。
接着所有的图元都在这块画布上绘制,绘制完成以后再使用该画布覆盖显示控件的背景,从而达到“显示一次仅刷新一次”的效果!
实现代码(在OnPaint方法中): Rectangle rect = e.ClipRectangle;Bitmap bufferimage = new Bitmap(this.Width, this.Height);Graphics g = Graphics.FromImage(bufferimage);g.Clear(this.BackColor);g.SmoothingMode = SmoothingMode.HighQuality;//高质量 g.PixelOffsetMode = PixelOffsetMode.HighQuality;//高像素偏移质量 foreach(IShape drawobject in doc.drawObjectList){ if(rect.IntersectsWith(drawobject.Rect)){ drawobject.Draw(g);if(drawobject.TrackerState == config.Module.Core.TrackerState.Selected
&& this.CurrentOperator == Enum.Operator.Transfrom)//仅当编辑节点操作时显示图元热点
{ drawobject.DrawTracker(g);} } } using(Graphics tg = e.Graphics){ tg.DrawImage(bufferimage, 0, 0);//把画布贴到画面上 }
b、直接在内存上创建Graphics对象: Rectangle rect = e.ClipRectangle;BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current;BufferedGraphics myBuffer = currentContext.Allocate(e.Graphics, e.ClipRectangle);Graphics g = myBuffer.Graphics;g.SmoothingMode = SmoothingMode.HighQuality;g.PixelOffsetMode = PixelOffsetMode.HighSpeed;g.Clear(this.BackColor);foreach(IShape drawobject in doc.drawObjectList){ if(rect.IntersectsWith(drawobject.Rect)){ drawobject.Draw(g);if(drawobject.TrackerState == config.Module.Core.TrackerState.Selected && this.CurrentOperator == Enum.Operator.Transfrom)//仅当编辑节点操作时显示图元热点
{ drawobject.DrawTracker(g);} } } myBuffer.Render(e.Graphics);g.Dispose();myBuffer.Dispose();//释放资源
至此,双缓冲问题解决,两种方式的实现效果都一样,但最后一种方式的占有的内存很少,不会出现内存泄露!
手工设臵双缓冲.netframework提供了一个类BufferedGraphicsContext负责单独分配和管理图形缓冲区。每个应用程序域都有自己的默认 BufferedGraphicsContext 实例来管理此应用程序的所有默认双缓冲。大多数情况下,每个应用程序只有一个应用程序域,所以每个应用程序通常只有一个默认
BufferedGraphicsContext。默认 BufferedGraphicsContext 实例由 BufferedGraphicsManager 类管理。通过管理BufferedGraphicsContext实现双缓冲的步骤如下:
(1)获得对 BufferedGraphicsContext 类的实例的引用。(2)通过调用 BufferedGraphicsContext.Allocate 方法创建 BufferedGraphics 类的实例。
(3)通过设臵 BufferedGraphics.Graphics 属性将图形绘制到图形缓冲区。(4)当完成所有图形缓冲区中的绘制操作时,可调用
BufferedGraphics.Render 方法将缓冲区的内容呈现到与该缓冲区关联的绘图图面或者指定的绘图图面。
(5)完成呈现图形之后,对 BufferedGraphics 实例调用释放系统资源的 Dispose 方法。dataGridView 闪烁 和 listview 闪烁 的解决办法。
-----------------------DataGridView
/// ///双缓冲DataGridView,解决闪烁 /// cla DoubleBufferDataGridView : DataGridView { public DoubleBuffeDataGridView(){ SetStyle(ControlStyles.DoubleBuffer | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);UpdateStyles();} } 然后在DataGridView所在窗体的InitializeComponent方法中,更改控件类型实例化语句为:
//this.dataGridView1 = new System.Windows.Forms.DataGridView();(屏蔽掉)this.dataGridView1 = new DoubleBufferDataGridView();-----------------------ListView------/// ///双缓冲ListView,解决闪烁 /// cla DoubleBufferListView : ListView { public DoubleBufferListView(){ SetStyle(ControlStyles.DoubleBuffer | ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);UpdateStyles();} } 然后在ListView 所在窗体的InitializeComponent方法中,更改控件类型实例化语句为:
//this.listView1 = new System.Windows.Forms.ListView();(屏蔽掉)this.listView1 = new DoubleBufferListView();
foreach
foreach 语句为数组或对象集合中的每个元素重复一个嵌入语句组。foreach 语句用于循环访问集合以获取所需信息,但不应用于更改集合内容以避免产生不可预知的副作用。此语句的形式如下:
foreach(type identifier in expreion)statement
其中:
type
identifier 的类型。
identifier
表示集合元素的迭代变量。如果迭代变量为值类型,则无法修改的只读变量也是有效的。
expreion
对象集合或数组表达式。集合元素的类型必须可以转换为 identifier 类型。请不要使用计算为 null 的表达式。
而应计算为实现 IEnumerable 的类型或声明 GetEnumerator 方法的类型。在后一种情况中,GetEnumerator 应该要么返回实现 IEnumerator 的类型,要么声明 IEnumerator 中定义的所有方法。
statement
要执行的嵌入语句。
事例:
int[] arr = new int[] { 0, 1, 2, 3, 4 };
foreach(int i in arr)
{
Console.Write(i);
}
C#中foreach语法
int[] arr={1,2,3};
foreach(int i in arr)
{
System.Console.WriteLine(i);
}
php中foreach语法
$arr=array(1,2,3,4,'a','b',“c”);
1.foreach($arr as $v)
{
echo $v.“ ”;
}
2.foreach($arr2 as $k=>$v)
{
echo “[$k]=>$v”;
}
java中foreach语法
格式:
for(元素类型type 元素变量x : 遍历对象obj)
{
引用了x的java语句;
}
Example:
public cla Test {
public static void main(String[] args)
{
int[] a = {1,2,3};
for(int i : a)
System.out.print(i + “ ”);
}
}