WPF系列之一:基于并行任务和MVVM创建响应灵敏和数据驱动的UI

在利用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>
MainWindow.xaml

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();
        }
    }
MainViewModel

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;
        }
    }
ModelSimulator

最主要的代码就是创建带返回值的异步任务与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

欢迎任何形式的转载,但请务必注明出处。

 http://www.cnblogs.com/andyzeng/p/3701892.html

原文地址:https://www.cnblogs.com/andyzeng/p/3701892.html