XAML 深入学习 (二) MVVM架构实现

一、设计理念

MVVM(Model-View-ViewModel)架构在C# XAML平台(WPF/UWP等)中的设计理念围绕‌关注点分离‌、‌数据驱动‌和‌可测试性‌三大核心原则构建,其分层逻辑与协作机制如下:

>核心设计理念

严格分层解耦

  • Model‌:封装纯业务逻辑与数据实体(如数据库操作、DTO对象),完全独立于UI层。
  • View‌:纯XAML声明式UI,仅负责数据呈现与用户交互捕获,禁止包含业务逻辑。
  • ViewModel‌:作为桥梁,将Model数据转换为View可绑定属性,并处理用户命令。通过INotifyPropertyChanged实现数据变更通知

数据驱动UI

  • 利用XAML数据绑定({Binding})自动同步View与ViewModel状态,消除手动操作UI控件的需求。
  • 双向绑定(TwoWay模式)确保用户输入自动更新ViewModel属性

命令模式解耦交互

用户操作(如按钮点击)通过ICommand接口(如RelayCommand)映射到ViewModel逻辑,避免View层事件处理代码

可测试性优先

ViewModel不依赖具体View,可通过单元测试验证所有交互逻辑与数据转换逻辑

>关键实现机制 

组件职责关键技术
Model数据存取、业务规则验证(如IsValid属性)POCO类、领域驱动设计(DDD)
ViewModel暴露绑定属性(ObservableCollection<T>)、命令对象、状态管理INotifyPropertyChangedObservableObject(CommunityToolkit.Mvvm)
View通过DataContext绑定ViewModel,XAML中使用{Binding Path}声明数据绑定x:Bind(编译时绑定)、TriggerConverter

 >相较传统模式(MVC/MVP)的优势

对比项MVVM优势
代码解耦View与ViewModel通过绑定连接,替换UI框架无需重写业务逻辑(如WPF→UWP)
开发效率设计师可独立修改XAML界面,程序员专注ViewModel开发,并行协作高效
维护成本业务逻辑集中于ViewModel,Bug定位更快;UI更新不影响底层逻辑

>实践注意事项

  • 避免“臃肿ViewModel”‌:
  • 复杂业务逻辑应拆分为独立服务层(如Services目录),ViewModel仅保留UI相关状态。
  • 性能优化‌:
    大数据集绑定需启用VirtualizingStackPanel,高频更新属性使用DispatcherTimer
  • 消息通信‌:
    跨ViewModel通信采用WeakEventManagerIMessenger(CommunityToolkit.Mvvm)

>典型场景示例 

// ViewModel
public partial class UserViewModel : ObservableObject
{
    [ObservableProperty]
    private string _username; // 自动生成属性变更通知(CommunityToolkit.Mvvm)
    
    public ICommand SubmitCommand => new RelayCommand(Submit);
    private void Submit() => /* 调用Model层服务 */;
}

// View(XAML)
<TextBox Text="{Binding Username, Mode=TwoWay}"/>
<Button Command="{Binding SubmitCommand}" Content="提交"/>

MVVM通过‌数据绑定‌与‌命令抽象‌彻底解耦UI与业务逻辑,成为现代XAML应用开发的首选架构。其设计本质是‌以ViewModel为中心‌的响应式编程模型,显著提升应用的可维护性与跨平台能力

二、详细实现案例

案例:学生管理系统(WPF实现)

项目结构

StudentManagement/
├── Models/          # 数据模型层
│   └── Student.cs
├── ViewModels/      # 视图模型层
│   └── MainViewModel.cs
├── Views/           # 视图层
│   └── MainWindow.xaml
└── Services/        # 数据服务
    └── LocalDb.cs

核心代码实现

模型层 (Model)

// Models/Student.cs
public class Student : INotifyPropertyChanged
{
    private int _id;
    public int Id
    {
        get => _id;
        set { _id = value; RaisePropertyChanged(); }
    }
    
    private string _name;
    public string Name
    {
        get => _name;
        set { _name = value; RaisePropertyChanged(); }
    }

    // 属性变更通知机制
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged([CallerMemberName] string name = null) 
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

数据服务层 (Service)

// Services/LocalDb.cs
public class LocalDb
{
    private List<Student> _students = new List<Student>();
    
    // 初始化模拟数据
    public LocalDb() 
    {
        for (int i = 1; i <= 30; i++) 
        {
            _students.Add(new Student { Id = i, Name = $"Student{i}" });
        }
    }
    
    // 增删改查操作
    public List<Student> GetStudents() => _students;
    public void AddStudent(Student stu) => _students.Add(stu);
    public void DeleteStudent(int id) => _students.RemoveAll(s => s.Id == id);
}

视图模型层 (ViewModel)

// ViewModels/MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
    private readonly LocalDb _db = new LocalDb();
    private ObservableCollection<Student> _students;
    public ObservableCollection<Student> Students
    {
        get => _students;
        set { _students = value; RaisePropertyChanged(); }
    }

    private Student _selectedStudent;
    public Student SelectedStudent
    {
        get => _selectedStudent;
        set { _selectedStudent = value; RaisePropertyChanged(); }
    }

    // 命令绑定
    public ICommand DeleteCommand => new RelayCommand(DeleteSelected);
    private void DeleteSelected() 
    {
        if (SelectedStudent != null) 
        {
            _db.DeleteStudent(SelectedStudent.Id);
            Students.Remove(SelectedStudent);
        }
    }

    // 初始化
    public MainViewModel() 
    {
        Students = new ObservableCollection<Student>(_db.GetStudents());
    }
    
    // INotifyPropertyChanged 实现
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged([CallerMemberName] string name = null) 
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

// 命令助手类
public class RelayCommand : ICommand
{
    private readonly Action _execute;
    public event EventHandler CanExecuteChanged;

    public RelayCommand(Action execute) => _execute = execute;
    public bool CanExecute(object parameter) => true;
    public void Execute(object parameter) => _execute();
}

 视图层 (View)

<!-- Views/MainWindow.xaml -->
<Window xmlns:vm="clr-namespace:StudentManagement.ViewModels"
        xmlns:local="clr-namespace:StudentManagement.Models">
    <Window.DataContext>
        <vm:MainViewModel/>
    </Window.DataContext>
    
    <Grid>
        <!-- 数据表格 -->
        <DataGrid ItemsSource="{Binding Students}" 
                  SelectedItem="{Binding SelectedStudent}"
                  AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="ID" Binding="{Binding Id}"/>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
            </DataGrid.Columns>
        </DataGrid>
        
        <!-- 删除按钮 -->
        <Button Content="Delete Selected" 
                Command="{Binding DeleteCommand}"
                HorizontalAlignment="Right" 
                VerticalAlignment="Bottom"/>
    </Grid>
</Window>

功能概要:

数据自动同步

  • ObservableCollection自动通知DataGrid更新列表
  • INotifyPropertyChanged实现属性变更即时刷新UI

解耦交互逻辑

  • 按钮操作通过ICommand转发至ViewModel处理
  • View完全无需编写事件处理代码

可测试性设计

  • ViewModel独立于UI层,支持单元测试验证业务逻辑
  • 数据服务可替换为真实数据库实现

常见问题解决方案 

问题场景解决方案
绑定失败检查Output窗口错误日志,启用PresentationTraceSources.TraceLevel=High
大数据集性能优化使用VirtualizingStackPanel实现列表虚拟化渲染
跨ViewModel通信通过Messenger(CommunityToolkit.Mvvm)或EventAggregator实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值