c#在Winform下如何实现在picturebox中高频率绘制大量矩形不闪烁

ace_chn 2018-12-30 11:10:30
平台是winform,用的C#。
背景:实时(20毫秒刷新周期)获取在线检测进度,每1000为一个单位,循环刷新。合格品在进度条中显示绿色,不合格品在进度条中显示红色,当前检测单位(1000为一个单位)内的检测数量在进度条中显示棕色。
个人解决方案:我用了一个picturebox来绘制整个进度条,初始化时把picturebox分割成了1000个矩形。定义了一个容量为1000的数组,包括矩形的位置,以及绘制矩形的颜色。用多线程BackgroundWorker每20毫秒刷新获取检测数据,并在进度条中绘制检测结果。我在picturebox的Paint函数中用FillRectangle实现绘制,每次调用picturebox.refresh,就是绘制当前1000个矩形的颜色。
问题:1.绘制的指示进度的矩形会闪烁(我即使只绘制进度矩形,也会闪烁)。双缓冲也开了,无效。
2.如果多条生产线,生成多个进度条,占用系统资源较多。
请高手帮看看代码,或者指点其它途径,多谢!


public partial class Form1 : Form
{
public struct CheckProgress
{//检测进度
public System.Drawing.RectangleF[] DrawRect; // 矩形数组
public System.Drawing.Brush[] DrawBush;// 每个矩形对应的Brush
}
CheckProgress m_DrawCheckProgress;
int CheckProgressLength; //进度条总长度
int m_DrawCounter; //检测进度游标
public Form1()
{
InitializeComponent();
CheckProgressLength = 1000;
m_DrawCounter = 0;
}

Color[] DrawColor = new Color[3] { Color.Red, Color.Green, Color.Yellow };
int rand()
{// 调试用随机数, 用于随机生成大约2%的不合格品
Random r = new Random();
if (r.Next(100) < 97)
{
return 1;
}
else return 0;
}


private void Form1_Load(object sender, EventArgs e)
{
// Initialize Struct CheckProgressBar
m_DrawCheckProgress = new CheckProgress();
m_DrawCheckProgress.DrawRect = new System.Drawing.RectangleF[CheckProgressLength];
m_DrawCheckProgress.DrawBush = new System.Drawing.Brush[CheckProgressLength];

//将Picturebox分割成1000个小矩形
for (int i = 0; i < m_DrawCheckProgress.DrawRect.Length; i++)
{
m_DrawCheckProgress.DrawRect[i] = new RectangleF(PicMainProgress.Width * i * 1.0f / CheckProgressLength, 0, PicMainProgress.Width * 1.0f / CheckProgressLength, PicMainProgress.Height);
m_DrawCheckProgress.DrawBush[i] = new SolidBrush(PicMainProgress.BackColor);
}

//Start Thread
if (false == Bgw_CheckResult.IsBusy)
{
Bgw_CheckResult.RunWorkerAsync();
}
}
//
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Random random = new Random(); //调试用随机数, 用于随机生成大约2%的不合格品
while (false == Bgw_CheckResult.CancellationPending)
{//调试用
Color c;
int rd = rand();
if (1 == rd)
{
c = Color.Green;
}
else
{
c = Color.Red;
}
m_DrawCheckProgress.DrawBush[m_DrawCounter % CheckProgressLength] = new SolidBrush(c);

// 设置游标颜色(占据2个矩形)
int n = 2;
for (int i = 0; i < n; i++)
{//防止溢出
if (m_DrawCounter % CheckProgressLength + n < CheckProgressLength)
{
m_DrawCheckProgress.DrawBush[m_DrawCounter % CheckProgressLength + (i+1)] = new SolidBrush(Color.Blue);
}
}
//
Bgw_CheckResult.ReportProgress(1);
m_DrawCounter++;
Thread.Sleep(20);
}
}
//
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Refresh Picturebox
PicMainProgress.Refresh();
}
//进度条绘制函数
private void PicMainProgress_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(SystemColors.Control);
for (int i = 0; i < m_DrawCheckProgress.DrawRect.Length; i++)
{
e.Graphics.FillRectangle(m_DrawCheckProgress.DrawBush[i], m_DrawCheckProgress.DrawRect[i]);
}
e.Dispose();
}

}
...全文
1154 27 打赏 收藏 转发到动态 举报
写回复
用AI写文章
27 条回复
切换为时间正序
请发表友善的回复…
发表回复
土豆赛叩 2019-01-06
  • 打赏
  • 举报
