WPF新手村教程(七)—— 终章(MVVM架构初见杀)

WPF个人文档(七)—— MVVM初见杀


# MVVM

View	    # ?
ViewModel	# ?
Model	    # ?

温馨提示:请不要企图一次就看懂MVVM架构,也不要妄图一次就明白MVVM,更不要妄想一次就可以上手开写
MVVM架构初期学习,需要反复看,反复学习,反复受折磨,反复骂街,然后接着去学......
别人学习一门架构,需要花数个月,甚至一两年,才能做到完全熟悉里面的每一个东西
所以你就不要想着,一两周甚至几天时间就可以学会并且熟练使用一门架构了
因此,这篇随笔,仅做对MVVM架构的初步了解,而不是去教会你怎么熟练使用
关于标题,初见杀,你以为杀的是谁,是我啊,也是你,
随笔的另一个目的,后面要是忘记了,我们也可以回来回顾回顾,和弄和弄

如果你是第一次接触前端架构,或者你以前没有搞过Web前端什么的,那么
恭喜你,一起一脸蒙圈的看着这MVVM架构吧

前期学习MVVM架构,大脑里面无时无刻浮现出宇宙三大终究问题:我是谁,我在哪,我从哪里来
因为MVVM其实是一门非常复杂的技术架构,涉及的知识点很多很多,但凡你对其中一项核心技术不甚了解
就会像过年的时候被亲戚灌酒喝断片一样——不对,刚刚发生什么了,我没走神吧,怎么一瞬间什么都不明白了
学习MVVM架构,最好去找一个小的教学Demo或者视频,如果你对其中的许多概念感到非常陌生
请去学习前置内容,我就不推荐你继续深入学习MVVM了,
毕竟,第一关都没有解锁,就单挑BOSS,你也想给BOSS修脚吗,那得修脚修到何年何月去了
(修脚就是字面意思,游戏里打下半身,一般说视野受限打不到上半身的情况所以修脚,在这里你可以理解为:
你把BOSS卡在一个角落,由于地形原因,他打不到你,然后你又只能打他的脚,但是你的伤害只有1,Boss有9999血)


零.引题

