C# 图形界面编程之 FlowLayoutPanel 界面闪烁问题解决
公司需要我写几个GUI程序,让虚拟机(guest)内部可以控制虚拟机(host)外部的硬件。
控制外部的硬件的方法就是开一个串口,这样虚拟机与宿主机就可以相互通讯,此时就可以让虚拟机发送命令,宿主机执行命令,并返回结果
我需要一行行地展示内容,比如这样:

使用的是 WinForm,很容易找到 FlowLayoutPanel这个组件,结果其效果可谓是大跌眼镜:
- 画面残影

- 画面出现大量空白

这两种现象,微软官方称为flicker,中文翻译为闪烁。可以见官方文档:

可见微软也承认自己的东西有这个毛病了,所以微软我xxx。
我后面和同学聊了下这个问题,他说Qt也有这个毛病,我几乎没用过Qt,所以不知道具体是什么样子的。
查了很多方法,网上的说法清一色的是设置DoubleBuffered,算是有点用处,结果就是“残影”没了,大量空白出现了,拖动条也变得卡顿了,真就给抄成习惯了……
然而浏览器、文件管理器的拖动条是正常的,这说明这个问题是可以被解决的。
顺便一提,拖动界面时可以出现界面刷新率低的问题,刷新率低到10也可以,但绝对不能出现闪烁问题。降低帧率似乎就是浏览器的做法,这也提醒我在界面绘制完成之前不要进行下一次绘制。
公司里没有人会 C#,所以也没有人可问,没办法,只能另寻它路。
想法一:逆向
首先想到的是逆向。浏览器不值得看,因为其主要界面大概率不是,文件管理器大概率可以。下载了几个界面分析软件,包括 SPY++,GUI-wizard等等,拿到的窗口的类名是没有见过的,所以放弃了这种做法
想法二:找其他开源框架
问GPT,推荐了ReaLTaiizor,SunnyUI,CSharpSkin之类的,只有ReaLTaiizor可以看到源代码,就下载它来用了。而它确实没有出现这个闪烁的问题。
虽然更换到这个开源框架也行,但是发现它自带的滚动界面不能满足我的要求,而且目前也已经实现了所有功能了,只差这个问题就可以提交给测试了,我希望快点做完,于是决定参考其代码做一个简单的组件。
ReaLTaiizor 对滚动界面的实现是:手写界面更新逻辑。也就是说重写了OnPaint方法,自己定义了一套绘制逻辑,要画线就调用划线的方法直接操作界面,要绘图就就调用绘图的方法直接操作界面……
想法三:参考ReaLTaiizor
我需要的是一个组件而不是一个GUI库,基于这样的想法,我确定了这个组件必须做成什么样子的:
- 表格形式,每一行的高度相同,每一列的宽度可以自定义,每一格中存储的内容可以是图片或者文字,文字需要支持换行。和本文第一张图相似的表格就行了。图片和文字就够了,拿两张图片,再绑定一个回调就可以做一个开关,这样按钮也有了
- 最重要的一点,绝对不能够闪烁,且滚动条需要与界面显示同步
- 需要一个回调,回调的参数是点击到的行,点击到的列,然后该行绑定到的对象,这一行的内容
- 由3引申出来的,即每一行都可以绑定一个对象
- 只支持上下滚动,左右滚动不支持
- 可以增加行、更新行或删除行,操作之后界面必须体现出来
所以就开始抄这个源代码了。因为是公司的代码所以不好贴出来,总体分为这几步:
- 基本验证
我不知道手写界面绘制能否解决问题,所以还是验证了一下。验证的方法很简单,每次鼠标滚轮事件触发了,就给界面换一个颜色,手机拍摄这整个过程,逐帧播放,检查是否有闪烁的问题。
验证之后,发现正常,所以继续实现了。
- 界面显示与同步滚动条与界面显示位置同步
界面显示就是每一次鼠标滚动滚轮,或者拖动滚动条,都调用界面刷新方法,将界面刷新,手动的把图片、文字刷新上去。
前文提到的“界面绘制完成之前不要进行下一次绘制”也在代码中实现了,方法很简单,添加一个bool isupdating,进入OnPaint之前检查它的值,为false就设置为true,然后在结束的时候设置回false;为true就直接返回,放弃这一回绘制。虽然可能没有用吧,但万一呢?
界面位置与滚动条位置同步问题,其实就是一个比例转换的问题,总体就是一个公式:
\]
关于这个公式,网上有一篇博客讲的不错。
https://www.cnblogs.com/lesliexin/p/13440927.html
虽然讲的不错,但是他没有解决闪烁问题,源代码我下载过来试了,评论里也有人说他没有解决这个问题

- 开启DoubleBuffered
其实手写了界面绘制之后,发现闪烁问题依然没有解决。此时我猜想是没有开启DoubleBuffered导致的。开启之后,问题解决了。
到此我猜想,DoubleBuffered的语义确实是微软描述的那样:界面先绘制到缓冲区,然后在一口气把缓冲区的内容显示到界面上。
但是为什么FlowLayoutPanel开启了这个功能,效果也没有多好?下班后我逆向了FlowLayoutPanel的源代码,发现它压根没有走OnPaint的绘制逻辑,所以DoubleBuffered应该是无效的。
到此界面效果就是下图了:

录制滚动界面的视频,逐帧播放,效果都与上图相似。当然,这个界面依然还有问题,见问题4。
- 实现回调
没什么好说的,就是将点击位置转换为行坐标与列坐标
- 界面模糊与字体锯齿严重
这界面模糊可以见这篇博客。
https://www.cnblogs.com/Wonderful-Life/p/10250575.html
字体抗拒齿严重,GPT说这么做就行了,事实证明这确实有效:
protected override void OnPaint(PaintEventArgs e)
{
Debug.Assert(rows.Count == bindObj.Count);
base.OnPaint(e);
if (rows.Count == 0) return;
double bar_position = GetScrollBarPosition(vsb.Value);
Graphics graphics = e.Graphics;
// graphics.TextRenderingHint = System.Drawing.Text.TextRendering
graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
Rectangle rect = new Rectangle(0, 0, base.Width, base.Height);
另一个尝试
这个组件只支持表格形式,我后面想试试能不能做一个更加通用的组件,即每行一个,但这一行的内容可以是WinForm中任何一种组件。
简单写了一个Demo,其基本想法是,不在界面以内的不显示,在界面以内的计算其位置并显示,绘制的方法用默认的。
效果如下图。它确实不闪烁了,但是界面变得割裂了!肉眼可见的界面从上面往下面刷新!

所以如果WinForm下想要彻底解决闪烁问题,其工作量估计和做一个GUI库差不多了。
C# 图形界面编程之 FlowLayoutPanel 界面闪烁问题解决的更多相关文章
- 界面编程之QT的线程20180731
/*******************************************************************************************/ 一.为什么需 ...
- 界面编程之QT的Socket通信20180730
/*******************************************************************************************/ 一.linu ...
- 界面编程之QT的基本介绍与使用20180722
/*******************************************************************************************/ 一.qt介绍 ...
- 界面编程之QT的数据库操作20180801
/*******************************************************************************************/ 一.数据库连 ...
- 界面编程之QT窗口系统20180726
/*******************************************************************************************/ 一.坐标系统 ...
- 界面编程之QT的信号与槽20180725
/*******************************************************************************************/ 一.指定父对 ...
- 界面编程之QT绘图和绘图设备20180728
/*******************************************************************************************/ 一.绘图 整 ...
- 界面编程之QT的文件操作20180729
/*******************************************************************************************/ 一.QT文件 ...
- 界面编程之QT的事件20180727
/*******************************************************************************************/ 一.事件 1 ...
- Java图形界面学习---------简易登录界面
/** * @author Administrator * Java图形界面学习---------简易登录界面 * date:2015/10/31 */ import java.awt.BorderL ...
随机推荐
- 聊一聊 C#线程池 的线程动态注入 (中)
一:背景 1. 讲故事 上一篇我们用 Thread.Sleep 的方式演示了线程池饥饿场景下的动态线程注入,可以观察到大概 1s 产生 1~2 个新线程,很显然这样的增长速度扛不住上游请求对线程池的D ...
- Windows 记录开机后应用启动慢的问题
最近大屏产品经常报一些开机启动的问题,工厂反馈厂测软件有些模块测试不通过,家里开发测试均发现Launcher等软件首次启动需要加载10多秒. 经过小伙伴们排查,发现是刷母盘后首次开机问题概率比较大.使 ...
- Qt音视频开发11-ffmpeg常用命令
一.前言 大部分的格式转换工具比如格式化工厂等,都用到了ffmpeg来处理,ffmpeg编译后生成的ffmpeg.exe.ffplay.exe.ffprobe.exe等可执行文件,其实就封装了众多牛逼 ...
- 揭秘vivo百亿级厂商消息推送平台的高可用技术实践
本文由vivo 互联网服务器团队Yu Quan分享,本文收录时有内容修订和重新排版. 1.引言 如今,Android端的即时通讯IM这类应用想实现离线消息推送,难度越来越大(详见<Android ...
- 决策单调性优化 DP
前言 本文将介绍决策单调性优化 DP 的相关内容.持续更新修正,如有差错请指出. 1.四边形不等式优化 DP 1.1 四边形不等式与决策单调性 四边形不等式:如果对于任意的 \(a \le b \le ...
- 2021 年万圣节 Github 彩蛋
记录每年 Github 万圣节彩蛋,也记录有来项目成长历程. 2021 万圣节彩蛋 2020 万圣节彩蛋
- Diary -「NOI 2022」尘降
又一次,以这样一种身份来到国赛赛场.起跑线延长出赛场外,我将于此开始又一场已知"无用"的竞技. 虚无中 我的尘埃盲目漂泊摇晃 时间回到数个月前的省选,\(600\) 分的总 ...
- CF div2 994 (A~E)
VP赛时三题,自我感觉发挥不错,唯一不满意的地方在于D题完全没有思路. A 答案最多为2,因为最坏情况即为先将整个区间合并为一个数,若这个数不是0,则再将这个数变为0. 所以3种情况分类讨论即可: 全 ...
- weblogic-copy
一.简介 WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于JAVAEE架构的中间件,WebLogic是用于开发.集成.部署和管理大型分布式Web ...
- C#定点执行任务测试案例
定时方法实现类 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text ...