在利用WPF创建桌面应用程序的界面时,经常使用MVVM的设计模式,以减少UI层与逻辑层的代码耦合度。
在MVVM的设计中,最主要的方法和技术是UI中的控件利用Binding来和逻辑层(ViewModel)进行交互,其中控件的属性为依赖属性,而作为控件的DataContext的ViewModel则实现了INotifyPropertyChanged接口。
除了一般意义上的属性的数据的交互,还有一些基于命令的Banding来实现界面元素的操作与内部函数的解耦。
这样,界面上的数据和操作(包括控件的命令和事件属性,事件的解耦后面会有一篇文章说明,在这里)都可以和底层实现解耦了,这样就使得ViewModel层和UI层有了明显的界限,松耦合的实现对将来的功能扩展和单元测试会是非常大的便利。
此外,因为View层(控件层)与ViewModel层的解耦,使得原来利用控件的事件进行底层交互的操作(可以使用Dispatcher进行异步交互)全部移到ViewModel层中了。某些情形下,这种交互可能是非常费时间的,比如访问数据库或者进行密集型运算,此时就可以利用.net的并行库对Model层进行异步的调用,当Model层结束查询或者运算时将结果更新到ViewModel层,ViewModel层因为实现了INotifyPropertyChanged接口,使得UI层得到通知更新。体现了数据驱动界面的思想。
准备:下载 Prism ,利用其中的DelegateCommand 放到ViewModel层来做为View层控件的Command绑定目标。
下面给出实际的例子:
1.创建一个WPF程序:
2.从下载的Prism包中找到Bin文件夹下的Desktop目录,引用其中的Assembly Microsoft.Practices.Prism.dll到新建的工程
3.主窗口对应的xaml文件如下:

<Window x:Class="WPF.MVVMDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:WPF.MVVMDemo" Title="MVVMDemo" Height="600" Width="600"> <Window.DataContext> <vm:MainViewModel></vm:MainViewModel> </Window.DataContext> <Grid> <StackPanel> <Button Height="30" Command="{Binding CalculateCommand}">Start to run complex calculation in engine</Button> <Button Command="{Binding CalculateCommandWithParameter}" CommandParameter="I am the parameter command!" Content="Start to run complex calculation in engine with Parameter" Height="30" /> <TextBlock Height="30" Text="{Binding CalculatingStatus}"></TextBlock> <GroupBox Header="Data from engine" Height="200" Name="groupBox1" Width="Auto"> <Grid> <TextBox Height="220" TextWrapping="Wrap" Text="{Binding DataStringFromEngine}"></TextBox> </Grid> </GroupBox> <GroupBox Header="Always editable Box" Height="200" Name="groupBox2" Width="Auto"> <Grid> <TextBox Height="150" TextWrapping="Wrap" Text="You can edit me while calculating"></TextBox> </Grid> </GroupBox> </StackPanel> </Grid> </Window>
4.对应窗口的分部代码不用改动。
5.定义一个类做为窗口的ViewModel

