C#之MVVM篇快速入门

MVVM(Model-View-ViewModel)模型-视图-视图模型

  • 参考网址: https://www.cnblogs.com/hsiang/p/15579839.htmlhttps://www.cnblogs.com/mingupupu/p/18218027
- 【模型】指的是后端传递的数据
- 【视图】指的是前端页面
- 【视图模型】mvvm模式的核心,它是连接view和model的桥梁
	- 将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。
	- 将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。
	- 这两个方向都实现的,我们称之为数据的双向绑定。
  • 新建WPF应用(.FW)程序
  • 安装MvvmLight插件
- 项目名称右键-->管理NuGet程序包-->搜索MvvmLight-->安装 # v5.4.1.1
  • 注意事项,此插件安装以后,需要删除两处代码
// app.xaml
......
    <ResourceDictionary>
      <!--注释掉这句(插件自动生成)-->
      <!--<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr-namespace:WpfAppMVVMFWDemo1.ViewModel" />-->
    </ResourceDictionary>
......

- 删除ViewModel目录(插件自动生成)底下的"ViewModelLocator.cs"文件
  • 创建Model目录,新建Student类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfAppMVVMFWDemo1.Model
{
    public class Student
    {
   
        // 唯一标识
        public int Id { get; set; }


        // 学生姓名
        public string Name { get; set; }


        // 年龄
        public int Age { get; set; }


        // 班级
        public string Classes { get; set; }
    }

}

  • 创建DAL目录,新建LocalDb类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfAppMVVMFWDemo1.Model;

namespace WpfAppMVVMFWDemo1.DAL
{
    public class LocalDb
    {
        private List<Student> students;

        public LocalDb()
        {
            init();
        }


        // 初始化数据
        private void init()
        {
            students = new List<Student>();
            for (int i = 0; i < 30; i++)
            {
                students.Add(new Student()
                {
                    Id = i,
                    Name = string.Format("学生{0}", i),
                    Age = new Random(i).Next(0, 100),
                    Classes = i % 2 == 0 ? "一班" : "二班"
                });
            }
        }


        // 查询数据
        public List<Student> Query()
        {
            return students;
        }


        // 按名字查询
        public List<Student> QueryByName(string name)
        {
            return students.Where((t) => t.Name.Contains(name)).ToList();//FindAll((t) => t.Name.Contains(name));
        }

        public Student QueryById(int Id)
        {
            var student = students.FirstOrDefault((t) => t.Id == Id);
            if (student != null)
            {
                return new Student()
                {
                    Id = student.Id,
                    Name = student.Name,
                    Age = student.Age,
                    Classes = student.Classes
                };
            }
            return null;
        }



        // 新增学生
        public void AddStudent(Student student)
        {
            if (student != null)
            {
                students.Add(student);
            }
        }


        // 删除学生
        public void DelStudent(int Id)
        {
            var student = students.FirstOrDefault((t) => t.Id == Id); //students.Find((t) => t.Id == Id);
            if (student != null)
            {
                students.Remove(student);
            }

        }
    }


}

  • 主窗体MainWindow内容如下
// xaml
<Window x:Class="WpfAppMVVMFWDemo1.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:WpfAppMVVMFWDemo1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="80" />
            <!--"*"表示会占据剩余所有可用空间-->
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal" Margin="5" VerticalAlignment="Center">
            <TextBlock Text="姓名" Margin="10" />
            <TextBox x:Name="sname" Text="{Binding Search}" Width="120" Margin="10" Padding="5" />
            <Button x:Name="btnQuery" Command="{Binding QueryCommand}" Content="查询" Margin="10" Padding="5" Width="80" />
            <Button x:Name="btnReset" Content="重置" Margin="10" Padding="5" Width="80" Command="{Binding ResetCommand}" />
            <Button x:Name="btnAdd" Content="创建" Margin="10" Padding="5" Width="80"  Command="{Binding AddCommand}" />
        </StackPanel>

        <DataGrid Grid.Row="1" x:Name="dgInfo" ItemsSource="{Binding GridModelList}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserSortColumns="False" Margin="10" >
            <DataGrid.Columns>
                
                <DataGridTextColumn Header="Id" Width="100"  Binding="{Binding Id}" />
                <DataGridTextColumn Header="姓名" Width="100"  Binding="{Binding Name}" />
                <DataGridTextColumn Header="年龄" Width="100"  Binding="{Binding Age}" />
                <DataGridTextColumn Header="班级" Width="100"  Binding="{Binding Classes}" />

                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
                                <Button x:Name="edit" Content="编辑" Width="60" Margin="3" Height="25" CommandParameter="{Binding Id}" Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
                                <Button x:Name="delete" Content="删除" Width="60" Margin="3" Height="25"  CommandParameter="{Binding Id}" Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                
            </DataGrid.Columns>
            
        </DataGrid>

    </Grid>