回复
自己实现双缓冲 新建一张缓存 后台往缓存上渲染 Print 只负责把缓存的图渲染出来即可
Fanstorm丶 2019-01-02
  • 打赏
  • 举报
回复
我用#CSDN#这个app发现了有技术含量的博客,小伙伴们求同去《C#.net picturebox动画效果,刷新图像的闪烁问题解决方法》, 一起来围观吧 https://blog.csdn.net/wf824284257/article/details/53888611
Fanstorm丶 2019-01-02
  • 打赏
  • 举报
回复
我用#CSDN#这个app发现了有技术含量的博客,小伙伴们求同去《C#.net picturebox动画效果,刷新图像的闪烁问题解决方法》, 一起来围观吧 https://blog.csdn.net/wf824284257/article/details/53888611
一彤 2019-01-02
  • 打赏
  • 举报
回复
调参,试参,也是一项技能
xuzuning 2019-01-01
  • 打赏
  • 举报
回复
你绘制的矩形
左边界 PicMainProgress.Width * i * 1.0f / CheckProgressLength
宽度为 PicMainProgress.Width * 1.0f / CheckProgressLength
那么绘制时因舍入的原因,会多绘(或少绘)一个点
至少应保证 PicMainProgress.Width * 1.0f / CheckProgressLength 不小于 0.5f,才不会丢失格线
ace_chn 2019-01-01
  • 打赏
  • 举报
回复
引用 14 楼 xuzuning 的回复:
步进由 1.0f 改为 0.5f
步进似乎无法指定,因为界面设计控件长度固定,检测单位也是1000。所以单个单位长度是相对固定的……
xuzuning 2019-01-01
  • 打赏
  • 举报
回复
步进由 1.0f 改为 0.5f
ace_chn 2019-01-01
  • 打赏
  • 举报
回复
引用 11 楼 xuzuning 的回复:
部分格线不显示的原因是你指定的分辨率有问题(1.0f),GDI+可以显示0.5f的点(你可以用放大镜去观察屏幕)
我不知道你是如何理解闪烁的,在纯色背景上突然出现其他色块(你用的是随机数据),并不是闪烁,只是跳跃,消除跳跃感需要渐入渐出(淡入淡出)的动画处理
纠正一下,我说的闪烁,不是整个绘图背景都在闪,是单指游标本身。就像你提到的,因为有时不显示,所以可能给人的感觉就是在闪烁。 如果我加大游标宽度为两格,那绘制时不会消失,但游标宽度一会一格一会二格,看起来依然有闪烁感。
ace_chn 2019-01-01
  • 打赏
  • 举报
回复
嗯,我重新调试了一下,不再随机色。简化要求,就是红色游标向前移动(仅仅是游标本身)。设置成1000毫秒刷新周期,发现闪烁的原因可能就是因为游标有规律的每3格就会灭一次。你回复中提到指定分辨率1.0f可能有问题,我没有指定,那句代码只是变成float型数据。 请问我怎么解决“部分格线有规律的不显示”
xuzuning 2019-01-01
  • 打赏
  • 举报
回复
部分格线不显示的原因是你指定的分辨率有问题(1.0f),GDI+可以显示0.5f的点(你可以用放大镜去观察屏幕)
我不知道你是如何理解闪烁的,在纯色背景上突然出现其他色块(你用的是随机数据),并不是闪烁,只是跳跃,消除跳跃感需要渐入渐出(淡入淡出)的动画处理
ace_chn 2019-01-01
  • 打赏
  • 举报
回复
引用 26 楼 xuzuning 的回复:
i=1 0.8 : 1
i=2 1.6 : 1.5
i=3 2.4 : 2
i=4 3.2 : 3
....
似乎也没什么影响
不过后面就重复了,我再琢磨琢磨吧。多谢了,版主
xuzuning 2019-01-01
  • 打赏
  • 举报
回复
i=1 0.8 : 1
i=2 1.6 : 1.5
i=3 2.4 : 2
i=4 3.2 : 3
....
似乎也没什么影响

