公司需要我写几个GUI程序,让虚拟机(guest)内部可以控制虚拟机(host)外部的硬件。

控制外部的硬件的方法就是开一个串口,这样虚拟机与宿主机就可以相互通讯,此时就可以让虚拟机发送命令,宿主机执行命令,并返回结果

我需要一行行地展示内容,比如这样:

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

  1. 画面残影

  1. 画面出现大量空白

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

https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.forms.flowlayoutpanel?view=windowsdesktop-8.0

可见微软也承认自己的东西有这个毛病了,所以微软我xxx。

我后面和同学聊了下这个问题,他说Qt也有这个毛病,我几乎没用过Qt,所以不知道具体是什么样子的。

查了很多方法,网上的说法清一色的是设置DoubleBuffered,算是有点用处,结果就是“残影”没了,大量空白出现了,拖动条也变得卡顿了,真就给抄成习惯了……

然而浏览器、文件管理器的拖动条是正常的,这说明这个问题是可以被解决的。

顺便一提,拖动界面时可以出现界面刷新率低的问题,刷新率低到10也可以,但绝对不能出现闪烁问题。降低帧率似乎就是浏览器的做法,这也提醒我在界面绘制完成之前不要进行下一次绘制。

公司里没有人会 C#,所以也没有人可问,没办法,只能另寻它路。

想法一:逆向

首先想到的是逆向。浏览器不值得看,因为其主要界面大概率不是,文件管理器大概率可以。下载了几个界面分析软件,包括 SPY++,GUI-wizard等等,拿到的窗口的类名是没有见过的,所以放弃了这种做法

想法二:找其他开源框架

问GPT,推荐了ReaLTaiizor,SunnyUI,CSharpSkin之类的,只有ReaLTaiizor可以看到源代码,就下载它来用了。而它确实没有出现这个闪烁的问题。

虽然更换到这个开源框架也行,但是发现它自带的滚动界面不能满足我的要求,而且目前也已经实现了所有功能了,只差这个问题就可以提交给测试了,我希望快点做完,于是决定参考其代码做一个简单的组件。

ReaLTaiizor 对滚动界面的实现是:手写界面更新逻辑。也就是说重写了OnPaint方法,自己定义了一套绘制逻辑,要画线就调用划线的方法直接操作界面,要绘图就就调用绘图的方法直接操作界面……

想法三:参考ReaLTaiizor

我需要的是一个组件而不是一个GUI库,基于这样的想法,我确定了这个组件必须做成什么样子的:

  1. 表格形式,每一行的高度相同,每一列的宽度可以自定义,每一格中存储的内容可以是图片或者文字,文字需要支持换行。和本文第一张图相似的表格就行了。图片和文字就够了,拿两张图片,再绑定一个回调就可以做一个开关,这样按钮也有了
  2. 最重要的一点,绝对不能够闪烁,且滚动条需要与界面显示同步
  3. 需要一个回调,回调的参数是点击到的行,点击到的列,然后该行绑定到的对象,这一行的内容
  4. 由3引申出来的,即每一行都可以绑定一个对象
  5. 只支持上下滚动,左右滚动不支持
  6. 可以增加行、更新行或删除行,操作之后界面必须体现出来

所以就开始抄这个源代码了。因为是公司的代码所以不好贴出来,总体分为这几步:

  1. 基本验证

我不知道手写界面绘制能否解决问题,所以还是验证了一下。验证的方法很简单,每次鼠标滚轮事件触发了,就给界面换一个颜色,手机拍摄这整个过程,逐帧播放,检查是否有闪烁的问题。

验证之后,发现正常,所以继续实现了。

  1. 界面显示与同步滚动条与界面显示位置同步

界面显示就是每一次鼠标滚动滚轮,或者拖动滚动条,都调用界面刷新方法,将界面刷新,手动的把图片、文字刷新上去。

前文提到的“界面绘制完成之前不要进行下一次绘制”也在代码中实现了,方法很简单,添加一个bool isupdating,进入OnPaint之前检查它的值,为false就设置为true,然后在结束的时候设置回false;为true就直接返回,放弃这一回绘制。虽然可能没有用吧,但万一呢?

界面位置与滚动条位置同步问题,其实就是一个比例转换的问题,总体就是一个公式:

\[\frac{界面显示的位置}{界面的总高度}=\frac{滚动条位置}{滚动条总长度}
\]

关于这个公式,网上有一篇博客讲的不错。

