WPF + MVVM 实战系列01-一个老狗回炉重造 WPF 的 12 天血泪史

by 雪隐_上班了 · 掘金主页

欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权。


WPF + MVVM 实战系列 · 导论

又名:一个老狗回炉重造 WPF 的 12 天血泪史

关键词:CommunityToolkit.Mvvm / EF Core / xUnit / Serilog / 独立部署

友情提示:本文含大量"我当初要是知道就好了"的真实吐槽,请谨慎饮用。


一、我为什么要把自己按回 WPF 的坑里

2026 年,我接了个正经活儿:一个企业级业务软件,技术栈:WPF。具体干啥的不能说,保密协议签了厚厚一摞。

问题是我上一次正儿八经写 WPF,还是 Windows 10 刚出的时候。那时候 MVVM 全靠手写 INotifyPropertyChanged,代码里全是 OnPropertyChanged() 的复制粘贴,跟打地鼠似的。

4 年没碰,突然要上项目,我脑内 WPF 模块的加载速度比 Win11 开机还慢。直接开干不是不行,但有三颗雷埋在我脚底下:

  1. WPF 的脑回路跟 WinForms 完全不在一个频道------WinForms 是"你点哪我动哪",WPF 是"你数据变了,UI 自己看着办"。这思维切换比左右手互搏还拧巴。
  2. 排期紧,踩坑成本高------正式项目里踩坑,那就是在老板的雷区蹦迪,回头改代码的时候测试小姐姐会拿眼刀剜你。
  3. 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 test 39/39 通过(绿色就是正义)
  • 发布版直接双击 picture.exe,哪怕你电脑是全新的 Windows 也能跑(前提是 64 位,别问我 32 位怎么办)

五、我踩过的坑,比你吃过的盐还咸(夸张了)

12 天里我翻了 5 次车,每次都是"我以为我知道,结果现实给我一巴掌":

  1. IsHitTestVisible=False 跟字面意思完全反着来 ------它让整个子树都不参与命中测试,而不是仅仅自己。我想让子控件还能点?对不起,门都没有。
  2. DataTrigger 改高亮改不赢 ------因为 Local Value 优先级高于 Style Trigger,你要是直接在控件上写了背景色,Trigger 再怎么改都无效。这个优先级规则简直比公司晋升制度还复杂。
  3. BitmapImage.DecodePixelWidth 写在 Image 标签上无效 ------必须写在 <BitmapImage> 的内联属性里。我 TM 试了一个小时才发现,微软文档都不带这么坑的。
  4. POCO 表达式属性 binding 缓存为 0 ------你改了 X1=10,但 binding 路径 {Binding X} 还是显示 0,因为它不知道你那个计算属性需要重新求值。这个问题我直接原地裂开。
  5. [ObservableProperty] 字段被 EF Core 当成表列 ------我加了个 UI 专用字段,结果 SaveChanges 报错说找不到列。最后乖乖加上 [NotMapped],EF 大爷才放过我。

这些坑我后续都会单篇细讲(第 4 篇专门搞个"WPF 五宗罪"),这里先列个清单,省得你们再掉进去。


六、练手项目和正式项目到底啥关系?

我这个 picture 项目,跟那个正式项目不是 1:1 复制 ,而是 骨架复用 + 业务替换

正式项目(业务功能) 练手项目(picture)
左侧列表(增删改查 + 搜索) 左栏图片分组(同款增删改查)
中央内容展示区(缩放/平移/旋转) 中央图片查看(同款操作)
标注工具(矩形/直线/箭头/文字) 同款标注工具,画得飞起
业务评分 / 结论模块 图片打标签 + 简单统计,意思到了就行
SQLite + EF Core 同款数据库,一模一样
右侧功能面板 右栏标签面板,布局照搬
报告导出 (可选)导出带标注的图片,也算导出

所以正式项目第一阶段做完了,基本就是 picture + 真实业务逻辑 + 加密狗授权。这套骨架我已经跑通了,后面就是换肉。


七、这个系列接下来要写啥(7 篇,篇篇干货)

  1. 导论(就是这篇):为啥 WPF + MVVM,12 天干了啥
  2. 三件套实操CommunityToolkit.Mvvm 入门,5 分钟写一个完整 View-ViewModel,包教包会
  3. 核心:什么放 ViewModel,什么放事件------4 问判别法,从此不再纠结
  4. WPF 必踩的 5 个坑 :从 IsHitTestVisible[ObservableProperty] 污染 EF,坑坑见血
  5. xUnit 入门:测什么 / 不测什么,WPF 项目的测试到底怎么玩
  6. 可测的纯逻辑设计:从 0 到 39/39 绿------Service 管 I/O,Pure 管逻辑,分层清清爽爽
  7. 从 0 到独立部署:172 MB self-contained exe 完整复盘,包含我的编译失败记录(反正你也看不全)

最后说句心里话:WPF 这老家伙虽然学习曲线陡,但一旦上手,写业务真的快。希望这个系列能让你少走我走过的弯路,少骂我骂过的娘。我们下一篇见,记得带好咖啡和降压药。☕️💊

相关推荐
在世修行2 小时前
从零打造 C# 工业视觉检测系统(四):海康威视 MVS SDK 入门与相机枚举实战
数码相机·c#·视觉检测
小王同学66667 小时前
Django 5 中实现文件上传功能
数据库·django·sqlite
czhc11400756637 小时前
8.4:今日概念与语法总结
c#
大数据张老师7 小时前
Typora 导出 Word 模版操作手册
开发语言·c#·word·typora
我是苏苏8 小时前
C#基础:使用System.Speech.Synthesis离线播放/朗读文字内容
开发语言·c#
程序员-Benothing10 小时前
如何实现高并发系统订单30分钟自动关闭?
开发语言·c#·wpf
海盗12341 天前
微软技术周报 ——2026-08-03
后端·python·microsoft·c#·.netcore
Ming_studying1 天前
Python + SQLite FTS5 构建本地文档全文搜索器:增量索引、中文检索与高亮
jvm·数据库·python·sqlite
dalong101 天前
WPF:3D甜甜圈
3d·c#·wpf·甜甜圈