本文所述问题,是一个古老的问题,理论上应该满网都是解决方案,但本人在修改一个老掉牙程序时,为解决WinForm ListBox快速大量更新时的闪烁问题,竟然化了一个星期。本文是本人在将近一个星期各种查找和翻墙后,实现自绘制双缓冲ListBox控件的有效方法,写出来是为了方便其他倒霉蛋和酷比攻城狮、程序猿遇到同样问题时不用花这么长的时间,轻松一点。
一般控件实现双缓冲离线绘制画面的方法:
双缓冲的运用能够避免画面或控件在进行复制绘制,更新画面时,避免出现烦人的闪动现象。WinForm2.0之后,已经内置支持控件和Form的双缓冲支持,开启方法是,在控件的构造函数中,调用SetStyle函数,如下:
this.SetStyle(ControlStyles.UserPaint,true); // 开启自绘制,使OnPaint重载有效
this.SetStyle(ControlStyles.DoubleBuffer,true); // 双缓冲
this.SetStyle(ControlStyles.OptimizedDoubleBuffer,true); // 双缓冲
this.SetStyle(ControlStyles.ResizeRedraw,true); // 调整大小时重绘
this.SetStyle(ControlStyles.AllPaintingInWmPaint,true); // 禁止擦除背景.
this.SetStyle(ControlStyles.SupportsTransparentBackColor,true); // 开启控件透明
因为需要在构造函数中调用,而且需要重载OnPaint函数,所以,只能自己派生一个控件的子类。本程序中使用FlickerFreeListBox:ListBox, 该类拷贝自2008年一篇停更的技术文章(要翻墙),该文章中的方法一定程度上有效,但未使用系统自带的双缓冲区管理类,并且没有解决ListBox横向滚动的自绘制问题。本文将该类做了修改,可直接用于实际项目。
系统自带的双缓冲已经有内部实现,使用方法:
1、在OnPaint函数开始后,使用如下代码,得到离线缓冲区的Graphics对象:
var bg =BufferedGraphicsManager.Current.Allocate(e.Graphics, rect);
var g = bg.Graphics;
2、使用该Graphic对象进行离线绘制画面,
3、完成绘制后,使用如下代码将离线画面更新到屏幕上:
bg.Render();
bg.Dispose();
ListBox的问题:
ListBox中存在Item集合,并且还有DrawMode属性,可使用用户自定义函数自绘制单个的Item对象。问题出在以下方面,1、ListBox有横向和纵向的滚动条,这样可视区域固定,但其中的Item和横向偏移位置怎么确定。2、在重载的OnPaint函数中,除需要获取双缓冲区外,还需要确定哪些Item需要被绘制,Item的绘制区域在哪。其它问题相对好办。
问题1:ListBox的属性ScrollAlwaysVisible确定纵向滚动条是否显示,HorizontalExtent属性是横向滚动条的长度,当Item长度大于该值时,横向滚动条会显示出