</Window>

// cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using WpfAppMVVMFWDemo1.ViewModel;

namespace WpfAppMVVMFWDemo1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            MainViewModel viewModel = new MainViewModel();
            viewModel.Query();
            this.DataContext = viewModel;
        }
    }
}

  • 创建Views目录,新建窗体StudentWindow
// xaml

<Window x:Class="WpfAppMVVMFWDemo1.Views.StudentWindow"
        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:WpfAppMVVMFWDemo1.Views"
        mc:Ignorable="d"
        Title="StudentWindow" Height="450" Width="800">


    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="60"></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition Height="60"></RowDefinition>
        </Grid.RowDefinitions>
        <TextBlock FontSize="30" Margin="10">修改学生信息</TextBlock>
        <StackPanel Grid.Row="1" Orientation="Vertical">
            <TextBlock FontSize="20" Margin="10" Padding="5">姓名</TextBlock>
            <TextBox x:Name="txtName" FontSize="20"  Padding="5" Text="{Binding Model.Name}"></TextBox>
            <TextBlock FontSize="20" Margin="10"  Padding="5">年龄</TextBlock>
            <TextBox x:Name="txtAge" FontSize="20"  Padding="5" Text="{Binding Model.Age}"></TextBox>
            <TextBlock FontSize="20" Margin="10"  Padding="5">班级</TextBlock>
            <TextBox x:Name="txtClasses" FontSize="20"  Padding="5" Text="{Binding Model.Classes}"></TextBox>
        </StackPanel>
        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button x:Name="btnSave" Content="保存" Margin="10" FontSize="20" Width="100" Click="btnSave_Click" ></Button>
            <Button x:Name="btnCancel" Content="取消" Margin="10" FontSize="20" Width="100" Click="btnCancel_Click" ></Button>
        </StackPanel>
    </Grid>



</Window>

// cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using WpfAppMVVMFWDemo1.Model;


namespace WpfAppMVVMFWDemo1.Views
{
    /// <summary>
    /// StudentWindow.xaml 的交互逻辑
    /// </summary>
    public partial class StudentWindow : Window
    {
        public StudentWindow(Student student)
        {
            InitializeComponent();
            this.DataContext = new { Model=student };
        }

        private void btnSave_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = true;
        }

        private void btnCancel_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
        }

    }
}

  • ViewModel.MainViewModel.cs
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using WpfAppMVVMFWDemo1.DAL;
using WpfAppMVVMFWDemo1.Model;
using WpfAppMVVMFWDemo1.Views;

namespace WpfAppMVVMFWDemo1.ViewModel
{
    /// <summary>
    /// This class contains properties that the main View can data bind to.
    /// <para>
    /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
    /// </para>
    /// <para>
    /// You can also use Blend to data bind with the tool's support.
    /// </para>
    /// <para>
    /// See http://www.galasoft.ch/mvvm
    /// </para>
    /// </summary>
    public class MainViewModel : ViewModelBase
    {

        private LocalDb localDb;
        private ObservableCollection<Student> gridModelList;
        public ObservableCollection<Student> GridModelList
        {
            get { return gridModelList; }
            set
            {
                gridModelList = value;
                RaisePropertyChanged();
            }
        }

        private string search;

        public string Search
        {
            get { return search; }
            set
            {
                search = value;
                RaisePropertyChanged();
            }
        }




        public MainViewModel()
        {
            localDb = new LocalDb();
            QueryCommand = new RelayCommand(this.Query);
            ResetCommand = new RelayCommand(this.Reset);
            EditCommand = new RelayCommand<int>(this.Edit);
            DeleteCommand = new RelayCommand<int>(this.Delete);
            AddCommand = new RelayCommand(this.Add);
        }

        public RelayCommand QueryCommand { get; set; }
        public RelayCommand ResetCommand { get; set; }
        public RelayCommand<int> EditCommand { get; set; }
        public RelayCommand<int> DeleteCommand { get; set; }
        public RelayCommand AddCommand { get; set; }