https://www.cnblogs.com/lesliexin/p/13440927.html

虽然讲的不错,但是他没有解决闪烁问题,源代码我下载过来试了,评论里也有人说他没有解决这个问题

  1. 开启DoubleBuffered

其实手写了界面绘制之后,发现闪烁问题依然没有解决。此时我猜想是没有开启DoubleBuffered导致的。开启之后,问题解决了。

到此我猜想,DoubleBuffered的语义确实是微软描述的那样:界面先绘制到缓冲区,然后在一口气把缓冲区的内容显示到界面上。

但是为什么FlowLayoutPanel开启了这个功能,效果也没有多好?下班后我逆向了FlowLayoutPanel的源代码,发现它压根没有走OnPaint的绘制逻辑,所以DoubleBuffered应该是无效的。

到此界面效果就是下图了:

录制滚动界面的视频,逐帧播放,效果都与上图相似。当然,这个界面依然还有问题,见问题4。

  1. 实现回调

没什么好说的,就是将点击位置转换为行坐标与列坐标

  1. 界面模糊与字体锯齿严重

这界面模糊可以见这篇博客。

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 界面闪烁问题解决的更多相关文章

  1. 界面编程之QT的线程20180731

    /*******************************************************************************************/ 一.为什么需 ...

  2. 界面编程之QT的Socket通信20180730

    /*******************************************************************************************/ 一.linu ...

  3. 界面编程之QT的基本介绍与使用20180722

    /*******************************************************************************************/ 一.qt介绍 ...

  4. 界面编程之QT的数据库操作20180801

    /*******************************************************************************************/ 一.数据库连 ...

  5. 界面编程之QT窗口系统20180726

    /*******************************************************************************************/ 一.坐标系统 ...

  6. 界面编程之QT的信号与槽20180725

    /*******************************************************************************************/ 一.指定父对 ...

  7. 界面编程之QT绘图和绘图设备20180728

    /*******************************************************************************************/ 一.绘图 整 ...

  8. 界面编程之QT的文件操作20180729

    /*******************************************************************************************/ 一.QT文件 ...

  9. 界面编程之QT的事件20180727

    /*******************************************************************************************/ 一.事件 1 ...

  10. Java图形界面学习---------简易登录界面

    /** * @author Administrator * Java图形界面学习---------简易登录界面 * date:2015/10/31 */ import java.awt.BorderL ...

随机推荐

  1. Flutter获取当前路由信息和全局路由监听

    Flutter获取当前路由信息和全局路由监听 获取当前路由名 通过Flutter提供的方式 var routePath = ModalRoute.of(context).settings.name; ...

  2. 在Asp.netCore中使用Attribute来描述限流

    前言 同事问我Asp.netCore的RateLimiting是怎么使用的,我回答说很简单的,你只要按照如下步骤来: 在RateLimiterOptions上注册policy,记住policy对应的p ...

  3. Qt通用方法及类库10

    函数名 //获取保存的文件 static QString getSaveName(const QString &filter, QString defaultDir = QCoreApplic ...

  4. SuperMap Objects .NET知识库:SQL查询以及通配符

    1     SQL 语句的构建 在SuperMap组件产品中,有许多接口都用到了过滤条件,也就是标准 SQL 语句中的 WHERE 子句部分,比如各种涉及属性查询的接口.网络分析中弧段的过滤条件.拓扑 ...

  5. Python 潮流周刊#84:2024 年 Python 的最佳实践(摘要)

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  6. cpa-税法

    1.税法总论 2.增值税法 3.消费税法 4.企业所得税法 5.个人所得税法 6.城市维护建设税法和烟叶税法 7.关税法和船舶吨税法 8.资源税法和环境保护税法 9.城镇土地使用税法和耕地占用税法 1 ...

  7. kafka介绍和使用

    1 Kafka简介 ​Kafka是最初由Linkedin公司开发,它是一个分布式.可分区.多副本,基于zookeeper协调的分布式日志系统:常见可以用于web/nginx日志.访问日志,消息服务等等 ...

  8. MyBatis的深入原理-架构设计以及实例分析

    MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例, ...

  9. Java底层知识面试题

    JVM内存结构class文件格式JVM不会理解我们写的Java源文件, 我们必须把Java源文件编译成class文件, 才能被JVM识别, 对于JVM而言,class文件相当于一个接口class文件是 ...

  10. RSA 小规模演算

    原理 秘钥生成 加解密 解密验证 小规模演算