
提到 SunnyUI做 C# 桌面开发的人应该不陌生。它是一套基于 WinForm 的开源界面控件库官方宣传是“70 控件多框架支持”网上甚至有人把它称为 WinForm 的终极 UI 解决方案。我最早看到“终极”两个字是有点反感的因为做 WinForm 这些年见过太多号称包治百病的控件包真要集成的时候各种问题。后来在一个设备管理系统里项目周期被压得很紧界面又必须从原生灰底换成一套统一风格我才认真试了一轮 SunnyUI。试完之后我的评价是它不一定是终极但在 C# 原生桌面这个领域确实是目前开源方案里完成度最高、最省心的选择之一。这两天正好在翻之前项目的改造笔记顺手把这套控件库的选型思路、接入步骤、常见坑都整理出来。全文不吹不黑只讲我实际用下来的感受给正在纠结 WinForm 界面方案的朋友一个参考。1. 为什么我最终把界面方案换成了 SunnyUI1.1 先说说 WinForm 界面开发的三大难做 C# 原生桌面开发的朋友应该都有同感WinForm 本身是一个以业务效率为先的框架不是以颜值见长的框架。用原生控件拖出来的界面Button、TextBox、DataGridView 完全是系统默认风格不同 Windows 版本下显示效果还不一样。我见过不少项目功能逻辑都写完了结果界面一打开客户直接说“这软件看着像上世纪 90 年代的”。第一个痛点是控件样式陈旧且不统一。原生控件的绘制由系统主题决定你在开发机上调整好的界面放到另一台不同系统的机器上按钮高度、边框颜色、选中态高亮都可能变。想统一就得自己处理控件的绘制事件每个控件单独写 Draw 逻辑工作量非常大。第二个痛点是自绘成本高。我之前在某项目里想做一个圆角按钮用原生 Button 的 OwnerDraw 折腾了半天还只是静态效果没有动画、没有状态过渡。后来换成 PictureBox 加自己画又得处理背景色、点击反馈、禁用态一个按钮画下来少说要小半天。项目一多光界面自绘的代码就能堆一个小模块。第三个痛点是商业控件库的授权和黑盒问题。WinForm 领域也有不少成熟第三方控件但价格不低License 有时候还按开发者数量算。更麻烦的是商业化控件遇到问题时你只能提工单内部怎么实现的看不到想自己修都无从下手。SunnyUI 能进入我的视野恰恰是把这三个痛点同时解决了开源免费、控件统一风格、源码可查可改。它不是那种只能改改颜色的换肤库而是从控件级重绘的一整套 UI 组件体系。1.2 我第一次用 SunnyUI 改造旧项目的过程我手头那个设备管理系统是个典型的老项目原生 WinForm窗体数量大概有二十多个各种 Button、ComboBox、DataGridView 混在一起界面风格基本靠手动设置 BackColor 和 ForeColor 维持时间一长早就乱得没法看了。客户提出要把整个系统升级成统一的蓝色主题并且要求按钮图标、登录窗口、数据表格都要有明显改观。我一开始是想先自己画几个关键控件试试水结果半天只做好一个带图标的按钮还是静态的。随后我同事建议试试 SunnyUI。我们做了一个很小的试点挑登录窗体来改造。安装 NuGet 包、把 Form 基类换成 UIForm、把原生控件替换成对应的 UI 控件总共花了一两个小时登录窗口的观感已经完全不一样了。那次试点给我最深的印象是SunnyUI 的控件命名和原生 WinForm 控件高度对应基本是原生控件前面加 UI。UIButton、UITextBox、UIComboBox、UICheckBox这些控件上手几乎没有学习成本。真正需要花时间理解的只有样式机制和几个特殊控件比如符号按钮、数据表格的增强用法。整个系统最后花了一个周末完成主要界面替换大部分工作量其实在处理旧代码里的控件事件重名和个别控件属性兼容界面本身的替换反而很快。这也是我愿意长期用它而不是继续手写自绘代码的直接原因。1.3 这套方案适合谁不适合谁先说话任何技术方案都有边界SunnyUI 也不例外。我用下来的感受是它最适合下面几类项目企业内部管理系统、后台管理工具、MIS 类软件这类项目界面追求功能清晰、风格统一不需要太多花哨动画。工业上位机、设备调试工具、数据采集软件这些场景大量使用按钮、指示灯、进度条、曲线图SunnyUI 的控件风格比较硬朗。快速交付的桌面工具项目周期短没时间在界面上逐个打磨但又不希望界面看起来太简陋。WinForm 老项目升级改造想在不重写整个架构的前提下提升视觉效果。不太适合的场景也有。如果你需要的是像素级定制的皮肤系统或者大量包含复杂动画的交互动效SunnyUI 这套基于经典自绘的方案不如 WPF 灵活。另外如果你做的是纯工具库希望最终发布体积非常小那引入整套控件库可能不划算。再就是跨平台 UI 项目WinForm 本身就绑定了 Windows 生态SunnyUI 无法改变这一点。2. 70 控件和多框架支持到底是不是真材实料2.1 高频使用的核心控件盘点SunnyUI 官宣 70 控件这个数字我在实际项目里没有逐个数过但按控件面板里的分组看常规桌面应用需要的东西确实都有。我挑几个真正帮到我的核心控件说说。UIButton 和 UISymbolButton 是我用得最多的。UIButton 解决了原生按钮在不同系统下样式不一致的问题UISymbolButton 更实用它自带一套字体图标库按钮上可以显示小图标不用再为每个按钮单独准备一张 PNG。以前要做“保存”“删除”“刷新”这样的图标按钮我得准备一堆图标资源换颜色还要重新做图。用 UISymbolButton 之后只需要指定一个 Symbol 编号图标就出来了颜色还能跟随主题变化这个体验解决了我很大的痛点。UITextBox、UIComboBox 和原生的差异主要在边框和背景色上。SunnyUI 把这些控件都改成了统一风格的自绘控件配合整体主题不会出现文本框底色是白色、窗体背景是灰色这种割裂感。UICheckBox、UIRadioButton、UISwitch 这几个选择类控件风格也明显比原生控件更现代特别是 UISwitch 做开关功能比原生 CheckBox 直观得多。UIForm 是我觉得整个库的基石。WinForm 原生窗体的非客户区标题栏由系统绘制想换颜色非常麻烦。SunnyUI 的 UIForm 把标题栏也统一接管了窗体边框、标题文字颜色、背景色都跟着全局主题走。改造老项目时把 Form 基类换成 UIForm窗体的观感立刻提升一个档次。还有 UIMessageBox、UITabControl、UIPagination、UIDatePicker 这些也都是日常高频控件。UIMessageBox 对应 MessageBox但弹窗样式和整体主题一致不会出现软件界面换了风格、弹窗还是老样子的别扭情况。UIPagination 做列表分页特别方便不用自己拼页码按钮。2.2 UIDataGridView 和图表控件的实战表现如果说上面那些基础控件是比较顺手的日常工具那 UIDataGridView 和图表系列就是让我把 SunnyUI 当成正式技术选的加分项。原生 DataGridView 在数据量稍大时会出现滚动卡顿表头样式也比较陈旧。UIDataGridView 在保留原有操作习惯的基础上把滚动条、表头、选中行状态都做了重绘视觉上干净很多。它还支持一些增强功能比如滑动手感、下拉刷新这类偏移动端的交互这个比较适合触摸屏工控场景。我那个设备管理系统里设备状态列表就是用它展示的滚动流畅度比之前原生控件有明显提升。图表系列覆盖了折线图、柱状图、饼图、雷达图等常见类型。对我来说用处最大的是 UILineChart 和 UIBarChart。以前做数据看板要么自己用 GDI 画折线要么引入重量级图表库。SunnyUI 的图表控件开箱即用直接设置数据源就能出图颜色风格也和整体主题保持一致不需要额外调色。不过图表控件也有需要注意的地方它更适合展示百量级数据点内的业务图表如果硬塞上万点的大数据曲线性能会明显下降。这是我在做某次压力测试时发现的后面会单独讲排查方法。总体而言它满足常规看板和管理系统的图表需求绰绰有余。2.3 多框架支持从 Framework 4.0 到 .NET 8 的兼容性多框架支持是 SunnyUI 很突出的一个优势。早几年 WinForm 项目大多还停在 .NET Framework 4.5 或者 4.6.2很多新控件库只支持 .NET Core 3.1 以上老项目根本用不了。SunnyUI 的 NuGet 包同时打包了多个目标框架的版本从老的 .NET Framework 到新的 .NET 6、.NET 8 都有对应 DLL项目用什么目标框架NuGet 还原时就自动选对应的那套。这点对我意义很大。我手上还有一个维护多年的老系统目标是 .NET Framework 4.0以前想引入任何现代化控件库都要考虑目标框架兼容SunnyUI 直接解决了最后一道门槛。而在新项目里用 .NET 8 建 WinForm 工程同样能直接安装使用。需要提醒的是在 .NET Core / .NET 5 上使用 WinForms 需要项目文件里开启 UseWindowsForms。用 Visual Studio 创建 WinForms 项目时这是默认开启的但如果是手工改的 csproj 文件千万别漏了UseWindowsFormstrue/UseWindowsForms这一项。这个问题我在新项目迁移时踩过一次运行起来直接报类型未加载的错误检查半天发现是目标框架没有启用 Windows Forms 桌面支持。2.4 主题换肤、多语言和风格统一机制SunnyUI 给我最大的好感来自它的全局主题机制。它所有的控件颜色、字体、边框都不是散落在各个窗口的魔法数字里而是由一套全局 Style 统一管理。切换主题时只需要改一次全局样式所有窗体上的 SunnyUI 控件会自动刷新不需要逐个窗口改属性。我项目里用到的场景是给系统做了蓝色和深色两套主题登录界面加了一个切换按钮。之前用原生控件时这种需求意味着每个窗体的每个控件都要写一遍换肤逻辑还要处理字体颜色、背景色、选中态各种组合。换成 SunnyUI 后这个需求变成了一行全局样式设置加一次窗体刷新省下来的工作量不是一点半点。多语言支持也是意外之喜。内置的中英文资源足以覆盖绝大多数桌面系统的常规文字配合全局静态的语言设置可以做界面语言的即时切换。对于需要做海外交付的桌面项目这一点会很省心。如果你只是做内部工具那这套多语言机制也能作为文字资源统一入口来用。3. 从零开始接入一个可落地的项目改造过程3.1 安装、初始化与项目结构改造接入 SunnyUI 的第一步很简单在 Visual Studio 里打开 NuGet 包管理器搜索 SunnyUI安装最新稳定版。如果你的项目允许建议直接把工程升级到当前长期支持的 .NET 版本体验会更好。如果老项目只能停留在 .NET Framework只要版本不低于 4.0基本也能跑。安装完成后第一步就是引用命名空间。简单的方式是直接在窗体代码文件里加using Sunny.UI;如果想全项目统一引用可以在 csproj 里做全局 using或者在项目根目录建一个 GlobalUsings.cs 文件global using Sunny.UI;这样新建的窗体文件不用每个都重复写 using。不过考虑到老项目里还有不少原生控件我建议先不要急着加全局 using免得已有的原生类型和 UI 控件类型名冲突。真正关键的改造动作是把需要美化的窗体基类从 Form 改成 UIForm。比如原先是public partial class MainForm : Form改成public partial class MainForm : UIForm改完之后重新编译设计器里可能会因为控件属性初始化顺序报一两个错误常见的是窗体设计器生成的代码里引用了一些原生窗体专属属性。这时候删掉 InitializeComponent 中对应行或者重新生成一次窗体问题基本就解决了。基类换成 UIForm 之后窗体标题栏、背景色、边框都会落到 SunnyUI 的管理范围内。3.2 一个登录界面的完整写法我拿一个登录窗体做例子大家感受一下替换过程和代码风格。假设窗体上已经拖好了两个文本框和一个登录按钮控件命名分别为 txtUser、txtPwd、btnLogin。窗体类型改为 UIForm 后登录逻辑可以这么写public partial class LoginForm : UIForm { public LoginForm() { InitializeComponent(); txtUser.Focus(); } private void btnLogin_Click(object sender, EventArgs e) { if (string.IsNullOrWhiteSpace(txtUser.Text) || string.IsNullOrWhiteSpace(txtPwd.Text)) { UIMessageBox.ShowError(用户名和密码不能为空); return; } // 这里实际应调用登录接口并做密码加密等处理 UIMessageBox.ShowSuccess(登录成功); DialogResult DialogResult.OK; Close(); } }需要注意UITextBox 的密码框属性和原生 TextBox 类似在设计器属性窗口里找到 PasswordChar 设置为指定字符即可。为了更好的视觉体验还可以给登录按钮设置一个 Symbol 图标比如钥匙图标。这样按钮上会显示图标加文字比纯文字按钮看起来专业很多。如果希望回车键也能触发登录可以覆写窗体的 ProcessCmdKey 方法或者使用事件处理器去监听文本框的键盘事件。这里简单在文本框的 KeyDown 事件里加判断private void txtPwd_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode Keys.Enter) { btnLogin_Click(sender, e); } }这样一个登录窗体的改造就完成了。整个过程里不需要手动设置任何控件的颜色和字体它们都跟随全局主题。3.3 全局暗色主题和字体设置如果你的系统要求整体使用暗色风格可以在启动代码里设置全局主题。我项目里 Program.cs 的写法参考如下不同版本 API 名称可能有细微差异以当前版本智能提示为准using Sunny.UI; static class Program { [STAThread] static void Main() { // 示例写法具体请按当前版本 SDK 调整 UIGlobal.Style UIStyle.Dark; UIGlobal.Lang UILang.Cn; Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new LoginForm()); } }上面这段代码里UIGlobal.Style 控制全局主题样式UIStyle.Dark 是暗色主题的枚举值UIGlobal.Lang 控制语言。实际项目里如果只需要默认蓝色主题这两行可以不用写。但只要你做了主题设置后续所有 SunnyUI 控件都会自动适配这是我在多个项目里验证过的事情。字体方面SunnyUI 默认使用微软雅黑这个字体在绝大多数 Windows 系统上都有但如果是精简版系统或者老旧的嵌入式 Windows 环境可能没有预装。遇到这种情况要么部署时一并带上字体文件要么在全局设置里把字体统一改成系统的默认字体。我建议不要只在一台开发机上改字体否则部署到客户机器上很容易出现字体回退导致控件文字错位。3.4 和原生控件混用时的兼容细节实际项目里很多时候没办法一次性把所有控件都替换成 SunnyUI尤其是老项目业务逻辑和自定义控件深度绑定。SunnyUI 与原生控件放在同一个窗体上通常不会冲突但视觉上需要做一些处理。最常见的问题是背景色不一致。SunnyUI 控件的背景色跟着主题走原生 TextBox 默认是白色原生 Button 默认是系统灰色如果窗体背景是深色主题这些原生控件会显得格外突兀。我的建议是混用阶段先不着急换全局暗色主题等主要控件都换成 SunnyUI 后再做主题切换。或者将混杂使用的区域用容器隔开比如把原生控件放进一个 Panel背景色设置为接近主题色的颜色减少观感差异。第二个需要注意的问题是字体和控件高度。原生控件在 DPI 变化时的缩放行为和 SunnyUI 控件可能不完全一致导致同一行里的控件高度对不齐。解决办法是在 UIForm 上统一设置全局默认字号并手动调整控件布局避免完全依赖 Anchor 自动拉伸。第三个问题是事件绑定。SunnyUI 控件和原生控件的许多事件名一致但事件参数细节可能不同。把原生控件替换成 UI 控件后原本写在事件里的 sender 类型判断可能报错。我改造时遇到过不少这类问题本质上是控件类型发生了变化。因此替换时要先编译一遍程序以编译错误为线索逐个修正事件代码不要试图一次性在可视化设计器里全部替换完。4. 实战之后高频问题排查和我的避坑心得4.1 高分屏 DPI 发虚的问题第一个绕不开的问题是高分屏。现在的笔记本普遍是 2K、4K 分辨率Windows 缩放比例调到 125% 或 150% 很常见。如果 WinForm 程序没有正确声明 DPI 感知模式整个界面都会变模糊。我用 SunnyUI 最开始也有这个问题在 4K 屏上文字和图标明显发虚部分自定义绘制的控件边缘有锯齿。原生 WinForm 在高 DPI 下本来就有这个毛病SunnyUI 的大量自绘控件如果不配合正确的 DPI 模式也会被系统整体缩放拉伸。解决方法是使用 PerMonitorV2 的 DPI 感知模式。在 Program.cs 的 Main 方法里确保在创建任何窗口之前调用Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);如果项目还在 .NET Framework 4.x 且 API 不可用可以通过修改 app.manifest 文件在里面加入 DPI 感知声明。做了这一步之后大部分控件在高分屏下都会清晰很多。但还有一类问题需要单独处理如果你自定义了 UserControl或者重写了某些控件的 OnPaint 方法坐标计算要自己乘以 DPI 缩放系数。SunnyUI 内置控件在这块做得不错但自绘代码仍然需要自己负责缩放。我的习惯是把所有图形绘制相关的坐标都封装到一个基于 DPI 比例的统一方法里而不是在控件里写死像素值。4.2 图表刷新卡顿和数据更新的节奏图表性能是另一个让我踩过坑的点。当时我在做一个实时数据监控面板用 UILineChart 每隔几百毫秒更新一次曲线数据。刚跑起来时看着挺流畅操作几个窗口之后就发现界面有明显卡顿CPU 占用也往上飙。排查后发现问题是刷新的频率太高每次数据到达都立刻触发图表重绘。UILineChart 本身是基于 GDI 绘制频繁重绘的性能开销不小特别是在数据点很多时每次更新都要重新计算坐标、绘制坐标轴和所有点连线反复触发自然卡。我的解决思路是降低刷新频率并用一个定时器做数据合并。具体做法是实时数据先写入临时缓冲区定时器每 1 秒或者 2 秒触发一次图表更新把缓冲区里的数据一次性设置到图表上。这样曲线展示的实时性还在但重绘次数从每秒几次降到了每秒一次界面明显流畅。方法可以简单概括为能不刷新就不刷新必须刷新就批量刷新。另外如果图表数据系列需要动态新增和删除不要频繁操作 Series 集合最好在初始阶段固定系列数量和名称后续只更新数据值。把数据准备和 UI 刷新分离这套逻辑在任何绘图控件上都适用。4.3 一组容易被忽略的控件行为接着聊几个用过一阵才会发现的细节行为。第一个就是我在前面提到的 BackColor 问题如果你给 SunnyUI 控件设置了 BackColor但发现运行后颜色没有生效不要紧张这不是 BUG。SunnyUI 的大部分控件颜色由 Style 统一控制手动设置 BackColor 会被覆盖掉。想改变颜色要么调整主题要么把该控件的 Style 设置为自定义模式再改颜色。搞清楚这点能少翻很多设计器报错。第二个是 UISwitch 的状态读取。有人习惯像读 TextBox 的 Text 属性那样去读开关显示的文字但 UISwitch 真正要读的是 Checked 属性。它显示的开/关文本只是视觉表现状态判断以 Checked 为准。类似这种情况SunnyUI 很多控件都和原生控件属性命名相似但语义不完全一致换了控件之后一定要看一眼实际有哪些属性和事件别凭老经验写。第三个是 UIMessageBox 的弹窗位置。原生的 MessageBox 默认居中于屏幕而 UIMessageBox 如果传入了 Owner 参数会居中于父窗口行为会让人觉得“弹得位置不对”。我建议手动传入当前窗体作为 Owner这样弹窗位置更符合桌面应用的通常习惯。第四个是 UISymbolButton 的图标模糊问题。这个在低 DPI 下不明显但在高分屏缩放比例较高时个别图标会出现边缘模糊。这是因为图标字体本身的渲染方式切换高 DPI 模式后会缓解但如果仍然不清晰可以把图标换成单独的矢量图标或者适当增大按钮尺寸。4.4 常见问题速查表我把实际项目里遇到的高频问题整理成了一个速查表方便大家对照排查。症状表现常见原因建议处理方式控件设置了 BackColor 不生效SunnyUI 控件颜色由 Style 统一接管切换主题或将该控件 Style 设为自定义模式高分屏下界面整体模糊程序未开启 PerMonitorV2 DPI 感知在 Main 中调用 SetHighDpiMode 或修改 app.manifestUILineChart 刷新卡顿高频率触发 GDI 重绘定时器合并数据减少刷新次数图标按钮在高分屏下模糊字体图标在缩放后的渲染问题加深色阴影或更换其它图标方案UISwitch 读取不到状态错误地使用了开关文本属性读取 Checked 属性判断状态替代原生控件后事件报错控件类型改变导致 sender 类型不匹配编译后用错误列表逐个修正事件代码窗体设计器出现初始化异常Form 基类替换后属性顺序变化重新生成窗体或删除无效初始化代码部署到精简系统后字体错位系统缺少微软雅黑字体随部署包携带字体或全局设置系统默认字体这张表覆盖了 80% 的初期问题。真正卡住的大多数不是控件本身的问题而是环境适配和旧项目改造时的习惯切换。4.5 我对“终极方案”这个说法的一点个人看法在动手写这篇总结之前我又把之前项目里用 SunnyUI 实现的部分翻了一遍。平心而论它离“终极”这两个字还有距离尤其是自定义控件的自由度和 WPF 相比完全不在一个维度。但对 C# 原生 WinForm 生态来说SunnyUI 提供了一个很难得的平衡点控件数量足够多、多框架支持覆盖够广、源码开放且持续更新。我现在的新项目里只要确认是内部管理类工具或数据看板都会默认把 SunnyUI 作为首选的界面基础组件。如果遇到它解决不了的特殊交互再单独为那个场景写一个自定义控件。这种“通用部分交给成熟框架特殊部分自己补”的组合是我目前看来 WinForm 项目效率比较高的实践方式。最后再说一句不要急着把老项目所有窗体一次性全部改造完。先挑一个登录窗口或者一个主框架页面做试点跑通全流程确认你的目标框架没问题、团队熟悉常用控件再分批推进。改界面这种事情最怕的就是一开始大动干戈后面遇到兼容问题被迫回滚。SunnyUI 整体可靠但小坑仍然有稳扎稳打才是最优解。