ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

最美C# WinForm控件库实战:选型、自绘与避坑指南

最美C# WinForm控件库实战:选型、自绘与避坑指南 1. 为什么WinForm在这个时代仍是上位机界面开发的硬选择先说结论在工业上位机领域WinForm不仅没死而且活得相当滋润。你可能在各种技术社区里看到“WPF才是未来”“WinForm太老了”这样的论调但真正在工控现场摸爬滚打过的人都知道设备供应商给的标准DLL、协议SDK、网关组件十有八九还是原生C或C#封装的Win32接口车间里的老工程师上手最快的也还是WinForm那套拖拽式开发。这不是保守这是行业惯性叠加工程效率带来的必然结果。我自己做上位机也有七八年了从最初的学生竞赛项目到后来的产线MES、温湿度监控、设备数据采集大部分交付界面都是WinForm。其间也尝试过WPF、Avalonia甚至研究过把网页嵌进去的方案但兜兜转转绝大多数稳定运行了好几年、现场维护成本最低的还是WinForm。原因很简单WinForm的成熟控件生态太庞大了从工业仪表盘到数据库报表从串口调试到曲线绘制第三方控件库几乎能覆盖所有场景而且你踩过的坑别人早就踩过了网上资料一搜一大把。这篇文章想聊的就是“控件库”这三个字。很多人把WinForm做得丑就认为是框架的锅其实不是。WinForm默认控件确实朴素但你完全可以通过控件库、自绘和分层架构把它做出令人惊艳的效果。我整理了自己这几年在项目里真正用过的、值得花时间去研究的控件库和对应的应用场景也把踩过的一些坑一并写出来给正在做上位机选型的朋友一个参考。先说清楚这里的“最美”不是一个纯外观的概念。对我来说一个控件库美不美要看四点开发效率高不高、运行稳不稳定、文档全不全、是否贴合工业场景。有些控件库外观漂亮但运行效率稀烂一到数据刷新就卡界面这种直接拉黑有些外观朴素但内存控制极佳上了产线跑几年不出问题这才是真正的宝贝。2. 主流的第三方控件库横向盘点哪个真正配得上“宝藏”二字2.1 从DevExpress到SunnyUI的真实对比市面上的WinForm控件库数量不少但真正值得上位机开发者关注的我按自己的使用体验排了个序。DevExpress是名气最大的网格、图表、导航、报表全套齐全视觉风格现代如果预算充足且不介意安装包体积和内存占用它确实能让你在很短时间内拼出接近商业软件的界面。不过要注意DevExpress的授权费不便宜而且做了深度定制之后升级版本时经常出现兼容性问题我遇到过两次老项目升级DevExpress后报表模板直接打不开的情况非常折腾。Telerik UI for WinForms也是老牌选手控件类型丰富主题系统做得不错图表控件尤其适合做数据可视化。但它的问题在于中文资料相对少遇到问题基本靠官方Demo和文档自己摸索国内中小企业用得少群里问一圈可能没人理你。如果你英语阅读没问题可以考虑如果需要快速上手、遇到问题想搜到解决方案它的优先级会低一些。ComponentOne现在叫MESCIUS算是老牌厂商里最“工业向”的它有专门的FlexGrid表格控件性能非常强配合flexChart、flexPivot做数据透视和趋势分析相当顺手。我做温湿度监控项目时试过用它的大数据量表格加载10万行数据几乎没有卡顿这一点DevExpress的GridView在未优化状态下反而容易掉链子。再来说国产开源控件库这一块我觉得是很多人忽略的宝藏。SunnyUI是我首推的国产免费开源控件库Gitee上星标很高。它最大的特点是“开箱即用且好看”——按钮、文本框、列表、仪表盘、滑块、进度条都有统一风格的Modern样式UI配色柔和不会像Windows原生控件那样死板。更重要的是它对中文开发者极其友好注释基本是中文控件属性也做了汉化新手照着Demo拖一拖就能做一个像样的界面。我自己给客户做方案演示时默认皮肤就是SunnyUI客户普遍反馈“界面看起来专业”。HZHControls也是国产控件库里的佼佼者。它的控件覆盖面极广包括工业仪表盘、管道阀门、LED显示屏、温度计、液位等基本就是为工控可视化量身定做的。我的一个做水处理项目的朋友用它做了一套泵房监控界面动画效果直接拉满客户验收一次通过。它的代码是全开源的你可以随意修改控件内部绘制逻辑这对有定制需求的上位机项目来说极其重要。C/S框架CSFramework以及MetroFramework、MaterialSkin这些也偶尔会出现在讨论里。MetroFramework和MaterialSkin胜在极简和现代感用来做工具类软件、内部小工具很合适但它们控件数量有限复杂业务界面撑不起来我一般不建议把它们作为主力控件库。2.2 我从项目角度怎么看这些控件库做上位机开发选控件库不能只看好看还要考虑几个实际问题。第一是授权合规。商用项目和自用学习完全是两码事DevExpress、Telerik、ComponentOne都是收费授权的如果你在公司项目里使用必须确认公司是否买了授权否则交付给客户后一旦被查风险很大。开源控件库也要看清开源协议SunnyUI和HZHControls是MIT协议商用相对宽松但有义务保留版权声明。这些细节配置在合作洽谈或者代码审查的时候很容易被忽略建议立项之前就定好。第二是长期维护能力。有些控件库看着不错但停更多年、Bug无人修一旦踩到坑你就哭都哭不出来。我建议选GitHub或Gitee上仍然在活跃更新的项目提交记录至少是半年内有更新的而不是下载一个2015年以后就没动静的包。第三是和实际场景的匹配度。做报表选FlexGrid做监控可视化选HZHControls做设备控制终端选SunnyUI做复杂业务系统选DevExpress——没有绝对最好的控件库只有最合适当前业务的那一个。我整理了一张表方便大家快速决策控件库授权控件丰富度上手难度工业可视化能力我的推荐场景DevExpress商业收费极高中强大型商业管理软件Telerik商业收费高中高较强国际化项目、图表展示ComponentOne商业收费高中强大数据量表格、工业报表SunnyUIMIT开源中极低中中小型设备终端、快速交付HZHControlsMIT开源高中极强监控大屏、设备状态可视化MetroFrameworkMIT开源低低弱内部小工具、简单工具软件MaterialSkinMIT开源低低弱轻量级应用、个人项目3. 不谈颜值的自绘方案用GDI把WinForm做出定制品的美感3.1 为什么有时候控件库反而帮倒忙有个场景很扎心你辛辛苦苦把SunnyUI或者HZHControls引进来界面确实好看了但客户的定制需求和控件库默认样式冲突了。比如客户要求按钮是圆形的带仪表指示灯的配色是特定的进度条要像液位那样流动——这时候你去扒控件库API很可能发现它根本没暴露这些自定义接口。我自己就遇到过这种情况。之前做一套空气压缩机控制器客户给了一个设计稿界面里所有的按钮都是圆角矩形带渐变填充的数据面板的底色要随着设备温度变颜色。当时我用HZHControls做底子发现改样式非常别扭它的内部绘制逻辑是写死的强行改要么破坏原有效果要么反射改源码。后来干脆不纠结控件库了直接用GDI自绘两天时间就把全部定制界面做完了效果反而比控件库更贴合客户需求。所以我想说的是控件库是加速工具不是万能解决方案。当界面定制程度高时自己动手用GDI绘制才是王道。3.2 用Region和Paint事件做异形窗口与自绘控件自绘WinForm控件核心其实就两块。第一块是窗体外形的裁剪用Region配合GraphicsPath把默认的矩形窗口变成任意形状第二块是控件内容的绘制重写OnPaint事件用GDI画一切。举一个最简单的例子——自绘一个圆形按钮public class RoundButton : Control { public RoundButton() { this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true); this.Size new Size(60, 60); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Graphics g e.Graphics; g.SmoothingMode System.Drawing.Drawing2D.SmoothingMode.AntiAlias; // 画圆形背景 using (SolidBrush brush new SolidBrush(this.BackColor)) { g.FillEllipse(brush, 0, 0, Width - 1, Height - 1); } // 画文字 TextRenderer.DrawText(g, this.Text, this.Font, new Rectangle(0, 0, Width, Height), this.ForeColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter); } }在上面这段代码里SetStyle里那几项设置非常关键特别是OptimizedDoubleBuffer如果不加的话自绘控件刷新时会出现强烈闪烁这在工业界面上是致命的现场操作员会以为你程序坏了。AllPaintingInWmPaint是让系统把所有绘制消息合并到一起减少重绘次数也是防闪烁的关键。再看异形窗口。如果你想做一个非矩形的悬浮监控面板比如一个圆形仪表浮窗可以在Form的OnResize或者OnLoad里设置Regionprivate void SetRoundRegion(int radius) { GraphicsPath path new GraphicsPath(); Rectangle rect new Rectangle(0, 0, this.Width, this.Height); path.AddArc(rect.X, rect.Y, radius * 2, radius * 2, 180, 90); path.AddArc(rect.Right - radius * 2, rect.Y, radius * 2, radius * 2, 270, 90); path.AddArc(rect.Right - radius * 2, rect.Bottom - radius * 2, radius * 2, radius * 2, 0, 90); path.AddArc(rect.X, rect.Bottom - radius * 2, radius * 2, radius * 2, 90, 90); path.CloseFigure(); this.Region new Region(path); }异形窗口的坑在于一旦设置了Region窗体的阴影效果会丢失而且鼠标在非有效区域点击时窗体不会响应。解决办法是添一个全局消息钩子处理WM_NCHITTEST消息让边缘区域也能拖动和关闭这个偏进阶了不是每个项目都需要。3.3 双缓冲与实时刷新的那些坑自绘界面做得多了必然要跟实时数据刷新打交道。上位机界面里最常见的就是设备状态、温度、压力、液位这些数据每隔几百毫秒刷新一次。这时候如果直接把数值绑定到Label控件上你会发现两个问题一是界面像抽风一样不停地闪二是CPU占用率高得离谱。解决思路有三个层次。第一层是控件的双缓冲上一节已经提到了这是基础保证控件在刷新时不闪屏。第二层是局部刷新代替全局刷新。很多新手喜欢在Timer的Tick事件里直接this.Invalidate()这是非常粗暴的做法——它会把整个窗口所有控件全部重绘一遍浪费资源。正确做法是只对需要变化的那块区域调用Invalidate(new Rectangle(...))让系统只重绘这块矩形。实测下来一个温度仪表盘用局部刷新比全局刷新CPU占用能降一半以上。第三层是数据的跨线程投递。上位机必然涉及串口、TCP、OPC这些通信数据一多就会牵扯到UI线程更新问题。C#里跨线程更新UI的标准姿势是用BeginInvoke但你别在数据到达的瞬间就疯狂BeginInvoke上万条数据一批压过来UI线程根本处理不过来反而导致通信线程阻塞。我常用的做法是先塞到一个并发队列里然后用一个定时器比如100ms批量取出来一次性刷新到界面上。这样做既保证了UI流畅又不丢失数据具体代码我会在后面的通信章节详细展开。4. 按上位机业务场景拆解控件需求仪表盘、趋势图、表格与LED屏4.1 仪表盘控件从圆形仪表到工业表盘的自绘思路仪表盘是上位机界面里出现频率最高的元素。温度、压力、转速、液位……凡是需要直观显示数值大小的场景仪表盘都比纯数字更友好现场操作员扫一眼就明白当前状态是否正常。市面上现成的仪表盘控件不少SunnyUI和HZHControls里都有。但如果定制程度高我建议自己画一个通用的圆形仪表盘控件。核心绘制逻辑其实不难背景圆环、刻度线、指针、中心轴、文字标签几个同心圆和旋转矩阵叠加起来就行。我做仪表盘控件时习惯了把范围、单位、表盘颜色作为公开属性暴露出来这样同一个控件可以复用到多个不同的设备参数上。比如一台空压机界面上有“排气压力”“润滑油温度”“电机电流”三个仪表盘只需要在Form里放三个实例分别设不同的MinValue、MaxValue、Unit、ValueColor就成了。这比去挨个找第三方仪表盘控件然后被它的API限制要省心很多。有一个容易被忽略的细节仪表盘的量程变化。有些设备运行在不同工况下量程会动态调整——比如风机启动时电流量程是0到100A切到重载后变成0到300A。如果你在仪表盘控件里把量程写死运行中改数值就会很尴尬要么超量程看不见指针要么刻度全部重新布局。好的做法是重写OnPaint的时候每次取当前的MinValue和MaxValue来算比例并且检测到量程变化时局部重绘刻度区域。我在自绘时用了一个ValueRatio属性来映射实际值到0到1的比例公式就是(value - min) / (max - min)屏幕坐标一律用比例去算这样无论量程怎么变绘制逻辑都不会乱。4.2 趋势图与实时曲线的控件选型上位机里第二高频的需求就是趋势图。温湿度监控、压力波动曲线、PLC数据变化历史都需要画实时曲线。如果业务不复杂比如就是一条温度曲线实时推进用微软自带的Chart控件就够了PowerShell级别的配置就能出图完全不花钱。但它的缺点是性能一般数据量超过几万点之后交互会卡而且坐标轴自动缩放做得一般。如果数据量大推荐用开源社区的ScottPlot。它是专门为WinForm和WPF设计的高性能绘图库绘制几十万个数据点依然流畅而且API简单到令人发指var plt formsPlot1.Plot; double[] xs new double[100000]; double[] ys new double[100000]; // 填充数据... plt.AddScatter(xs, ys); formsPlot1.Refresh();ScottPlot还有一个优势支持鼠标选中缩放、拖动画布、坐标轴十字线定位这些功能在工业数据分析场景里非常实用。现场调试的时候老工程师经常要放大某一段波形看看毛刺ScottPlot的表现比Chart控件好太多。我最近两个项目里的实时曲线全部切到了ScottPlot再也没被客户吐槽过“曲线看不清”。还有一点要提醒上位机画趋势图不要等所有数据都累积到内存再画应该做数据窗口滑动。也就是只保留最近N个点的数据新数据进来时旧的丢掉这样内存占用恒定绘制速度也不会因为运行时间长而变得更慢。ScottPlot里可以配合plt.Clear()和重新AddScatter的方式实现或者用它的Axes设置固定范围让图形看起来像在一直滚动。4.3 表格控件与ListView双击编辑的经典场景上位机里操作日志、报警记录、配方参数这些都用表格来展示。WinForm自带的DataGridView能应付大部分场景但默认样式实在太丑而且对触摸操作不友好。如果客户对界面美感有要求可以套一层主题美化如果表格数据量大且交互复杂建议用FlexGrid这类商业控件。搜索热词里有一个“winform listview mousedoubleclic现场编辑”这是很常见的需求在ListView的某个子项上双击直接进入编辑状态改完回车保存。实现思路不复杂我在项目里的做法是给MouseDoubleClick事件挂上处理方法先判断点击的是哪一列然后在那个Cell的位置动态创建一个TextBox覆盖在上面焦点到TextBox失去焦点或回车时把值写回再移除TextBox。比如这样private void listView1_MouseDoubleClick(object sender, MouseEventArgs e) { ListViewHitTestInfo info listView1.HitTest(e.Location); if (info.Item ! null info.SubItem ! null info.SubItem ! info.Item.SubItems[0]) { // 保存引用创建编辑框 editingItem info.Item; editingSubItem info.SubItem; Rectangle rect info.SubItem.Bounds; editTextBox new TextBox(); editTextBox.Bounds rect; editTextBox.Text info.SubItem.Text; editTextBox.BorderStyle BorderStyle.FixedSingle; editTextBox.Leave EditTextBox_Leave; editTextBox.KeyDown EditTextBox_KeyDown; listView1.Controls.Add(editTextBox); editTextBox.Focus(); editTextBox.SelectAll(); } } private void EditTextBox_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode Keys.Enter) { SaveEdit(); } else if (e.KeyCode Keys.Escape) { CancelEdit(); } }这个方案有几个坑要注意。第一HitTest返回的SubItem如果为null或第一列通常不进入编辑因为第一列经常用来做行标识。第二TextBox要设置FixedSingle边框不然跟ListView的背景色不搭。第三ListView默认控件是自带控件新加的TextBox位于控件上层如果你同时开启了OwnerDraw绘制顺序会乱需要小心避让。4.4 LED显示屏控件与工业物联网终端场景搜索热词里还有“c# 灵信LED屏显示多个文本”这个我研究过。灵信LED屏一般通过网络协议或者串口下发文本C#里做这事关键在于拼接好屏的协议帧然后通过TCP或串口发送。界面侧更常用的是LED数字显示控件。这种控件模仿LED显示屏的字体效果让温度、产量、设备状态等数据像跑马灯一样展示工业现场很认这种视觉效果。如果你有现成控件库可以用没有的话自绘也简单将文本绘制到一个纯色背景的画布上用FontFamily里的等宽字体配合描边就出来了。核心是不要用抗锯齿LED这种数码管字体是像素风格关闭抗锯齿反而更像实物。做LED显示控件时的另一个细节是刷新频率与人眼的关系。低于60ms刷新一次人眼会明显看到闪烁高于250ms刷新一次又觉得很“钝”。一般100到200ms是一个舒适区间再配合双缓冲就没有大面积闪烁了。5. 界面与通信的整合架构串口、TCP、OPC与UI线程的正确协作方式5.1 上位机为什么比其他C#项目更讲究线程模型上位机软件的硬骨架是通信软骨架是界面。串口、TCP、OPC、CAN这些通道源源不断地把设备数据送上来界面要把它们呈现成人能看懂的形式同时还要把人的操作指令下发到设备。这个过程如果线程模型设计不好程序跑到一半就会卡死、崩溃甚至在产线上酿成事故。我见过太多初学者犯的错在串口DataReceived事件里直接操作UI控件。C#里这么做会抛InvalidOperationException因为回调和UI线程不是一个线程。然后很多人就在回调里用Invoke这确实解决了问题但用错了场景却会引入性能问题。Control.Invoke是同步调用调用线程会阻塞等待UI线程执行完成如果通信线程大量数据到达每个数据包都Invoke一次UI线程就会被塞满消息队列通信线程则被同步等待拖死。正确的做法前面提过就是数据缓冲 定时批量刷新。我用一个ConcurrentQueueT作为缓冲通信线程只做入队UI线程有一个System.Windows.Forms.Timer每隔100ms批量出队并刷新。核心代码大概是// 通信线程中 private ConcurrentQueueDeviceData bufferQueue new ConcurrentQueueDeviceData(); void OnDataReceived(byte[] data) { var parsed ParseData(data); // 解析协议 bufferQueue.Enqueue(parsed); } // UI线程中Timer每100ms触发 void timer_Tick(object sender, EventArgs e) { int count bufferQueue.Count; for (int i 0; i count; i) { if (bufferQueue.TryDequeue(out var item)) { UpdateUI(item); // 只做界面赋值 } } }这比洪水式的Invoke优雅得多界面稳定、CPU占用低、逻辑也清晰。5.2 串口、TCPListener多客户端和OPC通信的界面联动搜索热词里有一串高频词串口控件收发通信、TCPListener多客户端、C#连接西门子OPC。这正好是上位机通信的三大主流场景。串口方面WinForm里现在还有人用SerialPort组件自带的UI封装吗我强烈建议不要直接用裸的SerialPort类就行界面上只用你自己的控件库来展示收发状态。用组件的好处是拖拽方便代价是维护逻辑不透明、调试很吃力。我自己做串口调试界面UI层只放三个元素连接参数面板、收发日志RichTextBox、发送区TextBox底层封装一个SerialPortHelper类管理打开、关闭、接收事件和发送队列。接收事件里同样走缓冲队列的思路日志区每200ms往RichTextBox追加一条数据。这样即使串口数据每秒上千条界面也不卡。TCP服务端场景比如同时接入多台设备的上位机程序比较常见。C#里可用TcpListener起服务每个接入的客户端开一个TcpClient对象和一个独立线程或者用Task处理。UI侧要做的是维护一个在线设备列表并把每个客户端收到的数据统一入队。这时你用ListView或者DataGridView做设备列表非常合适但要注意点击断开设备这个操作跨线程的问题——从UI线程操作通信线程持有的Socket要先加锁避免两者同时操作同一个Socket对象导致异常。OPC这块C#连接西门子设备目前主流方式就是走OPC UA用OPCFoundation的OPCFoundation.NetStandard.Opc.Ua库或者用西门子自己的S7.Net库直连S7协议。上位机界面要做的事情相对简单——把读到的点位值映射到仪表盘、表格或者PLC状态灯控件上即可。有一个常见坑是在UI线程里做同步OPC读一旦PLC响应慢界面就死板。OPC通信应该完全放到后台线程通过事件或者队列把数据送到UI层和前面讲的串口、TCP是一致的。写到这里我想推荐一个这几年觉得特别管用的架构思路ECSEntity-Component-System思想在上位机中的应用。你不需要引完整的ECS框架只要把每个设备抽象成一个Entity把传感器、执行器、状态参数这些当成Component把通信、刷新逻辑封装成System工程结构就会非常清晰。C#的委托和事件天然适合这种架构比单纯在Form里堆代码强太多。我也是从第二个项目开始用这套思路后续维护成本明显下降。5.3 远程维护与异常捕获的经验上位机程序跑在现场最怕程序崩溃或者卡死而现场又往往没有专业程序员。所以我养成了一个习惯给所有上位机程序加全局异常捕获和日志记录。在WinForm入口的Main方法里写上[STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.ThreadException Application_ThreadException; AppDomain.CurrentDomain.UnhandledException CurrentDomain_UnhandledException; Application.Run(new MainForm()); } private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) { // 写日志并提示用户但保持程序继续运行 Logger.Log(e.Exception.ToString()); MessageBox.Show(程序发生异常错误信息已记录。); } private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { // 这里一般是致命错误记录日志准备退出 Logger.Log(e.ExceptionObject.ToString()); }注意ThreadException是针对UI线程的后台线程的异常会走UnhandledException。两处都要处理才能把小型异常拦截在后台、把致命异常记录在案。日志文件我用简单的文本文件按日期命名放在程序目录的Log文件夹里结构化数据量大了之后再考虑接SQLite。这个习惯救过我很多次。有一回客户凌晨打电话说屏幕黑了我远程看不到现场只能让现场的人把日志文件发过来一看是某个设备返回了异常格式的数据导致解析越界半天就定位到了问题。没有日志这种问题可能要在现场蹲一整天才查得出来。6. 前沿思路把HTML/Web UI嵌进WinForm的现代玩法6.1 为什么我会考虑Web UI方案说完传统WinForm控件库得聊聊一个近两年越来越热的趋势把网页塞进WinForm窗体里。搜索热词里“c# winform 嵌入html”“winform url展示控件”就是冲这个来的。这事的动机本质上是因为Web UI在视觉表现力和跨平台展示能力上确实比纯WinForm控件强。甭管你用什么控件库怎么自绘做一个数据大屏HTMLCSSECharts几下就出效果看板式的动画和数据联动WinForm要写好几百行。而且现在微软官方提供了WebView2控件基于Chromium内核可以直接嵌入WinForm窗体替代老掉牙的WebBrowser控件。WebView2不仅能加载本地HTML文件还能和C#代码做双向交互C#调用JS函数传入数据JS调用C#方法通知事件。这就打开了WinForm界面开发的新思路——复杂好看的界面用Web技术写业务逻辑和通信层还是C#两者互补。我自己的项目里有三分之一采用了混合架构主窗体、设备面板、操作按钮用WinForm数据大屏、趋势图、3D设备模型展示这些用WebView2加载HTML页面。效果客户非常认可说“感觉你们的技术很新”。6.2 WebView2和C#双向通信的落地示例要给WinForm嵌入WebView2首先得通过NuGet安装Microsoft.Web.WebView2包然后保证目标机器上安装了WebView2 Runtime现在Win10/11系统基本自带如果遇到老系统需要安装包。嵌入和调用代码无非是// 在窗体上放置一个WebView2控件 await webView.EnsureCoreWebView2Async(); // 方式一加载本地HTML webView.CoreWebView2.NavigateToString(htmlString); // 或者 webView.Source new Uri(file:///C:/yourpage/index.html); // 方式二C#调用JS函数传数据进去 string jsonData JsonConvert.SerializeObject(deviceDataList); string script $updateDevicePanel({jsonData});; await webView.CoreWebView2.ExecuteScriptAsync(script); // 方式三JS里需要调用C#时在C#侧注册一个对象 webView.CoreWebView2.AddHostObjectToScript(bridge, new BridgeObject(this)); // 在JS里就能用 window.chrome.webview.hostObjects.bridge.xxx 来调C#方法这里有个性能提醒ExecuteScriptAsync虽然方便但如果在Timer里每100ms、每次传几MB的JSON照样会把WebView2的进程拖垮。我做数据大屏时结构是WebView2只加载一次数据模板之后每次更新只传增量数据或者只更新某个DOM节点的值用ExecuteScriptAsync执行一小段JS。比如温度变了只执行document.getElementById(tempValue).innerText 26.5而不是把整个页面状态全部重新灌进去。还有一点WebView2在偏老旧的工控机上内存占用比较高。如果你部署的设备还在用2GB内存的老式工控机我建议谨慎使用WebView2或者只在特定的数据大屏页面才启用主操作界面还是用传统WinForm控件这样兼顾性能和展示效果。7. 一套可抄作业的上位机界面选型方案与避坑清单7.1 最终选型建议根据前面的分析我把自己常用的选型方案整理成了一套“组合拳”你可以直接拿去当项目启动参考。项目类型我的推荐方案理由快速交付的简单设备终端SunnyUI 微软Chart控件开箱即用中文文档开发周期短监控大屏、流程可视化HZHControls 自绘GDI工业控件全动画效果好可深度定制复杂报表、大数据量管理ComponentOne(FlexGrid) ScottPlot表格性能强图表交互好定制化程度极高的界面纯自绘控件 GDI WebView2辅助完全可控客户想改哪就改哪需要数据大屏的混合上位机WinForm WebView2 EChartsWeb做视觉WinForm做通信与操作预算充足的大中型商业软件DevExpress全家桶界面现代、控件全、省人力7.2 避坑清单把坑集中列一下每一条背后都是真实项目砸出来的教训。1. 引入控件库之前先确认授权协议。开源的也有坑比如有的叫“免费开源”实际是LGPL或者GPL商用会导致你的代码被迫开源。SunnyUI和HZHControls的MIT协议相对安全但还是要保留版权声明。2. 控件库版本和.NET框架版本匹配。WinForm有.NET Framework 4.x和.NET 6/8两条线很多控件库只支持其中之一。曾遇到过.NET Framework 4.8项目引用了一个.NET Core的NuGet包编译虽然能过运行直接TypeLoadException排查半天才明白是版本不匹配。3. 自绘控件务必先做双缓冲再谈美观。未开双缓冲的GDI绘制会在控件刷新时疯狂闪烁。条件允许的话所有自定义控件继承UserControl或Control后第一行代码就是设置DoubleBuffered true。4. 数据刷新走批量异步不要Event驱动到底。通信事件触发的UI更新洪流是上位机卡顿的头号原因。队列缓冲和定时刷新百试不爽。5. 大屏可视化别什么都塞WebView2。WebView2很吃内存老工控机上会拖垮整个系统。轻量数据用自绘控件真正的大屏展示页面才用Web技术。6. 所有现场运行程序必须带日志和全局异常捕获。这不应该是一个可选项而是上位机交付的默认配置。有了日志现场出问题你能远程定位没有日志你只能买机票飞现场。7.3 最后想说的做上位机界面开发说到底是平衡“开发效率”“运行性能”“视觉呈现”和“维护成本”这四件事。控件库帮我们解决了大部分“造轮子”的问题自绘技术让我们在遇到特殊需求时有底气通信架构决定了程序能不能长时间稳定运行Web混合方案又给了我们现代感的加分项。回到“最美C# WinForm控件库”这个话题我的理解是最美的东西没有标准答案它在你的项目里在你对控件库特性的把握里更在你用这些工具创造出的那一个个能稳定跑在产线上、让客户满意的界面里。希望这篇整理能帮你少踩几个坑把更多精力花在真正有意思的设备和业务上。如果你在选型或者自绘过程中遇到具体问题欢迎在评论里详细描述你的场景我看到了会尽量给出更具体的建议。
返回列表