上周交付了一个 WinForms 迁移 WPF 项目的第一期,趁记忆还热,把整个过程里「哪些活工具能干、哪些必须手写」整理出来。背景:客户的检测上位机软件,2014 年的 WinForms + net40 + 老版 DevExpress,触发条件是办公室换了 4K 屏,整个界面挤成一团。客户原话:「能改就改,不能改你就直说,别骗我。」
自动化边界:约 30%
.NET Upgrade Assistant 能做的部分其实很有限但很省事:老 csproj 转 SDK 风格、TargetFramework 升到 net8.0-windows、API 兼容性扫描出报告。十几个项目的解决方案半小时跑完。
它做不了界面层。WinForms 的 InitializeComponent 是控件树实例化代码,WPF 的 XAML 是声明式布局 + 数据绑定——不是翻译问题,是范式差异,没有任何工具能可靠转换。
实际自动化的清单:
- packages.config → PackageReference(脚本批量)
- 纯业务逻辑层原样保留(我们项目 13 个业务类零修改)
- 资源文件、配置合并
必须手写的部分
1. 界面层重写(约占一半工期)。先搭 MVVM 骨架,我用的 CommunityToolkit.Mvvm,[ObservableProperty] + [RelayCommand] 省掉大量样板。然后按窗体逐个重做:TableLayoutPanel → Grid,事件 handler → ICommand,TextBox.Text = xxx → 绑定。
2. 实体类补通知。老 POCO 没有 INotifyPropertyChanged,手工改不现实。我写了个 Source Generator 批量生成通知代码——注意这个「自动化工具」本身的开发时间也要计入工期。
3. 第三方控件。老版 DevExpress 新版 API 变化大,grid 列配置、汇总行写法全要对着新文档调。报表模块单独警告:老 DevExpress 报表在新版拆成独立授权套件,我们最后决定自绘打印,这一项多花 4 天。
4. 线程模型。BackgroundWorker + Invoke 统一改 async/await。最费神的是串口:SerialPort.DataReceived 在线程池线程触发,深处的 UI 更新要一路理到 ViewModel。改完连真设备跑 48 小时连续采集不丢包才算过。
5. 高 DPI。app.manifest 声明 PerMonitorV2 只是开始:RenderOptions.BitmapScalingMode 逐处处理,自绘控件和第三方控件各有 DPI 缺陷要修。记住客户发起迁移的真实动机八成就是高分屏糊——这块做不好等于白迁。
两个典型报错
System.BadImageFormatException:原项目 x86 编译,串口 SDK 只有 32 位 DLL,升 x64 直接崩。解法是要 64 位版本,或者把硬件调用拆成独立 32 位进程走 IPC。评估期就要确认硬件 SDK 位数,这坑能吃掉整个缓冲期。
System.MissingMethodException:SDK 风格项目没有 packages.config 版本锁定,还原拉了不兼容新版。关键依赖显式锁版本。
评估口径
同行接活参考:自动化 30% + 界面重写 40% + 第三方控件/报表/DPI/线程 30%。按窗体数量估单窗体重写成本,硬件依赖单独加风险系数。甲方评估时要求把「工程升级」和「界面重写」分开报价,报价里没有「高 DPI 适配」的方案可以直接 pass。
第一期交付,客户在新屏上打开软件界面缩放正常,二期做报表。十年老系统改造的价值不在推倒重来,在于把能用的逻辑保下来、把运行时和界面换到能再战十年的栈上。