模型-视图-视图模型 (MVVM) 是一种用来分离 UI 和非 UI 代码的应用设计模式
MVVM 中的 Model 与 MVC 中的一致,用于封装业务逻辑以及数据处理。
Model 不依赖 View 和 ViewModel,可以独立存在,也就是说模型不关心自身被谁操作和展示。
Model 中不允许有任何跟界面相关的逻辑,比如操作界面上的控件。
通常在实际开发过程中 Model 可以再被划分成 Model 和 Service,区分业务和数据。
视图仅仅负责界面展示。
通过 DataContext(数据上下文)进行数据绑定。
不允许直接与 Model 交互。
可以通过绑定 Comand 来调用 ViewModel 的行为。
Command 是 View 到 ViewModel 的单向通行。
用户在 View 中触发事件,在 ViewModel 中处理。
ViewModel 是对 View 的抽象
视图模型包括界面模型数据和 Command 事件响应。
是 View 和 Model 的桥梁,是对 Model 的包装和抽象。
实现视图模型需要让类型实现 INotifyPropertyChanged 接口,用于实现属性和集合的变更通知,使用户在 View 上所做的操作可以实时通知到视图模型。
利用 MVVM 实现一个计算器;
要求界面和业务逻辑完全剥离;
比如我们可以更改界面展示形式,功能仍然相同;
第一步:先对页面进行抽象(建模)
第二步:让视图模型实现INotifyPropertyChanged接口
第三步:后台代码中声明一个视图模型属性交给当前数据上下文
第四步:前台数据绑定
第五步:定义Command命令
1 <Page 2 x:Class="MyJiSuanQi.MainPage" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:local="using:MyJiSuanQi" 6 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 7 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 8 mc:Ignorable="d" 9 DataContext="{Binding ViewModel,RelativeSource={RelativeSource Mode=Self}}" 10 Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 11 12 <StackPanel> 13 <Slider 14 Header="参数1" 15 Value="{Binding Parameter1,Mode=TwoWay}"/> 16 <!--<TextBox 17 x:Name="txtP1" 18 Header="参数1" 19 Text="{Binding Parameter1,Mode=TwoWay}"/>--> 20 <TextBox 21 x:Name="txtP2" 22 Header="参数2" 23 Text="{Binding Parameter2,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> 24 <AppBarButton 25 x:Name="btnAdd" 26 Icon="Italic" 27 Label="Except" 28 Command="{Binding ExceptCommand}" 29 CommandParameter="{Binding}"/> 30 <TextBox 31 x:Name="txtResult" 32 Header="结果" 33 Text="{Binding Result,Mode=OneWay}"/> 34 </StackPanel> 35 </Page>
1 namespace MyJiSuanQi 2 { 3 /// <summary> 4 /// 可用于自身或导航至 Frame 内部的空白页。 5 /// </summary> 6 public sealed partial class MainPage : Page 7 { 8 public MainPageViewModel ViewModel { get; set; } 9 public MainPage() 10 { 11 ViewModel = new MainPageViewModel(); 12 this.InitializeComponent(); 13 14 this.NavigationCacheMode = NavigationCacheMode.Required; 15 } 16 17 /// <summary> 18 /// 在此页将要在 Frame 中显示时进行调用。 19 /// </summary> 20 /// <param name="e">描述如何访问此页的事件数据。 21 /// 此参数通常用于配置页。</param> 22 protected override void OnNavigatedTo(NavigationEventArgs e) 23 { 24 // TODO: 准备此处显示的页面。 25 26 // TODO: 如果您的应用程序包含多个页面,请确保 27 // 通过注册以下事件来处理硬件“后退”按钮: 28 // Windows.Phone.UI.Input.HardwareButtons.BackPressed 事件。 29 // 如果使用由某些模板提供的 NavigationHelper, 30 // 则系统会为您处理该事件。 31 } 32 //第一步:先对页面进行抽象(建模) 33 //第二步:让视图模型实现INotifyPropertyChanged接口 34 //第三步:后台代码中声明一个视图模型属性交给当前数据上下文 35 //第四步:前台数据绑定 36 //第五步:定义Command命令 37 private void btnAdd_Click(object sender, RoutedEventArgs e) 38 { 39 //var d1 = double.Parse(txtP1.Text); 40 //var d2 = double.Parse(txtP2.Text); 41 //txtResult.Text = (d1 + d2).ToString(); 42 ViewModel.Result = ViewModel.Parameter1 + ViewModel.Parameter2; 43 } 44 } 45 public class MainPageViewModel : INotifyPropertyChanged 46 { 47 public MainPageViewModel() 48 { 49 AddCommand = new AddCommand(); 50 ExceptCommand = new ExceptCommand(); 51 } 52 public double Parameter1 { get; set; } 53 private double parameter2; 54 //影响当前命令是否可以被执行的依据 55 public double Parameter2 56 { 57 get { return parameter2; } 58 set 59 { 60 if (parameter2 == value) 61 { 62 return; 63 } 64 parameter2 = value; 65 //通知命令对象重新判断是否可以执行 66 ExceptCommand.OnCanExecuteChanged(); 67 } 68 } 69 //public double Result { get; set; } 70 private double result; 71 public double Result 72 { 73 get { return result; } 74 set { result = value; OnPropertyChanged("Result"); } 75 } 76 public ICommand AddCommand { get; set; } 77 public ExceptCommand ExceptCommand { get; set; } 78 private void OnPropertyChanged(string propertyName) 79 { 80 if (PropertyChanged != null) 81 { 82 PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 83 } 84 } 85 public event PropertyChangedEventHandler PropertyChanged; 86 } 87 //命令对象必须实现ICommand接口 88 public class AddCommand : ICommand 89 { 90 //判断当前命令对象是否可以被执行 91 public bool CanExecute(object parameter) 92 { 93 throw new NotImplementedException(); 94 } 95 96 public event EventHandler CanExecuteChanged; 97 //如何执行 98 public void Execute(object parameter) 99 { 100 var model = parameter as MainPageViewModel; 101 model.Result = model.Parameter1 + model.Parameter2; 102 } 103 } 104 //命令对象必须实现ICommand接口 105 public class ExceptCommand : ICommand 106 { 107 //判断当前命令对象是否可以被执行 108 public bool CanExecute(object parameter) 109 { 110 var model = parameter as MainPageViewModel; 111 return model != null && model.Parameter2 != 0; 112 } 113 //当CanExecute判断依据发生变化时,触发事件 114 public event EventHandler CanExecuteChanged; 115 public void OnCanExecuteChanged() 116 { 117 if (CanExecuteChanged != null) 118 CanExecuteChanged(this, EventArgs.Empty); 119 } 120 //如何执行 121 public void Execute(object parameter) 122 { 123 var model = parameter as MainPageViewModel; 124 model.Result = model.Parameter1 / model.Parameter2; 125 } 126 } 127 }
低耦合:View 可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。
可重用性:可以把一些视图的逻辑放在 ViewModel 里面,让很多View重用这段视图逻辑。
独立开发:开发人员可以专注与业务逻辑和数据的开发(ViewModel)。设计人员可以专注于界面(View)的设计。
可测试性:可以针对ViewModel来对界面(View)进行测试。