        public void Query()
        {
            List<Student> students;
            if (string.IsNullOrEmpty(search))
            {
                students = localDb.Query();
            }
            else
            {
                students = localDb.QueryByName(search);
            }

            GridModelList = new ObservableCollection<Student>();
            if (students != null)
            {
                students.ForEach((t) =>
                {
                    GridModelList.Add(t);
                });
            }
        }

        public void Reset()
        {
            this.Search = string.Empty;
            this.Query();
        }

        public void Edit(int Id)
        {
            var model = localDb.QueryById(Id);
            if (model != null)
            {
                StudentWindow view = new StudentWindow(model);
                var r = view.ShowDialog();
                if (r.Value)
                {
                    var newModel = GridModelList.FirstOrDefault(t => t.Id == model.Id);
                    if (newModel != null)
                    {
                        newModel.Name = model.Name;
                        newModel.Age = model.Age;
                        newModel.Classes = model.Classes;
                    }
                    this.Query();
                }
            }
        }

        public void Delete(int Id)
        {
            var model = localDb.QueryById(Id);
            if (model != null)
            {
                var r = MessageBox.Show($"确定要删除吗【{model.Name}】?", "提示", MessageBoxButton.YesNo);
                if (r == MessageBoxResult.Yes)
                {
                    localDb.DelStudent(Id);
                    this.Query();
                }
            }
        }

        public void Add()
        {
            Student model = new Student();
            StudentWindow view = new StudentWindow(model);
            var r = view.ShowDialog();
            if (r.Value)
            {
                model.Id = GridModelList.Max(t => t.Id) + 1;
                localDb.AddStudent(model);
                this.Query();
            }
        }


    }
}
  • 小结: MVVM具有低耦合,可重用,可测试,独立开发的优点,核心要素就两个
- 属性发生变化时的通知,即可达到数据的实时更新。
- 命令是实现用户与程序之间数据和算法的桥梁。

未命名绘图-第 2 页

  • ViewModel(视图模型)解析
- 包含视图所需的所有数据和命令
	- 数据(绑定): INotifyPropertyChanged接口
	- 命令(绑定): ICommand对象
 public class User
 {
     public string? Name { get; set; }
     public string? Email { get; set; }
 }


 public static class UserManager
 {
     public static ObservableCollection<User> DataBaseUsers = new       ObservableCollection<User>()
     {
         new User() { Name = "小王", Email = "123@qq.com" },
         new User() { Name = "小红", Email = "456@qq.com" },
         new User() { Name = "小五", Email = "789@qq.com" }
     };

     public static ObservableCollection<User> GetUsers()
     {
         return DataBaseUsers;
     }

     public static void AddUser(User user)
     {
         DataBaseUsers.Add(user);
     }
 }

ObservableCollection 详解

基本概念

ObservableCollection 是 .NET 中的一个特殊集合类,位于 System.Collections.ObjectModel 命名空间中。它与普通集合最大的区别在于实现了数据变更通知机制

核心特性

1. 自动通知机制

public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
  • 实现了 INotifyCollectionChanged 接口
  • 实现了 INotifyPropertyChanged 接口
  • 当集合内容变化时自动发出通知

2. 主要事件

// 集合内容变化时触发(添加、删除、移动、重置等)
public event NotifyCollectionChangedEventHandler CollectionChanged;

// 属性变化时触发(如Count属性)
public event PropertyChangedEventHandler PropertyChanged;

在示例代码中的作用

public static ObservableCollection<User> DataBaseUsers = new ObservableCollection<User>()
{
    new User() { Name = "小王", Email = "123@qq.com" },
    new User() { Name = "小红", Email = "456@qq.com" },
    new User() { Name = "小五", Email = "789@qq.com" }
};

public static void AddUser(User user)
{
    DataBaseUsers.Add(user); // 这里会触发CollectionChanged事件
}

实际应用场景

1. WPF/XAML 数据绑定

<!-- XAML 中绑定到 ObservableCollection -->
<ListBox ItemsSource="{Binding DataBaseUsers}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding Name}"/>
                <TextBlock Text="{Binding Email}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

2. MVVM 模式中的典型用法

public class UserViewModel
{
    public ObservableCollection<User> Users { get; set; }
    
    public UserViewModel()
    {
        Users = UserManager.GetUsers();
        // 当UserManager中添加新用户时,UI会自动更新
    }
}

与普通集合的区别

特性 List ObservableCollection
数据变更通知 ❌ 无 ✅ 自动通知
UI自动更新 ❌ 需要手动刷新 ✅ 自动同步
内存占用 较小 稍大(维护事件)
使用场景 纯数据处理 UI数据绑定

