Messenger和MVVM中的View Services
在前面的文章IoC容器和MVVM中, 介绍了IoC容器如何在大量用户类中帮助创建和分配用户类的实例。本文将介绍IoC容器如何帮助应用程序解耦,比如那些根据MVVM模式开发的应用。此模 式广泛应用在基于XAML的应用程序(Silverlignt, WPF, Windows Phone, Windows 8)中,因为此模式与数据绑定系统和用于这类程序设计的工具匹配的很好,尤其是在VS 设计器和Blend中。
在典型的XAML程序中,开发者利用数据绑定系统声明一个XAML UI元素的属性和应用程序中其他对象的属性之间的同步。这种绑定可以是单方向和双方向的。数据绑定非常方便,特别是在用可视化设计器的时候,比如VS设计 器或Blend。但这也有局限性。例如:一个简单的数据绑定不能触发UI中的动画或触发一个显示给用户的对话框。即使是基本的动作比如迁移到其他页面,这 也不能通过数据绑定来实现。
Figure 1 shows two-way data binding between a XAML view and its ViewModel (point 1). In addition, other possible interactions are represented.
Figure 1. Dependencies between Layers in MVVM
箭头2表明了一个普通的事件流。这可能是在XAML程序中和后台代码进行交互的最有名的方式。很多来自传统开发环境中的开发者对此也都很熟悉,比如 Windows Forms甚至是HTML/JavaScript。事件方式很有用,但是在XAML程序需要和代码进行解耦时则会引发问题。一种情况是XAML中的 DataTemplate需要移动到ResourceDictionary中的时候,另一种情况是处理事件的代码需要从后台移动到另一个对象,比如 ViewModel。在XAML和后台代码之间,事件导致了紧耦合,并且限制了代码的重构。
箭头3表明了XAML在代码中触发一个动作的另外一种方式。通常情况下,命令通过ViewModel的一个属性(实现了 ICommand接口)暴露出来,然后绑定到XAML UI元素上。例如:按钮支持命令属性,当按钮按下时,绑定的命令将会被触发。命令也有它的局限性,尤其是只有少数的UI元素才有Command属性并且只 能为一个事件(通常是Click事件)使用。如果有其他的事件需要处理,默认的命令显然是不够的。在这个系列文章中也会讨论命令局限性的解决办法,尤其是 使用MVVM Light的RelayCommand组件。
箭头4表明View的后台代码在ViewModel中直接调用方法,这听起来好像和View需要和ViewModel解耦是相违背的。事实上,View知 道它对应的ViewModel并不是问题,因为View很少需要抽象。一个View的后台代码很少需要单元测试。因为编程地触发一个事件来测试它的动作不 是那么简单。因此,ViewModel应该不知道View的存在,而反过来则没必要。下面的代码在MVVM程序中经常看到。根据MVVM的一个原则,开发 者应该保持后台代码量少,但有时用一点点后台代码来处理特殊情况比寻求一个复杂的解决方案来的更简单。
Figure 2. Getting the ViewModel in the View’s Code-Behind
public MainViewModel Vm
{ get
{
return (MainViewModel)DataContext;
}
}
在Windows 8中,从View中调用ViewModel是为了减轻一个问题:在绑定中没有UpdateSourceTrigger属性。而在WPF则不是这样。当一个 TextBox的Text属性绑定到ViewModel中的String类型字段时,我们可以使用下面这样的代码(只能在WPF中)
Figure 3. UpdateSourceTrigger Property in WPF XAML Markup
<TextBox Text="{Binding ZipCode,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource ZipCodeTextBoxStyle}" />
上面的代码能够运行是因为UpdateSourceTrigger的PropertyChanged,这个绑定在用户每次输入字符的时候都会触发。在 UpdateSourceTrigger中还有一个值是LostFocus,这意味着当用户把焦点移动到其他控件的时候,此绑定将会被触发。
在Windows RT中,绑定系统中并没有这个属性。而双向绑定在TextBox失去焦点时总会被触发。大部分情况下这都没问题,但这也会引发多余的问题。例如:假设显示一个验证对话框给用户,在用户输入的情况下更新验证对话框会是一个不错的体验。
Figure 4. Working Around the Lack of UpdateSourceTrigger.PropertyChanged in Windows RT
<!-- XAML markup -->
<TextBox Text="{Binding ZipCode, Mode=TwoWay}"
TextChanged="ZipCodeTextChanged"
Style="{StaticResource ZipCodeTextBoxStyle}" />
// Code behind
private void ZipCodeTextChanged(object sender, TextChangedEventArgs e)
{
var textbox = (TextBox)sender;
Vm.ZipCode = textbox.Text;
}
此时,ViewModel的ZipCode属性在用户输入字符的都回更新,这也会更新显示给用户的验证对话框的内容。
ViewModel到View的通信
有些人可能已经主要到在图1中没有从ViewModel到View的通信方式。文章的前面也说过,ViewModel应该对于使用它的View的存在是未知的。事实上,将一个ViewModel用于多个View是非常普遍的。
解决这个问题有很多途径。在这里要介绍的两个方案是使用MVVM Light的Messenger类和使用view services。
首先,要使用MVVM Light的Messenger实现一个状态消息系统。此类是消息总线的一个实现,它实现了消息发送方和消息接收方之间的解耦。这对于状态消息系统来说是十分方便的,因为应用程序的每一部分都可以选择显示给用户一个状态而不用担心依赖性。
一个关于Messenger的警告
Messenger是一个强大的组件,它可以极大地促进通信,但同时也会使得代码很难调试。因为消息的发送方和接收方相互之间都是未知的,所有也就很难第一眼就看出哪个接收方接收了消息。需要小心使用。
在MVVM Light工具包的GalaSoft.MvvmLight.Messaging名称空间下有很多预定义的消息类。同时也很方便定义自己的消息。比如在RssReader例子,在此会使用一个状态消息显示给用户来提示用户当前应用的状态。
首先,定义一个状态消息类,代码如下所示。
Figure 5. Defining the Message Type
public class StatusMessage
{
public StatusMessage(
string status,
int timeoutMilliseconds)
{
Status = status;
TimeoutMilliseconds = timeoutMilliseconds;
}
public string Status
{
get; private set;
}
public int TimeoutMilliseconds
{
get; private set;
}
}
MainViewModel的Refresh方法要轻微地修改一下,代码如下。在调用异步方法之前,发送一个StatusMessage。在数据接收和处 理之后,再发送一个生命期为3秒的StatusMessage。这个前提条件是有消息接收方注册了并且接收方知道处理这个消息。 StatusMessage是发送方和接收方之间的合约。
Figure 6. Setting the Status
public async Task Refresh()
{
Items.Clear();
Messenger.Default.Send(new StatusMessage("Getting articles", 0));
var list = await _rssService.GetArticles();
foreach (var item in list)
{
Items.Add(item);
}
Messenger.Default.Send(new StatusMessage("Done", 3000));
}
在view侧,需要一个类来显示这个状态(此时这里是MainPage),并且需要注册接收 StatusMessage类型。为了尽可能地保持Messenger简单清晰,我们将在OnNavigatedTo方法中注册消息处理方法,在 OnNavigatingFrom注销此方法。这个方法确保一次只有一个页面注册显示状态信息。
Figure 7. Showing the Status in Windows RT
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Messenger.Default.Register<StatusMessage>(
this, HandleStatusMessage);
base.OnNavigatedTo(e);
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
Messenger.Default.Unregister<StatusMessage>(
this, HandleStatusMessage);
base.OnNavigatingFrom(e);
}
private void HandleStatusMessage(StatusMessage msg)
{
Status.Message = msg.Status;
Status.Show(msg.TimeoutMilliseconds);
}
页面使用一个名字为Status的UserControl来显示消息。当需要显示消息是,设置此控件的Visibility属性为Visibility。当显示时间完了,将Visibility属性设置为Collapsed。
实现一个DialogService
使用Messenger服务从ViewModel显示一个消息到view是一个比较不错的方案。但是其中一个缺点就是对于当一个第一次看此代码的开发者来 说就不是那么清晰。因为消息的发送方和接收方互不知道。调试的时候工作流很难跟踪。一个有意思的替代方案就是向IoC容器中注册一个抽象的服务。在 RssService中,DialogService就是一个面向view的服务。
首先,在服务接口中定义一些常用到的方法。比如显示状态信息,错误信息等。在示例代码中,定义的方法如下。
Figure 8. The IDialogService Interface
public interface IDialogService
{
void ShowMessage(string message, string title, string buttonText);
void ShowError(string errorMessage, string title, string buttonText);
void ShowError(Exception error, string title, string buttonText);
}
接口的实现可以根据使用平台的不同而不同。为了保持简单,示例代码Windows Phone中使用了默认的MessageBox,Windows 8中使用了默认的MessageDialog。在现实工程中,可以使用自定义的消息框。DialogService的Windows RT实现版本如下所示。
Figure 9. Implementation of IDialogService in MainPage for Windows RT
public sealed partial class MainPage : IDialogService
{
public MainPage()
{
InitializeComponent();
} // More methods removed for brevity... // IDialogService implementation public async void ShowMessage(string message, string title, string buttonText)
{
// Using the same MessageDialog for errors and messages.
// In a real-life production implementation, a custom
// dialog box would be designed, and these methods would probably
// be passed into a Page base class (for instance in
// the LayoutAwarePage class in the Common folder.
var dialog = new MessageDialog(message, title); dialog.Commands.Add(new UICommand(buttonText)); dialog.CancelCommandIndex = 0;
await dialog.ShowAsync();
} public void ShowError(string errorMessage, string title, string buttonText)
{
// Using the same MessageDialog for errors and normal messages.
ShowMessage(errorMessage, title, buttonText);
} public void ShowError(Exception error, string title, string buttonText)
{
ShowMessage(error.Message, title, buttonText);
}
}
MainPage需要将它自己视为一个IDialogService注册到IoC容器中。因为一次只能由一个页面显示,所以同时需要在迁移到其他页面时注销。我们在OnNavigatedTo和OnNavigatedFrom方法中做这两个操作。
Figure 10. Registering and Unregistering the IDialogService in MainPage
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Messenger.Default.Register<StatusMessage>(
this, HandleStatusMessage); SimpleIoc.Default.Register<IDialogService>(() => this); base.OnNavigatedTo(e);
} protected override void OnNavigatedFrom(NavigationEventArgs e)
{
Messenger.Default.Unregister<StatusMessage>(
this, HandleStatusMessage); SimpleIoc.Default.Unregister<IDialogService>(); base.OnNavigatedFrom(e);
}
在MainPageViewModel中,通过从IoC容器获取IDialogService并且通过一个属性字段暴露出来。代码如下。
Figure 11. Refresh Method with Error Handling
public IDialogService DialogService
{
get
{
return ServiceLocator.Current.GetInstance<IDialogService>();
}
} public async Task Refresh()
{
Items.Clear(); try
{
Messenger.Default.Send(new StatusMessage("Getting articles", 0)); var list = await _rssService.GetArticles(); if (list.Count == 0)
{
DialogService.ShowMessage(
"We couldn't find any articles",
"Nothing found",
"Too bad!");
} foreach (var item in list)
{
Items.Add(item);
} Messenger.Default.Send(new StatusMessage("Done", 3000));
}
catch (Exception ex)
{
// Hide status
Messenger.Default.Send(new StatusMessage(string.Empty, 1)); DialogService.ShowError(
ex,
"Error when loading",
"Oops");
}
}
Messenger和MVVM中的View Services的更多相关文章
- Messenger在MVVM模式中的应用
Messenger在MVVM模式中的应用 Messenger在MVVM中应用的前提 我们知道在MVVM架构中,系统平台的Silverlight客户端界面开发和业务逻辑已经被分开,XAML是SL的主要部 ...
- WPF MVVM 如何在ViewModel中操作View中的控件事件
(在学习Wpf的时候,做一个小例子,想在TextBox改变后,检验合法性,并弹出提示.在找了很多贴后,发现这个小例子,抄袭过来,仅供参考. 最后也找到了适合自己例子的办法:在出发TextChanged ...
- WPF MVVM中在ViewModel中关闭或者打开Window
这篇博客将介绍在MVVM模式ViewModel中关闭和打开View的方法. 1. ViewModel中关闭View public class MainViewModel { public Delega ...
- wpf MVVM ViewModel 关闭View显示
上次说到了不同wpf窗体之间的交互,这个在MVVM模式之中用起来会方便很多 下面我来说下在ViewModel中关闭View的方法,其实也很简单的,注释照样不写,一看就懂的 public partial ...
- javascript基础修炼(9)——MVVM中双向数据绑定的基本原理
开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. 一. 概述 1.1 MVVM模型 MVVM模型是前端单页面应用中非常重要的模型之一,也是Single Page Appl ...
- MVVM中的vm双向监听和mvc的缺点
`MVVM`模型: - 即Model,模型,包括数据和一些基本操作 - 即View,视图,页面渲染结果- 即View-Model,模型与视图间的双向操作(无需开发人员干涉) `MVVM`中的`VM`要 ...
- Atitit.java c#.net php项目中的view复用(jsp,aspx,php的复用)
Atitit.java c#.net php项目中的view复用(jsp,aspx,php的复用) 1.1. Keyword1 1.2. 前言1 2. Java项目使用.Net的aspx页面view1 ...
- IOS 开发中 Whose view is not in the window hierarchy 错误的解决办法
在 IOS 开发当中经常碰到 whose view is not in the window hierarchy 的错误,该错误简单的说,是由于 "ViewController" ...
- 解决在onCreate()过程中获取View的width和Height为0的4种方法
很经常当我们动态创建某些View时,需要通过获取他们的width和height来确定别的view的布局,但是在onCreate()获取view的width和height会得到0.view.getWid ...
随机推荐
- Ocelot Consul ACL
Ocelot允许您指定服务发现提供程序,并使用它来查找Ocelot正在将请求转发给下游服务的主机和端口.目前,这仅在GlobalConfiguration部分中受支持,这意味着所有ReRoute将使用 ...
- 语法解析 rs.next()
ResultSet.next()方法将指针从当前位置下移一行.ResultSet 指针最初位于第一行之前:第一次调用 next 方法使第一行成为当前行:第二次调用使第二行成为当前行,依此类推. 如果新 ...
- 爬虫开发6.selenuim和phantonJs处理网页动态加载数据的爬取
selenuim和phantonJs处理网页动态加载数据的爬取阅读量: 1203 动态数据加载处理 一.图片懒加载 什么是图片懒加载? 案例分析:抓取站长素材http://sc.chinaz.com/ ...
- 为什么要使用rem
为什么要使用rem 今天2019年4月16号更新,模仿网易移动端的的写法: html { font-size: 13.33333vw } @media screen and (max-width:32 ...
- 6.iptables常用规则
开启ip段192.168.1.0/24端的80口 开启ip段211.123.16.123/24端ip段的80口 # iptables -I INPUT -p tcp --dport 80 -j DRO ...
- JOISC2019Day 1試験 (Examination)
题面 官网 题解 就是个裸的三维数点,\(CDQ\)直接套上去就行了 //minamoto #include<bits/stdc++.h> #define R register #defi ...
- word的xml文件中给文字添加超链
<w:hlink w:dest="http://xxx.com"><w:r></w:r></wr></w:hlink>& ...
- SpringMVC 的初理解
项目中用到了jetty,springboot两种构建服务器的方式,jetty是一种嵌入式的方式,部署启动都很灵活,springboot最大的优点就是很多配置文件都自己集成好了,虽然用了这么多好的框架, ...
- iOS学习笔记(1)--认识Xcode6.1的Interface Builder和常用快捷键
Interface Builder基本界面 红色区域为工具栏(Tool Bar) 蓝色区域为导航区(Navigator Area) 绿色区域为编辑区(Editor Area) 黑色区域是调试区(Deb ...
- NOIP模拟赛
T1 1.聪明的小偷 (thief.pas/c/cpp) [问题描述] 从前有一个收藏家收藏了许多相同的硬币,并且将它们放在了n个排成一排的口袋里,每个口袋里都装了一定数量的硬币. 这些硬币价值不菲, ...