在利用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());
//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, ));
//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 = ; i < ; i++)
{ rtn = rtn + rnd.Next(-, );
}
Thread.Sleep(new TimeSpan(,,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 = ; i < ; i++)
{
rtn = rtn + rnd.Next(-, );
}
Thread.Sleep(new TimeSpan(, , seconds));
return rtn;
}
}

ModelSimulator

最主要的代码就是创建带返回值的异步任务与Model层进行交互,命令被调用时,界面会保持响应状态。而Model层交互完成之后更新ViewModel层的数据。

此模式称为Future模式(带返回值得异步调用)。

  // create parallel task ,async
Task<string> engineTask = Task.Factory.StartNew<string>(() => engine.SimulateLongTimeWorkWithParameter(para, ));
//UI callback
engineTask.ContinueWith(task =>
{ this.DataStringFromEngine = task.Result;
IsEngineFree = true;
CalculatingStatus = "Complete!";
});

作者:Andy Zeng

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

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

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

  1. WPF系列教程——(二)使用Prism实现MVVM设计模式 - 简书

    原文:WPF系列教程--(二)使用Prism实现MVVM设计模式 - 简书 本文假设你已经知道MVVM设计模式是什么,所以直接进入正题,今天我们就用Prism来实现WPF的MVVM设计模式,百度上关于 ...

  2. 在WPF中使用依赖注入的方式创建视图

    在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...

  3. [WPF系列]-数据邦定之DataTemplate 根据对象属性切换模板

      引言 书接上回[WPF系列-数据邦定之DataTemplate],本篇介绍如何根据属性切换模板(DataTemplate)   切换模板的两种方式:   使用DataTemplateSelecto ...

  4. [WPF系列]从基础起步学习系列计划

    引言 WPF技术已经算不什么新技术,一搜一大把关于WPF基础甚至高级的内容.之前工作中一直使用winform所以一直没有深入学习WPF,这次因项目中使用了WPF技术来实现比较酷的展示界面.我在这里只是 ...

  5. 《Programming WPF》翻译 第8章 5.创建动画过程

    原文:<Programming WPF>翻译 第8章 5.创建动画过程 所有在这章使用xaml举例说明的技术,都可以在代码中使用,正如你希望的.可是,代码可以使用动画在某种程度上不可能在x ...

  6. 《Programming WPF》翻译 第6章 1.创建和使用资源

    原文:<Programming WPF>翻译 第6章 1.创建和使用资源 资源这个词具有非常广泛的意义.任何对象都可以是一个资源.一个在用户界面中经常使用的Brush或者Color可以是一 ...

  7. JavaScript系列-----对象基于哈希存储(<Key,Value>之Value篇) (3)

    JavaScript系列-----Objectj基于哈希存储<Key,Value>之Value 1.问题提出 在JavaScript系列-----Object之基于Hash<Key, ...

  8. 基于xlua和mvvm的unity框架

    1.框架简介 这两天在Github上发现了xlua的作者车雄生前辈开源的一个框架—XUUI,于是下载下来学习了一下.XUUI基于xlua,又借鉴了mvvm的设计概念.xlua是目前很火的unity热更 ...

  9. saltstack自动化运维系列11基于etcd的saltstack的自动化扩容

    saltstack自动化运维系列11基于etcd的saltstack的自动化扩容 自动化运维-基于etcd加saltstack的自动化扩容# tar -xf etcd-v2.2.1-linux-amd ...

随机推荐

  1. [题解] 洛谷 P3603 雪辉

    模拟赛中遇到了这个题,当时我这个沙雕因为把一个\(y\)打成了\(x\)而爆零.回来重新写这道题,莫名其妙的拿了rank1... 我的解法与其他几位的题解有些不同我太蒻了.并没有选取所谓的关键点,而是 ...

  2. python项目通过配置文件方式配置日志-logging

    背景:项目中引入日志是必须的,这里介绍通过配置文件config.ini的方式配置日志 1.新建config.ini 2.添加配置 [loggers]keys=root,ProxyIP [handler ...

  3. Centos7 Zabbix添加主机、图形、触发器

    制作自定义key zabbix自带模板Template OS Linux (Template App Zabbix Agent)提供CPU.内存.磁盘.网卡等常规监控,只要新加主机关联此模板,就可自动 ...

  4. 学习调用第三方的WebService服务

    互联网上面有很多的免费webService服务,我们可以调用这些免费的WebService服务,将一些其他网站的内容信息集成到我们的应用中显示,下面就以查询国内手机号码归属地为例进行说明. 首先安利一 ...

  5. 解决Ubuntu16.04下git clone太慢问题

    记录一些博客,省着自己再去找了... ss-qt5安装 生成.pac genpac --pac-proxy "SOCKS5 127.0.0.1:1080" --gfwlist-pr ...

  6. Alpha 冲刺7

    队名:日不落战队 安琪(队长) 今天完成的任务 完善回收站. 学习okhttp. 明天的计划 继续研究okhttp. 尝试登录的数据对接. 还剩下的任务 个人信息对接. 遇到的困难 今天白天焊接,晚上 ...

  7. lintcode-473-单词的添加与查找

    473-单词的添加与查找 设计一个包含下面两个操作的数据结构:addWord(word), search(word) addWord(word)会在数据结构中添加一个单词.而search(word)则 ...

  8. 小程序解密 encryptedData 获取 unionID 等信息

    index.php <?php include_once "wxBizDataCrypt.php"; // $appid 由小程序微信官方后台获取 $appid = 'wx4 ...

  9. git因commit的记录太大导致push失败解决方法

    发现好像这个方法不好使.......~~!还是会失败 如果有人或者自己失误把不该同步的大文件如数据或日志或其他中间文件给commit了并且push了,然后你删掉了,但是其实他仍然在你的git记录中,你 ...

  10. UVA11734_Big Number of Teams will Solve This

    题意很简单,本身也是个水题. 给你两个字符串,如果两个串完全相同,那么输出yes:如果两串只是空格的不同,出去空格后完全相同,那么输出Output Format Error,否则输出Wrong Ans ...