ace_chn 2019-01-01
  • 打赏
  • 举报
回复
引用 21 楼 xuzuning 的回复:
i=1 0.8
i=2 1.6
i=3 2.4
i=4 3.2
....
让 dgi+ 舍入就是 1、1、2、3
不满意的话就自己舍入(规则自定)
结贴后还可以发言么?
ace_chn 2019-01-01
  • 打赏
  • 举报
回复
明白。不过我这有点麻烦,因为一道线代表一个产品质量(合格是绿色不合格红色),如果四舍五入的话就会造成数据显示不准确,真是头疼。另,结贴后还能发言么?
xuzuning 2019-01-01
  • 打赏
  • 举报
回复
i=1 0.8
i=2 1.6
i=3 2.4
i=4 3.2
....
让 dgi+ 舍入就是 1、1、2、3
不满意的话就自己舍入(规则自定)
ace_chn 2019-01-01
  • 打赏
  • 举报
回复
多谢版主,问题已经基本解决。确实是步进间隔的问题,我调成了0.5倍数就不会再闪烁了。 那面临的问题是,如果控件固定就是800长度,那步进就是0.8,怎么解决呢?
ace_chn 2019-01-01
  • 打赏
  • 举报
回复
引用 16 楼 xuzuning 的回复:
你绘制的矩形
左边界 PicMainProgress.Width * i * 1.0f / CheckProgressLength
宽度为 PicMainProgress.Width * 1.0f / CheckProgressLength
那么绘制时因舍入的原因,会多绘(或少绘)一个点
至少应保证 PicMainProgress.Width * 1.0f / CheckProgressLength 不小于 0.5f,才不会丢失格线
明白,单根线的宽度都大于0.5,在初始化的时候已经设定。控件长度800,所以单根线宽0.8。
ace_chn 2018-12-31
  • 打赏
  • 举报
回复
引用 7 楼 xuzuning 的回复:
用 Invalidate 而不是 Refresh
虽然两者都通过 Paint 实现绘图,但 Refresh 可能导致事件链混乱
连续多次调用 Invalidate 只会引发一次 Paint(这已经足够了)

20毫秒刷新周期 与显卡的刷新频率不协调(60Hz、75Hz),即便不闪烁,也会产生滚动的暗条
人眼的视觉暂存是0.1秒,过快的刷新有害而无利
版主,我调成了1000毫秒一次,移动的游标每走几格(有时4格有时2格)就灭一次,这个应该不是你回复中提到的暗条吧?
ace_chn 2018-12-31
  • 打赏
  • 举报
回复
引用 4 楼 耀先森TM 的回复:
/// <summary> /// 存储进度图 /// </summary> private Image mDrawedImg; /// <summary> /// Graphics /// </summary> private Graphics mImgGraph; int ProgressNum = 0; /// <summary> /// 初始化 /// </summary> void LoadingInit() { pbxProgress.SizeMode = PictureBoxSizeMode.StretchImage; mDrawedImg = new Bitmap(1000,100); pbxProgress.Image = mDrawedImg; } private void timer1_Tick(object sender, EventArgs e) { ProgressNum += 10; mDrawedImg = new Bitmap(pbxProgress.Image); mImgGraph = Graphics.FromImage(mDrawedImg); //线填充 mImgGraph.DrawLine(new Pen(Color.Blue, 10), new Point(ProgressNum, 0), new Point(ProgressNum, 100)); pbxProgress.Image = mDrawedImg; }
多谢兄弟,你这个方法我之前试过,也不太行。
ace_chn 2018-12-31
  • 打赏
  • 举报
回复
引用 7 楼 xuzuning 的回复:
用 Invalidate 而不是 Refresh
虽然两者都通过 Paint 实现绘图,但 Refresh 可能导致事件链混乱
连续多次调用 Invalidate 只会引发一次 Paint(这已经足够了)

20毫秒刷新周期 与显卡的刷新频率不协调(60Hz、75Hz),即便不闪烁,也会产生滚动的暗条
人眼的视觉暂存是0.1秒,过快的刷新有害而无利
多谢。我之前也测试了试着把刷新周期调到100毫秒,发现进度条游标有规律每走3格,第4格不显示。这可能就是造成闪烁的原因。 只是为什么这样,没找到原因
加载更多回复(7)

110,536

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