尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从零手写WPF MVVM:核心三件套与五个典型坑

从零手写WPF MVVM:核心三件套与五个典型坑 平时带新人我一般会给他们扔一句话前面六篇你学完你只是会用WPF第七篇你学完MVVM你才算是能写WPF。这句话听起来有点标题党但实际带过项目的人都会有同感——前面学的XAML布局、控件、绑定、样式本质上都是在教你“怎么把界面画出来”而MVVM教的是“怎么把程序写下去”。这篇是新手村系列的最后一篇我会把MVVM里最容易让新人劝退的几个点掰开揉碎讲清楚包括INotifyPropertyChanged到底在干什么、Command和Click事件有什么本质区别、DataContext是怎么把View和ViewModel粘起来的以及我见过的新手在MVVM里最常翻车的五个细节。你可以把它当成一次“点灯式”的复盘学完之后C#那头你差不多就能正式脱离新手村了。1. 为什么新手村最后一关是MVVM先理解它解决的是哪种痛老实说MVVM并不是一个“必须会不会就写不了WPF”的东西。你完全可以照着事件驱动的老路子把按钮的Click、TextBox的TextChanged、ListBox的SelectionChanged全部在Code-behind里写一遍程序照样能跑而且跑的还很快。那为什么几乎所有WPF岗位面试都要问MVVM因为你不写MVVM项目活不过第一轮需求变更。1.1 事件驱动式开发的甜蜜与失控新手阶段写WPF最舒服的姿势是这样的窗口上拖一个按钮双击进去写private void Button_Click然后从控件取值、做计算、把结果塞回别的控件。两三年前我自己带的一个小工具一开始就三个按钮、两个输入框Code-behind代码不过一百行那叫一个清爽。等到功能加到十几个按钮、四五个弹窗、好几组联动输入事情就开始不对了。你会发现按钮的Click和TextBox的事件散落在窗口代码文件里的各个角落A控件改了个值B控件要跟着刷新你不得不在A的事件里写一句b.Text xxx反过来B的变更又影响了C的显示你又在B的事件里写一句c.Visibility ...。三四个控件之间来回耦合代码还是能跑的但是每一次改需求你都要把整个事件网络在脑子里重放一遍改完上一个联动下一个联动又炸了。那种感觉就像自己给自己织了一张蜘蛛网最后自己也被粘在了网上。事件驱动开发的失控期来得多快取决于界面复杂度增长的速度。做了两三年上位机或工具类软件的朋友应该都见过那种一万多行的MainWindow.xaml.cs。打开文件之后想定位一个功能逻辑只能靠搜索框搜到之后还得小心翼翼地在密密麻麻的事件方法里挪动生怕动错一行引发连锁反应。1.2 MVVM的匹配逻辑界面、状态、行为的三角关系MVVM的思路说白了很朴素把“界面长什么样”和“界面上发生了什么”彻底拆开。界面长什么样由XAML告诉你界面上的数据状态是什么由ViewModel告诉你界面上用户操作了某个东西之后程序该怎么反应由Command告诉你。View只负责显示和收集输入剩下的逻辑全部移到ViewModel里。这个三角关系里最核心的并不是什么高深技术而是一句边界声明View里不写逻辑ViewModel里不碰控件。这句话新手往往难以接受因为很多人会问“如果不碰控件那我怎么拿到TextBox里输入的文字怎么改ListBox的选中项”答案是让绑定机制去替你拿、替你做。你的ViewModel只关心“我有一个字符串属性叫UserName”至于这个字符串是被TextBox显示出来了还是被ComboBox选出来的ViewModel不需要知道。绑定就是那条看不见的输送带它把控件上的输入搬进属性把属性里的变化搬回控件。一旦接受了这个设定整个程序的逻辑就变成了可测试的纯C#代码。你不需要启动窗口、不需要点击按钮就能通过直接给ViewModel的属性赋值、调用命令方法验证业务逻辑对不对。这一点对后期维护的价值无可估量——代码里最贵的永远不是写出来的那一瞬间而是半年后改需求的那一天。1.3 新手常犯的心理误区不是“多了一个类”而是“换了一种组织代码的思维”我见过很多新人学MVVM时最纠结的一件事为什么要为这么简单的功能写这么多类一个登录窗口要写一个ViewModel类、一个登录命令类、再来一个继承INotifyPropertyChanged的基类这一通操作下来显示一个用户名的代码量比原来翻了三倍。这个困惑很正常但我想说的是——MVVM的第一份代码必然要比事件驱动啰嗦因为你在为后续的每一次改动省时间。换句话说MVVM这种写法不是“减少代码量”的它是“减少耦合度”的。我当时带的人里有一个很典型的转变过程。他一开始写MVVM怎么都不顺手每写一个窗口都觉得在绕远路。后来项目做到第三次需求变更他的ViewModel几乎没怎么大改只是加了两个属性和一个命令窗口代码完全没有动他才意识到这套思维方式的威力“原来我上一次花时间搭的这个结构是在为这一次改需求买单。”所以说到底MVVM不是让你一开始写得快而是让你三个月后改得动。2. MVVM三件套INotifyPropertyChanged、Command与DataContext的正确打开方式MVVM看起来概念很多但你真正要用起来的核心就三个东西属性通知、命令、数据上下文。把这三位理解透其它什么Messenger、依赖注入、框架之类的东西都是后话。这一节我先不去写一个完整项目先把这三个零件各自的功能边界说清楚顺便纠正一些我在新手代码里经常看到的概念性偏差。2.1 让属性会喊话INotifyPropertyChanged的真正意义很多教程管INotifyPropertyChanged叫“属性通知”这个叫法没问题但新手容易误解成“属性一有变化就会自动通知界面”。其实它没有自动的魔法。这个接口的完整协议是你的属性在值改变时主动去调用PropertyChanged事件告诉外界“我这个属性变了值已经更新了你们谁要刷新就抓紧刷新”。你不调用这个事件界面就永远不知道属性变了。把这句话翻译成容易理解的场景ViewModel里有一个属性叫Progress后台线程把进度从0算到了80。如果这个属性只是普通属性界面上的进度条永远停在0因为进度条只会在你主动通知的时候去读新值。所以我们必须写一段样板代码private int _progress; public int Progress { get _progress; set { if (_progress value) return; _progress value; OnPropertyChanged(nameof(Progress)); } }这里有两行容易被新手省略的细节。第一行if (_progress value) return是防止无效赋值触发多余通知性能小事主要能避免某些场景下陷入无意义的循环刷新。第二行OnPropertyChanged(nameof(Progress))是真正的关键它告诉绑定系统你该重新拉取这个属性了。我见过有人把通知事件名写错、写漏常见的就是nameof用成了字符串硬编码改属性名时忘了改字符串界面数据死活不刷新的问题一查一个准。后面我会专门讲这个坑。2.2 命令不是Click事件的马甲ICommand与CanExecute按钮的点击在MVVM里不是靠Click事件而是靠命令。命令的本质是一个对象它封装了“这个操作能不能执行”和“这个操作怎么执行”两个逻辑。ICommand接口正好就是这两个问题的指针public interface ICommand { event EventHandler CanExecuteChanged; bool CanExecute(object parameter); void Execute(object parameter); }CanExecute决定按钮可不可点Execute决定点了之后干什么。这个设计最大的好处是按钮的可用状态不再是你在代码里btnSave.IsEnabled false这样一句句手动设置的而是由ViewModel根据当前数据状态实时算出来的。比如用户没填用户名保存按钮就自动置灰填了才亮。这个“自动”是WPF绑定系统替你调用了CanExecute之后的结果。很多新手会写一个假的命令——也就是命令方法里只调了一下普通逻辑CanExecute永远返回true然后把按钮的Command属性绑上去。这当然可以工作但它绕过了命令最值钱的半壁江山。你等于还是在用Click事件的思维写命令只不过换了个壳。真正的Command用法会在后面的例子里详细展开。2.3 DataContext把两个世界粘在一起的那桶胶水DataContext这个概念是新手阶段最容易懵的地方之一。它的作用简单粗暴给XAML中的{Binding}提供一个默认的数据来源。你在XAML里写{Binding UserName}系统会沿着控件树往上找DataContext找到的那个对象的UserName属性就是绑定的目标。这个“沿着控件树往上找”的特性很重要因为子控件默认继承父级的DataContext。所以你只需要在Window层设置一次DataContext整个窗口内的所有绑定就都有数据源了。我个人调试的时候喜欢在脑子里把DataContext想成一块“挂牌”。你把ViewModel这个牌子挂到Window上Window下面的所有子控件都自动认这个牌子你在某个Grid上又挂了自己的牌子那Grid内部的控件就优先认Grid上这块牌子。这个思维模型基本能解释绝大多数绑定诡异问题——一旦绑定没数据显示就先问自己这个地方往上数最近的那块牌子是它想要的数据吗2.4 一张表说清Model、View、ViewModel各自该干与不该干的事MVVM分层新手容易走向两个极端要么所有东西都塞进ViewModel把ViewModel变成一个新的“Code-behind”要么一个界面一个Model把Model和ViewModel搅成一锅粥。为了把边界说透我做了一张我培训时最常用的职责表层级该做的事不该做的事View布局控件、定义样式、绑定属性与命令、处理纯粹的界面动画写业务逻辑、操作数据库、直接访问其他窗口的控件ViewModel暴露界面所需的数据属性、定义命令、协调Model层数据与界面状态的转换引用任何View类型、操作具体控件如TextBox.Text xxx、直接弹出消息窗Model定义业务实体的数据结构、数据的存取规则、业务状态的核心算法引用WPF相关类型、包含包含布尔值怎么显示成颜色的界面状态逻辑把这张表记住之后写代码时每次写完一行的归属就能大致判断出来。Model不碰WPF类型是一个容易被忽视但非常重要的规定——一旦Model里引用了System.Windows.Visibility或者Brush这个Model就再也无法脱离UI层单独做单元测试了。ViewModel虽然可以不引用具体控件但对于转换本身还是需要用到WPF类型比如把bool转成Visibility。真要做到极致解耦还会引入ValueConverter来帮你做显示转换不过新手阶段先把主体边界守住即可不必一开始就把体系搭得非常重。3. 手写一个最简MVVM从空白窗口到可运行的双向同步说再多概念都不如撸一个小项目来得通透。这一节我们什么框架都不用纯手工从零搭一个最简单的MVVM例子一个待办事项输入框一个“添加”按钮下面一个列表显示所有待办。这个例子麻雀虽小但属性通知、命令、CanExecute、集合绑定全都覆盖到了你跑通这一遍后面套任何框架都轻松。3.1 新建项目时如果发现WPF模板不见了怎么处理进实操第一步就可能卡人好多人装了VS2022之后新建项目发现C#分类下根本没有WPF应用程序模板。这不是你的VS坏了一般是安装VS的时候没勾选“使用.NET 桌面开发”工作负载。处理办法有两种第一种是打开Visual Studio Installer点你已安装版本右侧的“修改”在“工作负荷”里勾上.NET 桌面开发然后点右下角修改等它装完重启VS模板就回来了。第二种图省事的话可以直接新建一个控制台项目手动把UseWPF打开改成true加上MainWindow.xaml几个文件也能跑但没必要还是建议走正常模板安装的路子。这个话题也在很多搜索引擎的热搜里挂着顺带提一句省得有人卡在第一步。3.2 项目中ViewModel基类与命令类的落地代码MVVM里有个不成文的约定ViewModel基类通常叫ViewModelBase和命令类通常叫RelayCommand或DelegateCommand是每个项目都要往工程里带的“基础设施”。你完全不用自己从零发明网上大把现成的实现但我的建议是新手先亲手抄一遍别直接装NuGet包只有亲手写过一遍才知道那几行代码到底在干什么。先看基类using System.ComponentModel; using System.Runtime.CompilerServices; public class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetPropertyT(ref T storage, T value, [CallerMemberName] string propertyName null) { if (Equals(storage, value)) return false; storage value; OnPropertyChanged(propertyName); return true; } }这里有两个地方很容易被忽略。第一个是[CallerMemberName]编译器会自动把调用方法的属性名作为字符串穿进来这样你写SetProperty(ref _userName, value)的时候就不用手写属性名字符串以后重命名属性的时候也不会留一个过期的字符串引用。第二个是SetProperty返回的bool值——不要小看它它让你可以在属性setter里知道这次的赋值到底有没有产生变化从而做进一步的联动判断。然后是命令类using System; using System.Windows.Input; public class RelayCommand : ICommand { private readonly Actionobject _execute; private readonly Predicateobject _canExecute; public RelayCommand(Actionobject execute, Predicateobject canExecute null) { _execute execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute canExecute; } public bool CanExecute(object parameter) _canExecute null || _canExecute(parameter); public void Execute(object parameter) _execute(parameter); public event EventHandler CanExecuteChanged { add CommandManager.RequerySuggested value; remove CommandManager.RequerySuggested - value; } }这个版本是全网流传最广的经典实现后面我会专门聊CanExecuteChanged和CommandManager.RequerySuggested之间的关系因为这块是新手在MVVM里最容易想当然的隐藏坑。现在你先把这个类抄进项目里当作工具箱中的扳手备着。3.3 业务ViewModel把三件套串起来的核心逻辑这次的业务场景是待办列表那Model层我们就不单独再建类了直接用一个字符串集合存数据就行。真正的主角是ViewModel。我在写这个类的时候故意把逻辑写得稍微“胖一点”这样更能展示MVVM的日常操作形态但又不会胖到拿Model当摆设。using System.Collections.ObjectModel; using System.Windows.Input; public class TodoViewModel : ViewModelBase { private string _newTodoText; public string NewTodoText { get _newTodoText; set SetProperty(ref _newTodoText, value); } public ObservableCollectionstring Todos { get; } new ObservableCollectionstring(); public ICommand AddTodoCommand { get; } public TodoViewModel() { AddTodoCommand new RelayCommand( execute: _ AddTodo(), canExecute: _ !string.IsNullOrWhiteSpace(NewTodoText)); } private void AddTodo() { Todos.Add(NewTodoText.Trim()); NewTodoText string.Empty; } }我特别想提醒的是这个类里的一个细节AddTodoCommand的canExecute引用了NewTodoText属性但NewTodoText变化时WPF默认并不会主动重新查询所有命令的CanExecute那为什么“输入框中没字时按钮置灰、一输入文字按钮就亮”还是能生效呢答案藏在上一节的RelayCommand实现里CanExecuteChanged事件嫁接给了全局的CommandManager.RequerySuggested而WPF会周期性地通常在界面交互、焦点变化、输入发生等时机广播这个事件要求所有命令重新查一次自己的CanExecute。所以你不需要手动写任何代码按钮状态就会跟着输入框内容刷新。这个机制既是好消息也是坏消息后面第4章的第三个坑我详细展开说。3.4 XAML端绑定写绑定之前先问自己三个问题ViewModel写完接下来让View认这个ViewModel。在Window上挂DataContext有几种方式我推荐新手最先掌握的是在构造函数里赋值写起来最直观public MainWindow() { InitializeComponent(); DataContext new TodoViewModel(); }这是一种顺手但不算最“优雅”的挂法等以后用了MVVM框架一般会通过ViewModelLocator或者依赖注入在更高层的地方完成绑定但新手阶段先从这个学起理解成本最低。接下来是XAML里的三个控件StackPanel Margin20 TextBox Text{Binding NewTodoText, UpdateSourceTriggerPropertyChanged} / Button Content添加 Command{Binding AddTodoCommand} Margin0,10,0,0 Padding10,5 / ItemsControl ItemsSource{Binding Todos} Margin0,10,0,0 / /StackPanel写这个XAML之前我建议每个绑定都先问自己三个问题绑定源是什么绑定路径找得到吗源属性变化时界面需不需要主动刷新以第一行Text{Binding NewTodoText, UpdateSourceTriggerPropertyChanged}为例绑定源就是DataContext也就是TodoViewModel路径就是NewTodoText刷新时机则是每次按键立即把输入框内容写回源属性。如果不写UpdateSourceTriggerPropertyChangedTextBox默认失焦时才回写那“输入一个字立即判断按钮能否可用”的效果就会延迟到失焦才生效看起来像按钮状态坏了。ItemsControl的ItemsSource绑定也一样Todos是一个ObservableCollectionT这个集合有个特殊本事它在添加、删除元素时会主动通知UI重新拉取列表内容这正是WPF里列表绑定的默认选择。新手有个很常见的错误是用ListT去代替ObservableCollectionT那界面是永远不会因为你往里Add新元素而自动多出一行的。这个知识点在这个例子里第一次被真正用到我建议你跑起来之后再故意把ObservableCollection换成List试一遍看效果一次就能记住。4. 新手最容易在MVVM里翻车的五个细节代码跑通了只是第一步。我在过去几年带人用WPF的过程中发现有一批错误几乎是所有初学者都逃不掉的。有的错误是界面数据空白有的错误是按钮永远亮不起来还有的错误是改了一个属性导致几十个地方崩盘。我下面把这五个最典型的坑一次性列全每个都给出症状、根因和修复手段你以后遇到可以直接对号入座。4.1 DataContext写错位置内容变空白时的第一排查点界面绑定不上、数据一片空白的场景大概能排进WPF新手问题Top 3。排查路径其实很有规律先看输出窗口OutputWPF运行时会输出类似BindingExpression path error: UserName property not found的警告。这类警告出现后基本可以锁定是DataContext的问题。常见情况有三个一是你压根没赋DataContext二是赋错了对象比如把窗口本身赋给了DataContext然后绑定了一个ViewModel上的属性名窗口上自然找不到三是在子控件上挂错了DataContext导致上面的控件沿着控件树找数据源时找到了一个不对的东西。有一种比较隐蔽的情况是你在某个Grid上设置了DataContext然后里面有个子窗口或者弹窗是独立创建的它的DataContext并不会自动继承父窗口。举个例子你写了一个ChildWindow想在它上面显示TodoViewModel里的同一个属性但你忘了给它赋值DataContext那这个子窗口里的所有绑定都是空的。这种情况在开发中比想象中频繁得多尤其是用ShowDialog弹窗的时候。4.2 PropertyChanged的字符串手滑小改动引来连锁反应接着第2章的坑往深了说。我见过一个真实的事故一个人把ViewModel里的属性从UserName改成DisplayName所有XAML里的绑定都改过来了结果就漏了setter里那行OnPropertyChanged(nameof(UserName))没改。程序编译不报错、运行不报错就是界面上的用户名再也不刷新了。调试了一下午最后发现是通知字符串和属性名对不上。这种坑最好的解法就是永远不要手写属性名字符串全部用nameof表达式或者像我前面写的SetProperty配合[CallerMemberName]自动生成。一旦你把所有通知都改成编译期检查的写法这种“手滑改名”问题就绝迹了。如果你接手了别人的老代码里面大量硬编码字符串我的建议是别急着全重构项目稳定为先但以后自己新写的代码一定要用安全写法。4.3 CanExecute不刷新界面按钮置灰后“永不超生”前文提到RelayCommand把CanExecuteChanged嫁接到了CommandManager.RequerySuggested上这个方案在大多数场景下是“够用”的但它不是“万能”的。最典型的问题场景是按钮的CanExecute依赖的并不是界面输入变化而是一个后台异步任务的结果。比如你的按钮一开始是禁用的你在后台线程里跑了一个耗时检查检查结束时把IsReady设为true这时候你发现按钮还是灰的——因为CommandManager.RequerySuggested多半在你切换焦点、点击界面的时候才会广播后台线程修改属性并不会主动触发重查。这个现象的经典形容就是按钮像被点了死穴再也亮不起来。要解决这个坑方法很简单把RelayCommand稍微升级一下让它支持手动触发CanExecuteChanged。常见做法是在RelayCommand里加一个公共方法比如叫RaiseCanExecuteChanged()事件引用改成直接用CanExecuteChanged?.Invoke(this, EventArgs.Empty)这样的话你在网络请求回调或异步任务结束时手动调用AddTodoCommand.RaiseCanExecuteChanged()界面按钮状态就会立即刷新。不过这里有个新的注意点WPF默认不允许在非UI线程直接操作界面元素所以这个手动通知要么确保在UI线程上调用要么用Dispatcher封一层。新手阶段写到异步时尤其要把这两点同步记牢。4.4 把业务逻辑塞进错误层级ViewModel膨胀与View里的“偷袭”MVVM用一段时间后最常见的坏味道就是ViewModel开始无限膨胀。今天加一个属性做计算明天加一个命令跑数据加载后天再放一个集合放下拉框选项三个月后ViewModel变成了一千多行的“二把手Code-behind”。出现这个信号时不要急于“重构”而是先按旧代码里职责最重的那块功能试水拆分把纯业务逻辑下沉到Model或独立的ServiceViewModel只保留“当前界面状态”与“用户操作意图”的翻译工作。另一个反向坏味道也经常看到有人不适应MVVM在新项目里仍然在Code-behind里写了一段this.Canvas.MouseLeftButtonDown ...的代码从XAML后门绕了进来。这种“偷袭式”写法最麻烦的地方在于它破坏了绑定树的上下文导致ViewModel无从得知这段逻辑的存在后面人接代码时稍不留神就会漏看。我处理这类情况的原则很简单除非是与View生命周期强相关的代码比如窗口加载动画、或者需要操作视图树本身的特效否则一律不允许出现在Code-behind里。新手可以从头就立下这个规矩能省很多沟通成本。4.5 调试绑定的终极武器输出窗口、FallbackValue与实时验证最后一个细节不是某个具体错误而是整套排查思路。绑定不生效的时候新手的第一反应往往是在代码里瞎猜乱试我这里提供一条更高效的链路。第一步打开VS的输出窗口选择“调试”来源程序跑起来以后所有绑定失败都会有一行System.Windows.Data Error: 40之类的记录里面的信息会精确到是哪个绑定路径找不到、哪个转换器报错。第二步在XAML里给绑定临时加一个FallbackValue???或者TargetNullValueNaN让绑定失败时界面能显式显示一个占位字符这样你就能从视觉上立刻区分是“数据源为空”还是“绑定路径错误”。第三步如果第二步还看不明白就在ViewModel属性的getter里打一个断点运行后看它到底有没有被读取、被谁读取、值是多少。这三步下来绝大多数绑定问题都能在十分钟内定位。5. 从“跑通”到“架构”MVVM之后你该怎么继续走前面这些内容里我完全没有提Prism、MVVMLight、CommunityToolkit.Mvvm之类的框架。这不是因为它们不重要而是以我带新人的经验来看框架必须建立在“手工分层熟练”的基础上才学得扎实。你不亲手写过一遍一万行左右的灭驱动代码不亲手体会过从事件驱动迁移到MVVM的痛你就算装了Prism也只会把它当成装饰品。5.1 先别急着上Prism把手工分层练到条件反射什么叫条件反射就是你写完一个需求之后不需要停下来思考就能下意识地判断这几个状态码应该放Model还是ViewModel这个用户点击应该定义一个Command还是普通的属性和方法这个界面上的列表是否应该封装成ObservableCollection。如果这些判断还要犹豫那你上框架只会更懵。我经常对来问我“要不要现在学Prism”的新人说一句话你首先得有“拆”的能力然后才能体会框架替你做的那部分“组装”到底省在了哪里。Prism引入区域Region、导航Navigation和依赖注入之后整个项目的结构复杂度是手工MVVM的好几倍。我见过真的有人在新手阶段就硬上Prism结果连模块加载都理不清最后项目变成了一堆管不住生命周期的Region容器的集合比不用框架时更难维护。顺带说一个我在网上看到的提问“Prism在弹出用户控件内定义的Region注册不上”。这种问题十有八九是因为RegionManager的实例在弹窗上下文里和主窗口不是同一个或者弹窗的视图还没有被注册到Region中就被尝试导航。这种边界问题在你对Region和依赖注入没有基本理解的时候排查起来会非常痛苦。所以我的建议是Manual分层至少保持三个项目再考虑上框架。5.2 什么时候可以判断自己准备好迁移到框架了判断信号其实很具体。当你的手工MVVM项目里出现了这些烦恼中的任何一种就是该看框架的时机第一你的ViewModel构造函数的参数越来越多你还得手动一个个new希望有一个容器能统一管理第二你的窗口和弹窗导航不用只靠ShowDialog和Close希望有带参数的导航机制第三你的消息交互频繁比如一个子窗口改了数据要让好几个ViewModel同时知道你不想再手写事件注册和注销逻辑想用事件聚合器。这时候再去看Prism的模块化、导航、对话服务这些功能你会理解得飞快。5.3 实质上这套结构思想不仅仅是WPF的专属我最后想补一句可能有些超纲但很实用的话MVVM这套思想说白了就是“界面与逻辑解耦”的具体实践而一旦你把这个思维模式练出来了你会发现它不止适用于WPF。你把ViewModel换成BindableObject就能把同样的思路搬到MAUI、WinUI甚至Avalonia上你把它里的Model换成后端Service层的DTO同样的分层理念能帮助你写任何客户端代码都保持结构清晰。这也是为什么很多招聘JD上WPF岗位都要求MVVM的原因——他们真正要找的不是会写XAML的人而是能写出长期可维护客户端代码的人。写到这儿我的建议已经全部给到了。这个系列到终章剩下的路只能自己走多用几个真实项目去验证你在MVVM里吸收到的每一个概念。若有一日你翻到一年前自己写的ViewModel觉得当时的分层满是笨拙别沮丧那恰恰说明你已经在离开新手村的路上了。
返回列表