# 为什么我们需要MVVM架构?
# 如果不使用MVVM架构会产生什么问题?
  • 有人说MVVM架构是为了前后端分离,人不分离,
    换句话说,也就是业务层和界面分离,做到互不干扰,
    你也可以说是,将表现层和逻辑层分离,

  • 但总的来说,其实就是为了解耦,你也不想我写个业务逻辑(界面),还得去搞明白前端(后端)怎么写的吧

  • 所以为了避免业务逻辑和UI黏在一起,比强力粘鼠板还粘,于是MVVM架构诞生了

    • MVVM将 界面、逻辑、数据 彻底拆开,终于不用测试的时候还要烧纸祈祷了(bushi

附赠博客园讲的比较通俗易懂的一篇文章:
为什么要用MVVM - maanshancss - 博客园


  • 然后先来看几行简单的代码和超级简易的MVVM架构

    • 这里实现了一个非常简单的功能:点击按钮之后跳出一个弹窗
      但是会发现,事件交互逻辑和UI完完全全绑死了,
      如果我后面想要复用这个控件或者在其他页面代码中复用对应的事件逻辑,怎么办呢

    • <Grid>
          <StackPanel>
              <TextBox x:Name="txtname"/>
              <Button Height="100" Width="100" Click="Button_Click" Content="登录"/>
          </StackPanel>
      </Grid>
      
      private void Button_Click(object sender, RoutedEventArgs e)
      {
          string accout = txtname.Text;
          MessageBox.Show($"{accout}");
      }
      
      # 交互逻辑
      点击按钮
         ↓
       读控件
         ↓
      执行逻辑	# 事件Button_Click()
         ↓
       完成交互	# 弹窗
      
      
      

  • 现在我们用MVVM来实现一下,不懂没关系,先留下一个简单的印象

    • # 项目
      MVVM_Demo
      ├─ Views
      │  └─ MainWindow.xaml
      ├─ ViewModels	# 数据
      │  └─ MainViewModel.cs
      └─ Commands
         └─ RelayCommand.cs
      
      
    • 1.UI去掉 x:Name,绑定数据,引入数据ViewModel雏形

      由于现在的代码逻辑过于依赖UI控件,所以我们需要将数据和UI解耦,你们不能在一起 她是你妹妹

      // 现在
      string accout = txtname.Text;
      
      // 目标:实现UI和数据绑定
      	// TextBox.Text ←→ Account
      
      • 1)代码中加入属性

        • public string Account { get; set; }
          
      • 2)UI去掉 x:Name,绑定数据(这里以TextBox为例,Button同理)
        然后,别问我为什么混入了两种语言的注释.....

        • # 绑定 ViewModel
          <Window.DataContext>
              <vm:MainViewModel/>
          </Window.DataContext>
          
          <Grid>
              <StackPanel Margin="20">
                  <!-- 输入框 -->
                  # <TextBox x:Name="txtname"/>
                  <TextBox x:Name="txtname" Margin="0,0,0,10"
                          Text="{Binding Account, UpdateSourceTrigger=PropertyChanged}"/> 
          
                  <!-- 按钮 -->
                  <Button Content="登录" Height="40"
                          Command="{Binding LoginCommand}"/>
              </StackPanel>
          </Grid>
          

    • 2.核心 — UI自动刷新:实现绑定更新(这里依旧以TextBox为例)

      • public class MainViewModel : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler? PropertyChanged;
        
            // 绑定的数据
            private string _account = string.Empty;
            // 属性: get + set
            public string Account
            {
                get => _account;
                set
                {
                    _account = value;
                    OnPropertyChanged(nameof(Account));
                }
            }
        
            // 绑定更新
            void OnPropertyChanged(string propertyName)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        

    • 3.事件改命令Command(再次提醒:⚠命令不是事件的上位替代!!!)

      • 你问为什么要改啊,因为:MVVM 不允许 UI 调后台函数

      • <!-- 输入框 -->
        <TextBox x:Name="txtname" 
                 Text="{Binding Account, UpdateSourceTrigger=PropertyChanged}" />
        
      • public MainViewModel()
        {
            LoginCommand = new RelayCommand(Execute);
        }
        
        public ICommand LoginCommand { get; }
        
  • 附上完整示例代码:

    如果下面你遇到一些看不懂的代码,不要慌不要急,因为啊,私密马赛我也看不懂
    但是你要明白大致的内容和结构,不会的,随时随查
    我才学C#几个月啊,我要是可以这么短时间可以纯手搓MVVM,我就是真的天赋狗了

    • MainWindow.xaml

      • <Window x:Class="MVVM_Demo.MainWindow"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                xmlns:vm="clr-namespace:MVVM_Demo.ViewModels"
                mc:Ignorable="d"
                Title="这一定不是MVVM吧" Height="450" Width="800">
            <!-- 绑定 ViewModel -->
            <Window.DataContext>
                <vm:MainViewModel/>
            </Window.DataContext>
        
            <Grid>
                <StackPanel Margin="20">
        
                    <!-- 输入框 -->
                    <TextBox x:Name="txtname" Text="{Binding Account, UpdateSourceTrigger=PropertyChanged}" 
                             Margin="0,0,0,10"/>
        
                    <!-- 按钮 -->
                    <Button Content="登录"
                            Height="40"
                            Command="{Binding LoginCommand}"/>
        
                </StackPanel>
            </Grid>
        </Window>
        
    • MainViewModel.cs

      • using System.ComponentModel;
        using System.Windows;
        using System.Windows.Input;
        using MVVM_Demo.Commands;
        
        namespace MVVM_Demo.ViewModels
        {
            public class MainViewModel : INotifyPropertyChanged
            {
                public MainViewModel()
                {
                    LoginCommand = new RelayCommand(ExecuteLogin, CanExecuteLogin);
                }
        
                public event PropertyChangedEventHandler? PropertyChanged;
        
                // 密码
                private string _account = string.Empty;
                public string Account
                {
                    get => _account;
                    set
                    {
                        _account = value;
                        OnPropertyChanged(nameof(Account));
        
                        // 通知按钮状态变化
                        (LoginCommand as RelayCommand)?.RaiseCanExecuteChanged();
                    }
                }
        
                // 登录绑定命令
                public ICommand LoginCommand { get; }
        
                // 执行登录
                private void ExecuteLogin(object? obj)
                {
                    MessageBox.Show($"{Account}");
                }
        
                // 控制按钮是否可点
                private bool CanExecuteLogin(object? obj)
                {
                    return !string.IsNullOrWhiteSpace(Account);
                }
        
                // 绑定更新
                void OnPropertyChanged(string propertyName)
                {
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }
        
    • RelayCommand.cs

      • using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;
        using System.Threading.Tasks;
        using System.Windows.Input;
        
        namespace MVVM_Demo.Commands
        {
            public class RelayCommand : ICommand
            {
                private readonly Action<object?> _execute;
                private readonly Func<object?, bool>? _canExecute;
        
                public event EventHandler? CanExecuteChanged;
        
                public RelayCommand(Action<object?> execute, Func<object?, bool>? canExecute = null)
                {
                    _execute = execute;
                    _canExecute = canExecute;
                }
        
                // 能否启用
                public bool CanExecute(object? parameter)
                {
                    return _canExecute == null || _canExecute(parameter);
                }
        
                // 执行逻辑
                public void Execute(object? parameter) => _execute(parameter);
        
                // 手动刷新按钮状态
                public void RaiseCanExecuteChanged()
                {
                    CanExecuteChanged?.Invoke(this, EventArgs.Empty);
                }
            }
        }
        

    🕳盲生,你发现华点了吗?

    这里的示例代码中,Model 去哪了,不会是跑路了吧

    因为现在的代码比较简单,还没有到需要Model的程度
    但是这里可以直接丢出一个结论:Model = 数据 / 业务 层

    话说ChatGPT新版本更新之后,好像特别喜欢说,我先给出结论
    啊哈,我绝对找到了我是AI的证据


一.概念

1.Web前端架构的发展

这里,可以当作一个小故事来看,可以作为我们的思维扩展
但是由于,我学编程才两三年,而且最开始是嵌入式的,现在是搞上位机的
对Web了解很少,前端三件套也是一窍不通,所以我也讲不了这个,讲了可能也是干巴巴的
但是找到一个讲发展的视频,大家有兴趣可以去看看,摸鱼的时候随便看看就行啦
mvc,mvp,mvvm 的区别【up认为是讲得最好的】_哔哩哔哩_bilibili


2.MVVM概念

  • 老规矩,一张树状图一图流

    • UI层 ⇿ 控制层 ⇿ 数据层
      
      View(界面 - UI层)
         ⇵	# 绑定
      ViewModel(核心 - 控制层)
         ⇵	# 调用
      Model(数据/业务 - 数据层)
      
      
  • MVVM:一种 UI 架构模式,用来 解耦 UI 和非 UI 代码”,通过数据绑定实现同步(微软官方)

    • 实际上MVVM架构核心是分离关注点(Separation of concerns,SOC)

      • 分离关注点:一种通过分解复杂系统、分别处理不同功能模块的系统思维方法
      • 从软件设计上看,分层只是实践关注点的一种,常见的有应用层、业务层、领域层、资源层等,每一层关注的东西是不一样的,而这每一层关注的内容其实就是关注点,而SOC要做的就是让这些内容相互独立,互不干扰

      如果你不明白什么是SOC,可以去看看知乎的这一篇帖子
      软件架构之分离关注点 - 知乎

    • SOC一般有三个关注的问题点,而我也将围绕SOC讲解MVVM:
      (主要是不讲架构,难懂啊,而且写程序的谁不幻想成为一位架构师呢)

      • 关注点是什么?
      • 关注点如何实现的?
      • 如何分离关注点?

💫MVVM的关注点是什么?

关于MVVM的概念,我们可以换一种问法:MVVM的关注点有什么,他们分别是什么,都是负责什么的?

  • 我们先来看一下MVVM的英文全称:Model – View – ViewModel

    • 翻译一下就是:模型 – 视图 – 视图模型

      • View,顾名思义,就是UI层,也可以理解为前端负责的内容 虽然我们的前端兄弟已经在AI的战场死去了
      • Model,模型,负责的是我们的数据层或者业务层,也就是后端负责的内容 在中国你干C#默认全栈
      • Viewmodel,视图模型,说直接点,就是搭建UI层和数据业务层之间的桥梁,可以称它为控制层
    • 如果你还是理解不了,参考一下嵌入式系统,软件 - 嵌入式系统 - 硬件 交互

      • 如果软件和硬件直接交互,虽然看起来非常方便,但是一套软件不能匹配很多不同类型的硬件(兼容性)
        一套软件,要实施在不同的硬件设备上,需要修改很多东西,软件厂商需要非常高的成本
        但是,如果我们引入嵌入式系统,那就不一样了,软件厂家只需要去关注和嵌入式系统构建的联系
        自己就可以不再去关注设备兼容问题了,毕竟活全部丢给嵌入式系统了
    • MVVM架构同理,我们不再去关注前后端的数据交互了,而是去关注Model / View 和 ViewModel的交互

      • 示意图阐明了视图模型如何充当模型和视图之间的中介
    • 层级 关注点是什么? 具体职责示例
      View(视图/UI) 显示界面元素,接收用户输入 TextBox 显示内容,Button 响应点击,不处理业务逻辑
      ViewModel(视图模型/逻辑层) 管理 UI 状态,处理界面逻辑,调用业务 判断按钮是否可用,处理输入,调用 Model 执行登录验证
      Model(模型/数据业务层) 管理核心业务逻辑和数据 用户登录验证、数据结构定义(User、Order)、与数据库/API 交互

二.MVVM架构搭建

1.💫关注点如何实现的?

  • 与其问关注点是如何实现的,不如问 Model – View – ViewModel 三者之间是如何进行数据交互的

    • 上面放了一张来自微软官方文档的图片,简单的描述了一下两两之间的数据交互 但是,我们需要中文,再简单也要!

(1)View ↔ ViewModel(数据层和 UI 状态)

  • 🔧实现工具:数据绑定Binding + 依赖属性的改变回调PropertyChanged

    • <!-- 输入框 -->
      <TextBox x:Name="txtname" Text="{Binding Account, UpdateSourceTrigger=PropertyChanged}" 
               Margin="0,0,0,10"/>
      
      <!-- 按钮 -->
      <Button Content="登录"
              Height="40"
              Command="{Binding LoginCommand}"/>
      
      
  • 数据绑定让 UI 自动显示数据

  • INotifyPropertyChanged改变回调 让 ViewModel 控制层的数据变化通知 ViewUI层

忘记了依赖属性?没逝的,没逝的,我借来了多啦A梦的传送门
🚪:WPF新手村教程(二) - 铁匠铺攻略:如何给隔壁张铁匠带两块铁(依赖属性) - 假设狐狸有信箱 - 博客园


(2)View → ViewModel(用户操作:UI层触发数据层逻辑)

  • 🔧实现工具:Command(命令)

    • 用户点击按钮 → 命令触发 ViewModel 的方法

    • 替代传统的 Click 事件,保证逻辑和 UI 分离,然后——命令不是事件的上位替代!!!

      • <!-- 按钮 -->
        <Button Content="登录" Height="40"
                Command="{Binding LoginCommand}"/>
        

忘记了命令?没逝的,没逝的,这一次,我偷来了多啦A梦的传送门
🚪:WPF新手村教程(六)— 新手村BOSS战前准备(命令) - 假设狐狸有信箱 - 博客园


(3)ViewModel → Model(业务逻辑执行)

  • ViewModel 调用 Model 提供的接口或方法
  • Model 返回结果,更新 ViewModel 的属性 → 自动刷新 UI
// <TextBox Text="{Binding Account, UpdateSourceTrigger=PropertyChanged}" />
// <Button Command="{Binding LoginCommand}" />
// <TextBlock Text="{Binding ErrorMessage}" />

bool success = _userService.Login(Account, Password);
ErrorMessage = success ? "" : "账号或密码错误";

如果你忘记了——嗯......如果你忘记了属性,有逝的,有逝的,我抢来了多啦A梦的传送门
🚪:WPF新手村教程(一) - 走不出新手村别找我 - 假设狐狸有信箱 - 博客园
这推销自己东西的人是真可恶,对吧,还好我是🦊,不是人


2.💫🕳如何分离关注点?

  • 换个说法:我们怎么在MVVM架构中做到 禁止越界的准则?

  • 但是,实际写代码中,肯定不可能这么简单,但是,我并没有那么丰富的实际项目经验
    这一点,我没法补充啊.......写不出来,写出来不是误人子弟吗,过一年再来更新吧

    • 层次 禁止:不该做什么 允许:应该做什么
      View 写业务逻辑(如判断密码是否正确) 显示 UI 元素,绑定 ViewModel 属性,响应用户操作
      ViewModel 操作控件(如 TextBox.Text = "xxx") 管理 UI 状态,处理逻辑,调用 Model
      Model 调用 UI 或处理 UI 状态(如 MessageBox.Show) 管理数据和业务逻辑,提供接口给 ViewModel
  • 待更新???????????????


好了,你现在已经初步了解了MVVM架构,但是,我现在更推荐你去看一下官方的文档:
简介 - Training | Microsoft Learn
一共有7个小节,还有几道非常简单的练习题
看完之后,再来看看我剩下的内容吧 顺便我也去找一个详细的案例
虽然你应该也不会去看就是了,是我我也不想看微软官方那一堆不是给人类看的文档.......


三.其他几种架构的简单介绍(了解即可)

表格也由ChatGPT生成

架构/模式 核心思想 数据流 UI 与逻辑关系 状态管理 是否适合 WPF 优点 缺点
Code-Behind 事件驱动 双向/混乱 强耦合 分散 简单直接,上手快 难维护、难扩展
MVC 分层控制 双向 View依赖Controller 分散 经典架构,职责清晰 不适合WPF绑定机制
MVP Presenter中控 双向 View被Presenter控制 分散 ⚠️ 解耦比MVC好,可测试 冗余代码多
MVVM 数据绑定驱动 单向为主(近似) 解耦(Binding连接) 半集中(VM) ✅⭐⭐⭐⭐⭐ 最适合WPF,解耦强 学习成本高
MVU 状态驱动(函数式) 严格单向 完全解耦 完全集中 ⚠️ 状态清晰,不易出错 不符合传统OOP习惯
Qt信号槽 事件通信机制 任意 容易耦合 分散
————————
灵活、直观 无架构约束,易混乱

四.MVVM代码示例

  • 案例一半是 我写的,剩下一半是用ChatGPT优化的

  • 大致内容是,有一个显示框,一行一行显示用户信息(用户信息实时显示)
    你可以添加用户信息:用户名+年龄(添加用户信息)
    同时你也可以选择显示框中的一行数据,选择删除(删除用户信息)

  • DemoApp
    ├─ Models
    │  └─ User.cs
    ├─ ViewModels
    │  └─ MainViewModel.cs
    ├─ Commands
    │  └─ RelayCommand.cs
    └─ Views
       └─ MainWindow.xaml
    

1.视图层 — View

Views
   └─ MainWindow.xaml
  • <Window x:Class="MVVM_Examples.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:MVVM_Examples"
            xmlns:vm="clr-namespace:MVVM_Examples.ViewModels"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
    
        <!-- 数据上下文 -->
        <Window.DataContext>
            <vm:MainViewModel/>
        </Window.DataContext>
    
        <Grid Margin="10">
            <Grid.RowDefinitions>
                <RowDefinition Height="2*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
    
            <!-- 用户列表 -->
            <ListBox ItemsSource="{Binding Users}"
                     SelectedItem="{Binding SelectedUser}"
                     DisplayMemberPath="Name"/>
    
            <!-- 操作区 -->
            <StackPanel Grid.Row="1" Orientation="Vertical" Margin="0,10,0,0">
    
                <TextBlock Text="Name"/>
                <TextBox Text="{Binding NewName, UpdateSourceTrigger=PropertyChanged}"
                         Margin="0,2,0,5"/>
    
                <TextBlock Text="Age"/>
                <TextBox Text="{Binding NewAge, UpdateSourceTrigger=PropertyChanged}"
                         Margin="0,2,0,5"/>
    
                <StackPanel Orientation="Horizontal">
                    <Button Content="Add"
                            Width="100"
                            Command="{Binding AddCommand}"/>
    
                    <Button Content="Delete"
                            Width="100"
                            Margin="10,0,0,0"
                            Command="{Binding DeleteCommand}"/>
                </StackPanel>
            </StackPanel>
        </Grid>
    </Window>
    

2.数据层 — Model

Models
  └─ User.cs
  • using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace MVVM_Examples.Models
    {
        /// <summary>
        /// 用户数据类
        /// </summary>
        public class User
        {
            public string Name { get; set; }  = string.Empty;
            public int Age { get; set; } = 0;
        }
    }
    

3.命令基类(命令管理)— Command

 Commands
   └─ RelayCommand.cs
  • using System;
    using System.Windows.Input;
    
    namespace MVVM_Examples.Commands
    {
        public class RelayCommand : ICommand
        {
            private readonly Action<object> _execute;           // 执行命令的委托
            private readonly Func<object, bool> _canExecute;    // 判断命令是否可执行的委托
    
            public event EventHandler ?CanExecuteChanged;       // 当命令的可执行状态发生变化时触发
    
            /// <summary>
            /// 构造函数,接受执行命令的委托和判断命令是否可执行的委托
            /// </summary>
            /// <param name="execute"></param>
            /// <param name="canExecute"></param>
            public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
            {
                _execute = execute;
                _canExecute = canExecute;
            }
    
            /// <summary>
            /// 命令是否可执行
            /// </summary>
            public bool CanExecute(object parameter)
                => _canExecute == null || _canExecute(parameter);
    
            /// <summary>
            /// 执行命令
            /// </summary>
            /// <param name="parameter"></param>
            public void Execute(object parameter)
                => _execute(parameter);
    
            /// <summary>
            /// 触发 CanExecuteChanged 事件,通知 WPF 重新查询命令是否可执行
            /// </summary>
            public void RaiseCanExecuteChanged()
                => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
    

4.管理层 — ViewModel

  •  ViewModels
         └─ MainViewModel.cs
    
    • using MVVM_Examples.Commands;
      using MVVM_Examples.Models;
      using System.Collections.ObjectModel;
      using System.ComponentModel;
      using System.Windows.Input;
      
      namespace MVVM_Examples.ViewModels
      {
          public class MainViewModel : INotifyPropertyChanged
          {
              /// <summary>
              /// 实现 INotifyPropertyChanged 接口,通知 UI 属性值发生变化
              /// </summary>
              public event PropertyChangedEventHandler ?PropertyChanged;
      
              // 用户列表
              public ObservableCollection<User> Users { get; set; }
      
              // 当前选中的用户
              private User _selectedUser;
              public User SelectedUser
              {
                  get => _selectedUser;
                  set
                  {
                      _selectedUser = value;
                      OnPropertyChanged(nameof(SelectedUser));
      
                      (DeleteCommand as RelayCommand)?.RaiseCanExecuteChanged();
                  }
              }
      
              // 新用户的姓名和年龄输入
              private string _newName;
              public string NewName
              {
                  get => _newName;
                  set
                  {
                      _newName = value;
                      OnPropertyChanged(nameof(NewName));
                      (AddCommand as RelayCommand)?.RaiseCanExecuteChanged();
                  }
              }
      
              // 新用户的年龄输入,使用字符串以便验证输入
              private string _newAge = string.Empty;
              public string NewAge
              {
                  get => _newAge;
                  set
                  {
                      _newAge = value;
                      OnPropertyChanged(nameof(NewAge));
                      (AddCommand as RelayCommand)?.RaiseCanExecuteChanged();
                  }
              }
      
              // 添加和删除用户的命令
              public ICommand AddCommand { get; }
              public ICommand DeleteCommand { get; }
      
              public MainViewModel()
              {
                  // 初始化两个用户:列表和命令
                  Users = new ObservableCollection<User>
                  {
                      new User { Name = "Gura", Age = 20 },
                      new User { Name = "Lucy", Age = 22 }
                  };
      
                  AddCommand = new RelayCommand(AddUser, CanAddUser);
                  DeleteCommand = new RelayCommand(DeleteUser, CanDeleteUser);
              }
      
              // 添加用户的操作
              private void AddUser(object obj)
              {
                  if (!int.TryParse(NewAge, out var age) || age < 0)
                      return;
      
                  Users.Add(new User
                  {
                      Name = NewName.Trim(),
                      Age = age
                  });
      
                  NewName = string.Empty;
                  NewAge = string.Empty;
              }
      
              // 判断是否可以添加用户:姓名不能为空,年龄必须是非负整数
              private bool CanAddUser(object obj)
                  => !string.IsNullOrWhiteSpace(NewName)
                     && int.TryParse(NewAge, out var age)
                     && age >= 0;
      
              // 删除用户的操作
              private void DeleteUser(object obj)
              {
                  if (SelectedUser != null)
                      Users.Remove(SelectedUser);
              }
      
              // 判断是否可以删除用户:必须有选中的用户
              private bool CanDeleteUser(object obj)
                  => SelectedUser != null;
      
              // 通知 UI 属性值发生变化
              void OnPropertyChanged(string propertyName)
                  => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
          }
      }
      
      

参考资料:
1.模型-视图-视图模型 - .NET | Microsoft Learn
2.什么是 MVVM? - Training | Microsoft Learn
3.为什么要用MVVM - maanshancss - 博客园
4.Windows 数据绑定与 MVVM - Windows 应用 |Microsoft Learn
5.软件架构之分离关注点 - 知乎


翻阅了很多资料,总感觉找不到一个可以彻彻底底讲明白且适合萌新入门学习的教程
有的人真的很厉害,但是他的教程是真的烂,或者完全就是给那些非初学者至少有一两年经验的人准备的
关于这一篇,真的是写的我吐,我同事吐槽我,我感觉这几天你都快被MVVM给强*了
公司其实也没有多少人会MVVM架构,挺离谱的一件事情,我们公司写架构的人是个小老板......
最近一段时间用AI智能体重构了大学期间的一个C++项目,使用WPF-MVVM写的
愈发觉得,未来程序员的路,已经完完全全变了,
在这场AI大战中,我们亲爱的前端兄弟已经倒下了,接下来该我们的后端兄弟了,测试兄弟了.......
只不过——哈!干C#的,前后端不都是一个人吗
感觉现在程序员学习的一个出路就是,学习架构,从你的业务代码编写转向架构编写
学习架构,但是因为有AI的诞生,你可以不是完完全全的熟悉每一个环节了,
你可以向AI描述一个项目的架构,让他写出一个程序,并且记录每一次架构出现的问题
然后去寻找更好的方案,然后查缺补漏,缺什么补什么,去补充你的个人架构之道缺少的知识

最后嘛,这个MVVM新手教学也在此正式完结撒花了,虽然很多东西我还不是特别熟练
但是我也尽量用自己的语言总结了一下,可以给后来人提供一个教学参考
同时也是给未来的自己,一份自己的数据手册,只不过感觉学院派的味道有点浓啊
我宁愿相信自己的体系,也不愿意去相信别人组件的体系,因为那始终都是别人的
这只名叫MVVM的新手村BOSS,后面一定会以更加强大的姿态,变成我们路上的一个精英BOSS,
出现在我们面前——这莫名其妙的让我想起来了勇者斗恶龙

好了,废话不多说了,虽然已经写了不知道多少废话就是了.......
不管你是工作使用WPF,还是业余爱好,以及工作以外学习,都希望各位能够在这七篇帖子上有所收获

posted @ 2026-03-27 10:02  假设狐狸有信箱  阅读(406)  评论(1)    收藏  举报