当前位置:首页 > 技术 > 正文内容

WPF中MVVM模式的实践与应用

访客 技术 2026年9月13日 11

1. MVVM模式概述

MVVM(Model-View-ViewModel)是一种软件架构设计模式,由微软的WPF和Silverlight架构师John Gossman于2005年首次提出。它旨在帮助开发者在用户界面(UI)开发中实现关注点分离,提升代码的可维护性和可测试性。

在WPF应用中采用MVVM模式,通常可以带来以下核心优势:

  1. UI与业务逻辑解耦: 彻底分离了用户界面(View)和应用程序的业务逻辑(ViewModel),使得View成为ViewModel的纯粹消费者。
  2. 职责清晰: 有助于明确区分哪些是UI呈现和交互操作,哪些是核心业务逻辑,避免职责混淆。
  3. 降低耦合度: 各层之间相互依赖程度降低,符合面向对象设计原则,提高了代码的灵活性和可重用性。
  4. 易于测试: ViewModel层不依赖于具体的UI框架,可以独立进行单元测试,从而提高测试效率和覆盖率。

MVVM模式的结构通常可以通过下图来表示:

MVVM Architecture Diagram 1

MVVM Architecture Diagram 2

2. MVVM模式实践:计数器示例

接下来,我们将通过一个简单的计数器应用来演示如何在WPF中实现MVVM模式。

2.1 数据模型(Model)定义

首先,创建数据模型。这个模型将包含我们应用程序的数据和基本的业务逻辑。在本例中,我们创建一个CounterModel来存储一个整数值,并提供一个方法来增加这个值。由于WPF的UI需要感知数据的变化,所以CounterModel必须实现INotifyPropertyChanged接口。

在项目根目录创建Models文件夹,并添加CounterModel.cs文件:

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace MvvmExample.Models
{
    public class CounterModel : INotifyPropertyChanged
    {
        private int _currentCount;

        public int CurrentCount
        {
            get { return _currentCount; }
            set
            {
                if (_currentCount != value)
                {
                    _currentCount = value;
                    OnPropertyChanged();
                }
            }
        }

        public CounterModel()
        {
            _currentCount = 0; // 初始值为0
        }

        public void Increment()
        {
            CurrentCount++; // 增加计数
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

2.2 命令(Command)实现

在MVVM模式中,View中的交互(如按钮点击)通常通过Command绑定到ViewModel中的逻辑。为了实现这一点,我们需要一个实现了ICommand接口的通用命令类,通常称为RelayCommand或DelegateCommand。

在项目根目录创建Commands文件夹,并添加RelayCommand.cs文件:

using System;
using System.Windows.Input;

namespace MvvmExample.Commands
{
    public class RelayCommand : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Func<object, bool> _canExecute;

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
        {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute(parameter);
        }

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

        // 可以手动触发CanExecuteChanged事件
        public void RaiseCanExecuteChanged()
        {
            CommandManager.InvalidateRequerySuggested();
        }
    }
}

2.3 视图模型(ViewModel)定义

ViewModel是View和Model之间的桥梁。它负责封装Model数据以供View展示,并提供View可以绑定的命令和属性。它不直接引用View,而是通过数据绑定和命令机制与View通信。

在项目根目录创建ViewModels文件夹,并添加CounterViewModel.cs文件:

using MvvmExample.Commands;
using MvvmExample.Models;
using System.Windows.Input;

namespace MvvmExample.ViewModels
{
    public class CounterViewModel
    {
        public CounterModel CurrentCounter { get; set; }
        public ICommand IncrementCountCommand { get; set; }

        public CounterViewModel()
        {
            CurrentCounter = new CounterModel();
            IncrementCountCommand = new RelayCommand(ExecuteIncrementCount, CanExecuteIncrementCount);
        }

        private void ExecuteIncrementCount(object obj)
        {
            CurrentCounter.Increment();
        }

        private bool CanExecuteIncrementCount(object obj)
        {
            // 例如,可以添加逻辑限制计数器的最大值
            // return CurrentCounter.CurrentCount < 100;
            return true; 
        }
    }
}

2.4 视图(View)构建

最后,创建View,它是一个WPF用户界面。View负责展示数据,并通过数据绑定(Data Binding)与ViewModel进行连接。它将ViewModel的属性绑定到UI元素上,并将用户操作(如按钮点击)绑定到ViewModel的Command上。

MainWindow.xaml

修改MainWindow.xaml文件,使其绑定到CounterViewModel:

<Window x:Class="MvvmExample.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:MvvmExample"
        xmlns:vm="clr-namespace:MvvmExample.ViewModels"
        mc:Ignorable="d"
        Title="MVVM计数器示例" Height="250" Width="350">
    <Window.Resources>
        <vm:CounterViewModel x:Key="CounterVM"/>
    </Window.Resources>
    <Grid DataContext="{StaticResource CounterVM}">
        <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
            <TextBlock Text="当前计数:" FontSize="24" Margin="0,0,0,10"/>
            <TextBlock Text="{Binding CurrentCounter.CurrentCount}" 
                       FontSize="48" FontWeight="Bold" 
                       HorizontalAlignment="Center" Margin="0,0,0,20"/>
            <Button Content="增加计数" 
                    Command="{Binding IncrementCountCommand}" 
                    Width="150" Height="40" FontSize="18"/>
        </StackPanel>
    </Grid>
</Window>

MainWindow.xaml.cs

在MainWindow.xaml.cs中,我们将DataContext设置为CounterViewModel的一个实例。这使得WPF的数据绑定机制能够找到ViewModel中定义的属性和命令。

using System.Windows;

namespace MvvmExample
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            // 在XAML中已经通过 <Window.Resources> 设置了DataContext
            // this.DataContext = new ViewModels.CounterViewModel(); 
        }
    }
}

通过以上步骤,我们就完成了一个遵循MVVM模式的WPF计数器应用。当点击"增加计数"按钮时,View会通过绑定的IncrementCountCommand调用ViewModel中的ExecuteIncrementCount方法,进而修改Model中的CurrentCount。由于Model实现了INotifyPropertyChanged,CurrentCount的改变会自动通知到ViewModel,并最终更新到View上的TextBlock。

标签: WPF

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。