public class MainViewModel : INotifyPropertyChanged, IDisposable { public event PropertyChangedEventHandler PropertyChanged; private ModelSimulator engine; public MainViewModel() { dataStringFromEngine = string.Empty; calculatingStatus = string.Empty; isEngineFree = true; engine = new ModelSimulator(); calculateCommand = new DelegateCommand(this.executeCalculateCommand, this.canExecuteCalculateCommand); calculateCommandWithParameter = new DelegateCommand<string>(this.executeCalculateCommandWithParameter, this.canExecuteCalculateCommandWithParameter); cancelCalculateCommand = new DelegateCommand(this.executeCancelCalculateCommand, this.canExecuteCancelCalculateCommand); } private string dataStringFromEngine; public string DataStringFromEngine { get { return dataStringFromEngine; } set { if (dataStringFromEngine != value) { dataStringFromEngine = value; OnPropertyChagned("DataStringFromEngine"); } } } private string calculatingStatus; public string CalculatingStatus { get { return calculatingStatus; } set { if (calculatingStatus != value) { calculatingStatus = value; OnPropertyChagned("CalculatingStatus"); } } } private bool isEngineFree; public bool IsEngineFree { get { return isEngineFree; } set { if (isEngineFree != value) { isEngineFree = value; OnPropertyChagned("IsEngineFree"); ((DelegateCommand)calculateCommand).RaiseCanExecuteChanged(); ((DelegateCommand<string>)calculateCommandWithParameter).RaiseCanExecuteChanged(); ((DelegateCommand)cancelCalculateCommand).RaiseCanExecuteChanged(); CommandManager.InvalidateRequerySuggested(); } } } private ICommand calculateCommand; public ICommand CalculateCommand { get { return calculateCommand; } set { if (calculateCommand != value) { calculateCommand = value; OnPropertyChagned("CalculateCommand"); } } } private void executeCalculateCommand() { IsEngineFree = false; CalculatingStatus = "Running!"; // create parallel task ,async Task<string> engineTask = Task.Factory.StartNew<string>(() => engine.SimulateLongTimeWork(5)); //UI callback engineTask.ContinueWith(task => { this.DataStringFromEngine = task.Result; IsEngineFree = true; CalculatingStatus = "Complete!"; }); } private bool canExecuteCalculateCommand() { return isEngineFree; } private ICommand calculateCommandWithParameter; public ICommand CalculateCommandWithParameter { get { return calculateCommandWithParameter; } set { if (calculateCommandWithParameter != value) { calculateCommandWithParameter = value; OnPropertyChagned("CalculateCommandWithParameter"); } } } private void executeCalculateCommandWithParameter(string para) { IsEngineFree = false; CalculatingStatus = "Running!"; // create parallel task ,async Task<string> engineTask = Task.Factory.StartNew<string>(() => engine.SimulateLongTimeWorkWithParameter(para, 5)); //UI callback engineTask.ContinueWith(task => { this.DataStringFromEngine = task.Result; IsEngineFree = true; CalculatingStatus = "Complete!"; }); } private bool canExecuteCalculateCommandWithParameter(string para) { return isEngineFree; } private ICommand cancelCalculateCommand; public ICommand CancelCalculateCommand { get { return cancelCalculateCommand; } set { if (cancelCalculateCommand != value) { cancelCalculateCommand = value; OnPropertyChagned("CancelCalculateCommand"); } } } private void executeCancelCalculateCommand() { } private bool canExecuteCancelCalculateCommand() { return !isEngineFree; } private void OnPropertyChagned(string propertyName) { if (PropertyChanged != null) { this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public void Dispose() { //throw new NotImplementedException(); } }
6.定义一个模拟Model层的类ModelSimulator

public class ModelSimulator { public string SimulateLongTimeWork(int seconds) { string rtn = null; Random rnd = new Random(DateTime.Now.Millisecond); for (int i = 0; i < 300; i++) { rtn = rtn + rnd.Next(-100, 1000); } Thread.Sleep(new TimeSpan(0,0,seconds)); return rtn; } public string SimulateLongTimeWorkWithParameter(string para, int seconds) { string rtn = para + Environment.NewLine; Random rnd = new Random(DateTime.Now.Millisecond); for (int i = 0; i < 300; i++) { rtn = rtn + rnd.Next(-100, 1000); } Thread.Sleep(new TimeSpan(0, 0, seconds)); return rtn; } }
最主要的代码就是创建带返回值的异步任务与Model层进行交互,命令被调用时,界面会保持响应状态。而Model层交互完成之后更新ViewModel层的数据。
此模式称为Future模式(带返回值得异步调用)。
// create parallel task ,async Task<string> engineTask = Task.Factory.StartNew<string>(() => engine.SimulateLongTimeWorkWithParameter(para, 5)); //UI callback engineTask.ContinueWith(task => { this.DataStringFromEngine = task.Result; IsEngineFree = true; CalculatingStatus = "Complete!"; });
作者:Andy Zeng
欢迎任何形式的转载,但请务必注明出处。