• 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

  • 相关阅读:
    Go安装
    Redis 安装与使用
    scala总结
    C++学习笔记4
    LeetCode 22.将数组分成和相等的三个部分
    LeetCode 21.二叉树的直径 DFS深度遍历
    LeetCode 20.买卖股票的最佳时机 暴力破解法与动态规划
    LeetCode 19.凑零钱问题 动态规划
    LeetCode 18.队列的最大值
    Java SSM Spring MVC 三层架构和MVC+SpringMVC的入门案例+请求参数的绑定+常用的注解
  • 原文地址:https://www.cnblogs.com/andyzeng/p/3701892.html
Copyright © 2020-2023  润新知