WPF(Windows Presentation Foundation):WPF 是基于 Windows 的应用程序框架,在 WPF 中多为数据驱动 UI ,框架提供数据绑定和模板支持
MVVM(Model – View – ViewModel):MVVM软件架构模式把应用程序分成三部分,分别是模型(Model)、视图(View)、视图模型(ViewMdel);设计思想在于隔离界面和业务逻辑
- 模型(Model):数据和业务逻辑
- 视图(View):交互界面
- 视图模型(ViewModel):为视图提供可绑定的数据和命令
基于WPF中的数据绑定机制, MVVM 模式可以很好地支持双向绑定和数据验证;由于 WPF 是基于 XAML 的,这使得 MVVM 模式可以很好地支持视图和视图模型的分离。
示例:
Model,此处创建一个设备的模型
namespace MVVMDemo { /// <summary> /// 设备 /// </summary> public class Device { /// <summary> /// 设备名称 /// </summary> public string Name { get; set; } /// <summary> /// 设备序列号 /// </summary> public string Sn { get; set; } /// <summary> /// 是否运行中 /// </summary> public bool IsActive { get; private set; } = false; /// <summary> /// 运行 /// </summary> /// <returns>已运行或运行失败返回false</returns> public bool Run() { if (IsActive) return false; IsActive = true; return true; } /// <summary> /// 停止 /// </summary> /// <returns>已停止或停止失败返回false</returns> public bool Stop() { if (!IsActive) return false; IsActive = false; return true; } } }
此处先实现一个 ICommand
,在视图模型中需要定义一些 Command 绑定到视图中的按钮上以便与用户交互;当然也可以使用 MVVM 工具库(例如Prism,MvvmLight等)中实现好的 Command
using System; using System.Windows.Input; namespace MVVMDemo { public class DelegateCommand : ICommand { private readonly Func<bool> _canExecute; private readonly Action _execute; public event EventHandler CanExecuteChanged { add { if (_canExecute != null) CommandManager.RequerySuggested += value; } remove { if (_canExecute != null) CommandManager.RequerySuggested -= value; } } public DelegateCommand(Action execute) : this(execute, null) { } public DelegateCommand(Action execute, Func<bool> canExecute) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) { return _canExecute?.Invoke() ?? true; } public void Execute(object parameter) { _execute(); } } }
ViewModel,为 MainWindow
创建的视图模型,在视图模型添加模型中需要与用户交互的属性和 Command ;在 MainViewModel
中实现 INotifyPropertyChanged
接口,INotifyPropertyChanged
是 WPF 中用于实现数据绑定的接口,它定义了一个 PropertyChanged
事件,当视图模型中的属性发生变化时,可以触发这个事件来通知视图进行更新,具体实现方式通常是在属性的 setter 中触发这个事件;在视图模型中需要创建了一个静态的VM(Instance
)实例用于视图绑定;除此之外还可以有多种方式创建和绑定,例如在 MainWindow.xaml.cs 中创建并指定,例如:DataContext = new MainViewModel();
using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows.Input; namespace MVVMDemo { public class MainViewModel : INotifyPropertyChanged { private readonly Device _machine; public static MainViewModel Instance => new MainViewModel(); public string Name { get => _machine.Name; set { _machine.Name = value; OnPropertyChanged(); } } /// <summary> /// 编辑的名称,由于没有从VM属性更新到UI,故不路由属性变更,仅接收从UI到VM的变化 /// </summary> public string EditName { get; set; } public string Sn => _machine.Sn; public bool IsActive => _machine.IsActive; public ICommand RunStopCommand { get; } public ICommand UpdateCommand { get; } public MainViewModel() { //初始化一个设备 _machine = new Device { Name = "Machine", Sn = "A20230128" }; //将命令绑定接到模型的逻辑上 RunStopCommand = new DelegateCommand(() => { if (_machine.IsActive) _machine.Stop(); else _machine.Run(); //通知属性变更,UI会重新get这个属性 OnPropertyChanged(nameof(IsActive)); }); //更新编辑的名称到真正的名称 UpdateCommand = new DelegateCommand(() => { if (string.IsNullOrWhiteSpace(EditName)) return; Name = EditName; }); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
在实现视图之前先创建一个 BooleanToBrush
的 Converter,这个 Converter 用于将视图模型中的 Bool 属性在视图上显示成不同的颜色;这个 Converter 需要继承 MarkupExtension
和实现 IValueConverter
using System; using System.Globalization; using System.Windows.Data; using System.Windows.Markup; using System.Windows.Media; namespace MVVMDemo { public class BooleanToBrush : MarkupExtension, IValueConverter { private readonly Brush RunBrush = new SolidColorBrush(Colors.LawnGreen); private readonly Brush StopBrush = new SolidColorBrush(Colors.Red); public override object ProvideValue(IServiceProvider serviceProvider) { return this; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { bool isActive = (bool)value; if (isActive) return RunBrush; else return StopBrush; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } }
View,视图中,视图中通过d:DataContext="{d:DesignInstance local:MainViewModel}"
指定调试时的视图模型,通过DataContext="{x:Static local:MainViewModel.Instance}"
指定实际绑定的视图模型,如果通过MainWindow.xaml.cs 中创建并指定则不需要在视图中指定,相关控件的属性都与视图模型中的Name
、Sn
、EditName
、IsActive
、UpdateCommand
、RunStopCommand
进行绑定
<Window x:Class="MVVMDemo.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:local="clr-namespace:MVVMDemo" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="MainWindow" Width="800" Height="450" d:DataContext="{d:DesignInstance local:MainViewModel}" DataContext="{x:Static local:MainViewModel.Instance}" mc:Ignorable="d"> <StackPanel Margin="20" Orientation="Vertical"> <StackPanel Margin="0,10" Orientation="Horizontal"> <TextBlock FontWeight="DemiBold" Text="Name: " /> <TextBlock Text="{Binding Name}" /> <TextBlock Margin="20,0,0,0" FontWeight="DemiBold" Text="Sn: " /> <TextBlock Text="{Binding Sn}" /> <TextBlock Margin="20,0,0,0" FontWeight="DemiBold" Text="IsActive: " /> <Ellipse Width="14" Height="14" Fill="{Binding IsActive, Converter={local:BooleanToBrush}}" Stroke="Black" /> </StackPanel> <StackPanel Margin="0,10" Orientation="Horizontal"> <TextBlock FontWeight="DemiBold" Text="Edit Name: " /> <TextBox MinWidth="100" Text="{Binding EditName}" /> <Button Command="{Binding UpdateCommand}" Content="Update" /> <Button Margin="20,0,0,0" Command="{Binding RunStopCommand}" Content="Run/Stop" /> </StackPanel> </StackPanel> </Window>
效果演示