触发通知的操作

var users = new ObservableCollection<User>();

// 以下操作都会触发CollectionChanged事件:
users.Add(new User());                    // 添加
users.RemoveAt(0);                        // 删除
users[0] = new User();                    // 替换
users.Move(0, 1);                         // 移动
users.Clear();                            // 清空

事件处理示例

// 监听集合变化
DataBaseUsers.CollectionChanged += (sender, e) =>
{
    switch (e.Action)
    {
        case NotifyCollectionChangedAction.Add:
            Console.WriteLine($"添加了 {e.NewItems.Count} 个用户");
            break;
        case NotifyCollectionChangedAction.Remove:
            Console.WriteLine($"删除了 {e.OldItems.Count} 个用户");
            break;
        case NotifyCollectionChangedAction.Reset:
            Console.WriteLine("集合被重置");
            break;
    }
};

优点总结

  1. 自动化 - UI自动同步,无需手动刷新
  2. 响应式 - 实时响应数据变化
  3. 简化代码 - 减少样板代码
  4. 框架集成 - 与WPF、Xamarin等完美集成

注意事项

  • 线程安全:ObservableCollection不是线程安全的,跨线程操作需要调度
  • 性能:频繁的大批量操作可能影响性能,可使用AddRange扩展方法优化
  • 内存:事件订阅可能导致内存泄漏,注意及时取消订阅

这就是为什么在您的代码中使用 ObservableCollection - 它使得用户界面的数据绑定变得简单而高效!

RelayComand解析

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace WpfApp2MVVMTestInit.Commands
{
    public class RelayComand: ICommand
    {
        public event EventHandler CanExecuteChanged;
        private Action<object> _Excute { get; set; }
        private Predicate<object> _CanExcute { get; set; }

        public RelayComand(Action<object> ExcuteMethod,Predicate<object> canExcuteMethod)
        {
            _Excute = ExcuteMethod;
            _CanExcute = canExcuteMethod;
        }

        public bool CanExecute(object parameter)
        {
            return _CanExcute(parameter);
        }

        public void Execute(object parameter) {
            _Excute(parameter);
        }
        
           
    }
}

这是一个WPF MVVM模式中常用的RelayCommand(中继命令)实现。让我逐部分解释:

类结构概述

public class RelayComand : ICommand
  • 实现了 ICommand 接口,这是WPF命令系统的核心
  • 用于将UI操作(如按钮点击)绑定到ViewModel中的方法

核心字段

private Action<object> _Excute { get; set; }
private Predicate<object> _CanExcute { get; set; }
  • _Excute: 存储要执行的实际方法(无返回值,接受object参数)
  • _CanExcute: 存储判断命令是否可执行的方法(返回bool,接受object参数)

构造函数

public RelayComand(Action<object> ExcuteMethod, Predicate<object> canExcuteMethod)

接受两个委托参数:

  • 执行逻辑的方法
  • 判断是否可执行的方法

ICommand接口实现

CanExecute 方法

public bool CanExecute(object parameter)
{
    return _CanExcute(parameter);
}
  • 决定命令是否可用(控制UI元素的启用/禁用状态)
  • 返回true时按钮可用,false时按钮变灰

Execute 方法

public void Execute(object parameter) {
    _Excute(parameter);
}
  • 实际执行命令时调用的方法
  • 包含主要的业务逻辑

CanExecuteChanged 事件

public event EventHandler CanExecuteChanged;
  • 当命令的可用状态改变时触发
  • WPF自动监听此事件来更新UI状态

使用示例

// 在ViewModel中
public ICommand SaveCommand { get; private set; }

public ViewModel()
{
    SaveCommand = new RelayComand(
        ExecuteSave,     // 执行方法
        CanExecuteSave   // 判断是否可执行的方法
    );
}

private void ExecuteSave(object parameter)
{
    // 保存逻辑
    MessageBox.Show("数据已保存!");
}

private bool CanExecuteSave(object parameter)
{
    // 只有数据有效时才允许保存
    return !string.IsNullOrEmpty(FileName);
}

XAML中的绑定

<Button Content="保存" 
        Command="{Binding SaveCommand}"
        CommandParameter="一些参数"/>

这个RelayCommand是MVVM模式的关键组件,它解耦了UI和业务逻辑,使代码更易于测试和维护。

posted @ 2025-07-09 14:56  清安宁  阅读(188)  评论(0)    收藏  举报