by 雪隐_上班了 · 掘金主页
欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权。
WPF + MVVM 实战系列 · 导论
又名:一个老狗回炉重造 WPF 的 12 天血泪史
关键词:CommunityToolkit.Mvvm / EF Core / xUnit / Serilog / 独立部署
友情提示:本文含大量"我当初要是知道就好了"的真实吐槽,请谨慎饮用。
一、我为什么要把自己按回 WPF 的坑里
2026 年,我接了个正经活儿:一个企业级业务软件,技术栈:WPF。具体干啥的不能说,保密协议签了厚厚一摞。
问题是我上一次正儿八经写 WPF,还是 Windows 10 刚出的时候。那时候 MVVM 全靠手写 INotifyPropertyChanged,代码里全是 OnPropertyChanged() 的复制粘贴,跟打地鼠似的。
4 年没碰,突然要上项目,我脑内 WPF 模块的加载速度比 Win11 开机还慢。直接开干不是不行,但有三颗雷埋在我脚底下:
- WPF 的脑回路跟 WinForms 完全不在一个频道------WinForms 是"你点哪我动哪",WPF 是"你数据变了,UI 自己看着办"。这思维切换比左右手互搏还拧巴。
- 排期紧,踩坑成本高------正式项目里踩坑,那就是在老板的雷区蹦迪,回头改代码的时候测试小姐姐会拿眼刀剜你。
- MVVM 新工具我一个都不认识 ------什么
CommunityToolkit.Mvvm,什么源生成器,我还在用手写 INPC 的原始社会,人家已经开着收割机了。
所以我做了一个理智且怂 的决定:先练手 1 周 。原则就一条------不做玩具 Demo,只做正式项目的"微缩版沙盘"。把第一阶段最棘手的功能全预演一遍,但绝不碰任何业务敏感内容(我可不想在练手时就把正式数据搞出 Bug)。
于是就有了这个项目:
picture------ 一个 图片标注相册 。左边分组、中间看图、右边贴标签,三栏布局 + 画标注 + SQLite + 单元测试 + 独立发布。这哪是相册,这分明是正式项目的"代餐"。
二、为什么 MVVM 是 WPF 的"正宫娘娘"
WPF 天生自带 Binding / Command / DataTemplate 这三大法器。你如果不搞 MVVM,等于娶了个会做饭的老婆却天天点外卖------浪费核心能力,还把 WPF 用成 WinForms Plus,代码写得比老太太的裹脚布还难受。
MVVM(Model-View-ViewModel)三大好处,句句戳心:
1. View 和业务逻辑分手快乐
- View 只管"长啥样、点哪响应"
- ViewModel 管"业务咋跑、状态咋变"
- Model 管"数据长啥样"
你瞅瞅这 ViewModel,纯 C#,一个 WPF 控件都不引用:
csharp
public partial class ImageViewModel : ObservableObject
{
[ObservableProperty]
private string? _imagePath; // 就这一行,自动帮你生成 PropertyChanged,爽不爽?
[RelayCommand]
private async Task OpenImageAsync()
{
// 这里面全是业务逻辑,跟 UI 没半毛钱关系
}
}
再看 XAML,干净得像刚擦过的玻璃:
xaml
<Image Source="{Binding ImagePath}" />
<Button Command="{Binding OpenImageCommand}" Content="打开图片" />
ViewModel 说:"我改了。" View 说:"收到,我变。" 全程不用你手动赋值,这就是 data binding 的魔法。
2. ViewModel 能单测,终于不用开 UI 点点点了
ViewModel 不依赖控件,直接 dotnet test 就能测业务逻辑------比如"画完矩形之后坐标对不对"、"保存图片时会不会抛异常"。一行命令跑完 39 个测试,绿得发亮,比开十次程序手点靠谱多了。
3. 设计时支持,Blend 都能帮你预览
加上 d:DataContext,你改 XAML 时 Visual Studio 都能给你弹出属性提示,再也不用启动 F5 等编译半天才能看效果。
三、WPF 的 Binding 体系到底有多"霸道"
WPF 的 Binding 不是单向的数据→UI 管道,它是 双向、解耦、还能带转换器 的超级传送带:
xaml
<!-- 双向绑定,边打字边更新 ViewModel -->
<TextBox Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<!-- DataTrigger:数据一变,UI 自动换皮肤,比亲妈还贴心 -->
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding IsLoading}" Value="True">
<Setter Property="Content" Value="加载中..." />
</DataTrigger>
</Style.Triggers>
</Style>
<!-- Converter:数据到显示的无缝转换,比如价格变货币格式 -->
<TextBlock Text="{Binding Price, Converter={StaticResource CurrencyConverter}}" />
跟 WinForms 那种每次都要手动 textBox.Text = model.Name 相比,WPF 的 Binding 简直是自动洗碗机对比手洗------省下的时间够你摸鱼三小时。
但是! Binding 体系也是把双刃剑。当你写了 {Binding X} 结果 UI 上啥也没显示时,你根本猜不到是:
- X 根本没值?
- Binding 路径写错了?
- DataContext 链在哪个环节断了?
这种时候你就会怀念 WinForms 的 "老子直接赋值" 的简单粗暴。后续文章我会专门开一篇吐槽这些坑,敬请期待。
四、12 天我都干了什么(不是摸鱼,是真肝)
不画大饼,直接上硬核产出表:
| Day | 内容 | 关键产出(和内心 OS) |
|---|---|---|
| 1-2 | MVVM 三件套 | 主窗口跑通 [ObservableProperty] 和 [RelayCommand],终于不用手写 INPC 了,感动到哭 |
| 3-4 | 三栏布局 + EF Core SQLite | 相册增删改查玩得飞起,数据库文件乖乖躺在本地的 AppData 里 |
| 5-7 | 图像查看器 | 缩放/平移/旋转 + 缩略图条,连续 48 小时高强度压测,内存愣是没爆(我骄傲) |
| 8-9 | 标注工具 | 矩形/直线/箭头/文字,加选中/删除,画得比美图秀秀还顺手 |
| 10 | 标签 + 单元测试 | 39/39 测试全绿,那一刻我仿佛听到了代码在唱歌 |
| 11 | Serilog + 异常兜底 | 日志按天滚动,三种异常捕获全安排上,妈妈再也不怕我半夜被线上 Bug 叫醒了 |
| 12 | 独立部署 | dotnet publish 出 172 MB 的 self-contained exe,双击直接跑,用户不用装 .NET 运行时------爽 |
最终成果:
dotnet build零错误零警告(强迫症狂喜)dotnet test39/39 通过(绿色就是正义)- 发布版直接双击
picture.exe,哪怕你电脑是全新的 Windows 也能跑(前提是 64 位,别问我 32 位怎么办)
五、我踩过的坑,比你吃过的盐还咸(夸张了)
12 天里我翻了 5 次车,每次都是"我以为我知道,结果现实给我一巴掌":
IsHitTestVisible=False跟字面意思完全反着来 ------它让整个子树都不参与命中测试,而不是仅仅自己。我想让子控件还能点?对不起,门都没有。- DataTrigger 改高亮改不赢 ------因为
Local Value优先级高于Style Trigger,你要是直接在控件上写了背景色,Trigger 再怎么改都无效。这个优先级规则简直比公司晋升制度还复杂。 BitmapImage.DecodePixelWidth写在Image标签上无效 ------必须写在<BitmapImage>的内联属性里。我 TM 试了一个小时才发现,微软文档都不带这么坑的。- POCO 表达式属性 binding 缓存为 0 ------你改了
X1=10,但 binding 路径{Binding X}还是显示 0,因为它不知道你那个计算属性需要重新求值。这个问题我直接原地裂开。 [ObservableProperty]字段被 EF Core 当成表列 ------我加了个 UI 专用字段,结果 SaveChanges 报错说找不到列。最后乖乖加上[NotMapped],EF 大爷才放过我。
这些坑我后续都会单篇细讲(第 4 篇专门搞个"WPF 五宗罪"),这里先列个清单,省得你们再掉进去。
六、练手项目和正式项目到底啥关系?
我这个 picture 项目,跟那个正式项目不是 1:1 复制 ,而是 骨架复用 + 业务替换:
| 正式项目(业务功能) | 练手项目(picture) |
|---|---|
| 左侧列表(增删改查 + 搜索) | 左栏图片分组(同款增删改查) |
| 中央内容展示区(缩放/平移/旋转) | 中央图片查看(同款操作) |
| 标注工具(矩形/直线/箭头/文字) | 同款标注工具,画得飞起 |
| 业务评分 / 结论模块 | 图片打标签 + 简单统计,意思到了就行 |
| SQLite + EF Core | 同款数据库,一模一样 |
| 右侧功能面板 | 右栏标签面板,布局照搬 |
| 报告导出 | (可选)导出带标注的图片,也算导出 |
所以正式项目第一阶段做完了,基本就是 picture + 真实业务逻辑 + 加密狗授权。这套骨架我已经跑通了,后面就是换肉。
七、这个系列接下来要写啥(7 篇,篇篇干货)
- 导论(就是这篇):为啥 WPF + MVVM,12 天干了啥
- 三件套实操 :
CommunityToolkit.Mvvm入门,5 分钟写一个完整 View-ViewModel,包教包会 - 核心:什么放 ViewModel,什么放事件------4 问判别法,从此不再纠结
- WPF 必踩的 5 个坑 :从
IsHitTestVisible到[ObservableProperty]污染 EF,坑坑见血 - xUnit 入门:测什么 / 不测什么,WPF 项目的测试到底怎么玩
- 可测的纯逻辑设计:从 0 到 39/39 绿------Service 管 I/O,Pure 管逻辑,分层清清爽爽
- 从 0 到独立部署:172 MB self-contained exe 完整复盘,包含我的编译失败记录(反正你也看不全)
最后说句心里话:WPF 这老家伙虽然学习曲线陡,但一旦上手,写业务真的快。希望这个系列能让你少走我走过的弯路,少骂我骂过的娘。我们下一篇见,记得带好咖啡和降压药。☕️💊