Prism 4 文档
- 作者: 五速梦信息网
- 时间: 2026年04月04日 13:33
在上一章中描述了如何通过将UI,表现逻辑,业务逻辑分别放到三个单独的类中(View,View Model,Model),实现这些类之间的交互(通过数据绑定,命令以及数据验证接口)以及实现一个策略来处理建筑和绑定的方式实现MVVM的基本元素。
通过使用实现MVVM的这些基本元素的方式可以支持应用程序中许多的应用场景。然而,您可能会遇到更复杂的场景,需要扩展基本MVVM模式或者需要应用更先进的技术。如果你的应用程序比较大或者比较复杂,这种情况很有可能会发生,但也可能在很小的应用中遇到这些场景。Prism类库提供了许多已经实现了这些技术的组件,允许你可以更加容易的在应用程序中使用它们。
本章介绍了一些复杂的场景,并介绍了MVVM模式如何支持他们。下一节将说明如何命令可以链接在一起,或与子视图,以及他们如何可以扩展到支持自定义的要求。以下各节则描述了如何处理异步数据请求和随后的UI交互,以及如何处理的视图和视图模型之间的交互请求。
本节为提供了当使用依赖注入容器时处理构造方式和wire-up的指导,例如Unity或者使用MEF。最后一节介绍了如何通过单元测试您的应用程序的ViewModel和Model类提供指导测试MVVM应用程序,以及测试的行为。
命令
命令提供了将命令的实现逻辑从UI展现中分离出来的一种方式,数据绑定和行为提供了将View中声明的元素与ViewModel中提供的命令相关联的一种方式。在第5章实现MVVM模式中描述了如何在ViewModel中将命令实现为一个命令对象或者命令方法,以及如何通过行为或者与特定控件内联的命令属性在View中被调用的。
注意 :
WPF Routed Commands:需要注意的是在MVVM模式中奖命令实现为命令对象或者命令方法与WPF的内建的实现路由命令是有一些不同的(Sliverlight没有任何路由命令的实现).WPF路由命令通过路由遍历元素的方式在UI元素树(特指逻辑树)中来传递命令消息。因此,命令消息在UI树中是从焦点元素或者特定的目标元素向下或者向上路由传递的;默认的,它们不会路由遍历UI树的外部组件,例如与View关联的View Model。然而,WPF路由命令可以使用视图中定义一个命令处理程序的后台代码转发命令调用视图模型类。
组合命令
在许多情况下,在ViewModel中定义的一个命令将会被绑定到与关联View中控件,那样用户可以直接从View中调用命令。然而,在一些情况下,你可能想要在一个父类View中的控件调用一个或者多个ViewModel类中的命令。
例如,在你的应用程序中允许用户同事编辑多个条目,你可能想要允许用户通过应用程序中工具栏或者功能区中某个展现为一个按钮的命令来一次保存所有的条目。在这种情况下,Save All命令将会调用Save命令在每一个ViewModel实例中的实现,如下图所示:


Prism通过CompositeCommand类支持这种场景。
CompositeCommand类代表了一个来自多个子命令聚合在一起的命令。当一个组合的命令被调用时,每个子命令将会一次的被调用。它在你需要在UI中使用一个单独的命令代表一组命令或者您希望调用多个命令来实现逻辑命令的时候很有用。
例如,CompositeCommand在Stock Tarder RI中使用,目的是在买/卖View中通过展示一个Submit All按钮来实现SubmitAllOrders命令的功能。当用户点击Submit All按钮是,每个定义在不同的个人买/卖交易中的SubmitCommand将会被执行
CompositeCommand类维护着一系列的子命令(DelegateCommand实例)。CompositeCommand类的Execute方法只是简单的依次调用每个子命令的Execute方法。CanExecute方法也只是简单的调用每个子命令的CanExecute方法。但是如果任何一个子命令不能被执行,CanExecute将会返回false。换而言之,只有所有子命令可以被执行,CompositeCommand才可以被执行。
注册及卸载子命令
通过RegisterCommand和 UnregisterCommand方法来注册和卸载子命令。在Stock Trader RI,例如,每一个买/卖的Submit和Cancel命令注册到SubmitAllOrders命令中以及CancelAllOrder命令中。如下示例(查看OrdersConttoller类):
commandProxy.SubmitAllOrdersCommand.RegisterCommand(
orderCompositeViewModel.SubmitCommand );
commandProxy.CancelAllOrdersCommand.RegisterCommand(
注意:
上面的CommandProxy对象提供了访问Submit和Cancel组合命令的实例,它被定义为静态对象。更多信息,查看StockTraderRICommands.cs文件
执行子视图的命令
经常,你的应用程序需要在UI上展示一个子View的集合,每个子View将会有一个一致的ViewModel,依次,可能实现了一个或多个命令。组合命令可以用来展现这些在UI中的子View实现并且整合了如何被父View中调用的命令。为了支持这种场景,Prism设计了同Region一起的CompositeCommand和DelegateCommand类。
Prism Region(在第7章 组合用户界面中的“Regions”一节介绍)提供使得程序的子View和UI界面中的逻辑占位符联系在一起的一种方法。他们经常被用来将子View指定的布局方式与逻辑占位符和UI中的位置解耦。Regions是基于占位符名称来联系到指定的布局控件的。下面的插图示例中展示了每个子View被添加到名称为EditRegion的Region中,UI设计师在Region中选用Tab控件布局View。


复合命令在父view级别通常会被用来协调命令在子view级别是如何调用的。在一些情况下,你想要所有的显示View的命令被执行,就像在前面的Save All命令。在另外一些情况下,你想要仅在活跃View的视图中的命令被执行。在这种情况下,复合命令将会执行在被认为是活跃的View中的命令;那些在非活跃View中的命令将不会被执行。例如,你可能在应用程序工具栏或者功能区实现一个缩放功能的命令,它只会使得当前活动的View进行缩放,如下图所示:


为了支持这种场景,Prism 提供了IActiveAware接口,IActiveAware接口定义了一个IsActive属性,当它的实现者出在活跃状态时返回true,定义了一个活跃状态发生变化时将会引发的IsActiveChanged事件。
你可以在子View或者ViewModel上实现IActiveAware接口。它主要用于在Region中跟踪子View的状态。一个View是否处于活动状态决定与区域适配器(Region Adaper),它负责指定Region控件中的Views。例如,就像前面展示的Tab控件,它就有一个区域适配器来设置当前选中的View处在Active状态。
DelegateCommand类也实现了IActiveAware接口。通过在构造方法中指定monitorCommandActivity参数为true来配置CompositeCommand以评估它的子DelegateCommands的活动状态(除CanExecute状态外)。这个参数被设置为true时,当确定CanExecute方法的返回值以及当执行子命令的Execute方法时,CompositeCommand类将会考虑每个子DelegateCommand的活动状态。
当monitorCommandActivity参数为true时,CompositeCommand类展现以下行为:
- CanExecute。只有当所有的活动的命令可以被执行时,才会返回true。那些非活动的子命令将不会被考虑。
- Execute。执行所有的活动的命令。非活动的命令将不会被考虑。
你可以利用这个功能来实现前面的例子。通过在你的子ViewModel中实现IActiveAware接口,在Region中的子View的变成活动或者非活动时你都会被通知。当子View的状态改变时,你可以更新子命令的状态。然后,当用户调用Zoom复合命令时,活动的子View的Zoom命令将会执行。
集合命令
另一种常见的情况,你显示在视图中的项目集合时会经常遇到的是,当你需要的用户界面为每个项目集合中要与在父视图级别(而不是项目级)的命令有关。
例如,在如下图所示的应用程序中,视图显示项目的集合在一个ListBox控件,用于显示每个项的数据模板定义了一个删除按钮,允许用户删除从集合中的个别项目。


因为ViewModel实现了Delete命令,面临的挑战是要连接的Delete按钮在用户界面的每个项目,由ViewModel实现的Delete命令。
困难的产生是由于在ListBox中的每一项的数据上下文引用的集合中的项,而不是一个实现的删除命令中的父ViewModel中的项。
解决这个问题的一种方法是在数据模板中使用ElementName属性绑定父View中的命令,来保证绑定是相对于父控件,而不是相对于数据模板,下面的XAML展示了这种技术:
<Grid x:Name="root">
<ListBox ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=Name}" Command="{Binding ElementName=root, Path=DataContext.DeleteCommand}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
数据模板中的按钮控件的内容绑定到了集合项中的Name属性。然而,按钮的命令通过使用root元素的数据上下文绑定到了Delete命令。这使得按钮的命令绑定到了父View级别而不是项目级别。你可以使用CommandParameter属性指定哪一项应用命令或者你可以实现命令来操作当前选中项(使用CollectionView)。
命令行为
在Sliverlight3和更早版本中,Silverlight中控件不直接支持命令。ICommand接口可以用。但是没有控件实现了Command属性来使得它们直接拥有ICommand实现的钩子。为了解决这个限制并在Silverlight3中支持MVVM模式,Prism类库(2.0版本)提供了一种通过附加属性机制来允许任何Silverlight控件绑定到命令对象。这种机制在WPF中同样起作用,这使得ViewModel实现可以在Silverlight和WPF应用程序之间复用。
接下来的例子展示了,Prism 命令对象是如何将一个按钮事件绑定到在ViewModle中定义的命令对象的。
<Button Content="Submit All"
prism:Click.Command="{Binding Path=SubmitAllCommand}"
prism:Click.CommandParameter="{Binding Path=TickerSymbol}" />
Silverlight4为所有Hyperlink派生控件和ButtonBase派生控件支持了Command属性,使得它们可以像在WPF中一样可以直接绑定到命令对象,在第5章 “实现MVVM模式”中的“命令”一节描述了这些控件的Command属性的使用。然而,Prism 命令行为仍然支持向后兼容,并且支持发展自定义行为,接下来会描述。
行为方式是一种通用可行的技术,用来实施并在某种程度上封装交互行为使得很容易被应用到View中的控件的一种方式。
扩展Prism命令行为,在前面的使用行为来支持命令仅是行为可以支持的许多场景之一。Blend已经提供了各种各样的行为,包括第5章“实现MVVM模式”中“从视图调用命令方法”一节中描述的InvokeCommandAction和CallMethodAction,并且SDK允许开发自定义行为。Blend提供了拖拽创建和属性编辑行为。这使得添加任务非常方便。关于更多开发自定义Blend行为的知识,请看MSDN上的“Creating Custom Behaviors"
虽然Silverlight4中引入了对命令的支持, 并且引入了Blend SDK中的行为,但是避免太多的必要性Prism命令的行为,你会发现他们的紧凑语法和实施,以及他们的能力可以很容易地扩展,是有用的。
扩展Prism命令行为
Prism命令行为是基于一个附加的行为模式。这种模式通过连接到控制的ViewModel所提供的命令对象引发的事件。Prism命令的行为是由两部分组成:一个附加的属性和行为对象。附加属性确定了目标控制和行为对象之间的关系。行为对象监视目标控件和采取基于事件的动作或控件状态的变化或者ViewModel。
Prism命令通过提供ButtonBaseClickCommandBehavior类和一个附加属性附加到目标控件的点击事件来执行基于ButtonBase派生控件的Click事件。下面的插图展示了ButtonBase,ButtonBaseClickCommandBehavior 和ViewModel提供的ICommand对象之间的关系。


你的应用程序可能需要从控件或者事件调用命令而不是从ButtonBase的Click事件,或者你可能需要自定义目标控件和绑定的View model之间的行为交互方式。在这种情况下,你将需要定义你自己的附加属性和/或行为实现。
Prism类库提供了CommandBehaviorBase<T>类使得创建同ICommand 对象交互的行为变得简单。这个类调用命令并且监视命令的CanExecuteChanged事件的变化,并且它可以用来在Silverlight和WPF中扩展命令。
为了创建自定义的行为,创建一个继承自CommandBehaviorBase<T>的类并且关联你需要监视的目标控件。这个类的参数指定了行为被附加的控件的类型。在你的类的构造方法中,你可以从你监视的控件订阅事件。下面的例子展示了是实现了ButtonBaseClickCommandBehavior的类。
public class ButtonBaseClickCommandBehavior : CommandBehaviorBase<ButtonBase>
{
public ButtonBaseClickCommandBehavior(ButtonBase clickableObject)
: base(clickableObject)
{
clickableObject.Click += OnClick;
} private void OnClick(object sender, System.Windows.RoutedEventArgs e)
{
ExecuteCommand();
}
}
使用CommandBehaviorBase<T>类,你可以定义你自己的自定义行为类;这允许你自定义目标控件和ViewModel提供的命令之间的行为交互。例如,你可以定义一个行为,它调用一个基于不同控件事件的命令或者改变一个基于绑定命令的CanExecute状态控件的可视化状态。
为了支持声明式将命令行为附加到目标控件,一个附加属性将会被使用。这个附加属性将允许在XAML中奖行为附加到控件上,并且管理构造方法和关联目标控件与行为实现。这个附加属性被定义在一个静态类中。Prism命令行为是基于公约,静态类指的是事件的名称,用于调用命令。附加的属性的名称是指被数据绑定的对象的类型。因此,前面描述的Prism命令行为使用一个名为Click的静态类,它定义了一个附加属性命名Command。这允许使用Click.Command语法所示。
命令行为对象本身其实也通过一个附加属性与目标控件相关联。然而,这个附加属性私有静态类和开发人员不可见。
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(Click),
new PropertyMetadata(OnSetCommandCallback)); private static readonly DependencyProperty ClickCommandBehaviorProperty =
DependencyProperty.RegisterAttached(
"ClickCommandBehavior",
typeof(ButtonBaseClickCommandBehavior),
typeof(Click),
null);
实现命令的附加属性创建ButtonBaseClickCommandBehavior类的一个实例,通过OnSetCommandCallback回调方法,如以下代码示例所示。
private static void OnSetCommandCallback(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
ButtonBase buttonBase = dependencyObject as ButtonBase;
if (buttonBase != null)
{
ButtonBaseClickCommandBehavior behavior = GetOrCreateBehavior(buttonBase);
behavior.Command = e.NewValue as ICommand;
}
} private static void OnSetCommandParameterCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
ButtonBase buttonBase = dependencyObject as ButtonBase;
if (buttonBase != null)
{
ButtonBaseClickCommandBehavior behavior = GetOrCreateBehavior(buttonBase);
behavior.CommandParameter = e.NewValue;
}
} private static ButtonBaseClickCommandBehavior GetOrCreateBehavior(
ButtonBase buttonBase )
{
ButtonBaseClickCommandBehavior behavior =
buttonBase.GetValue(ClickCommandBehaviorProperty) as
ButtonBaseClickCommandBehavior;
if ( behavior == null )
{
behavior = new ButtonBaseClickCommandBehavior(buttonBase);
buttonBase.SetValue(ClickCommandBehaviorProperty, behavior);
} return behavior;
}
关于附加属性的更多信息,请参阅附加属性在MSDN概述。
处理异步交互
你的ViewModel将会经常需要同应用程序的服务和组件进行异步的通信交互而不是同步交互。这将非常场景如果你在创建一个Sliverlight应用程序或者同一个Web Service进行交互 或者通过网络访问其他资源,或者是你的应用程序使用后台任务来执行计算或者I/O。异步执行这些操作可以保证你的应用程序仍能响应这对于提供一个良好的用户体验是关键的。
IAsyncResult asyncResult = this.service.BeginGetQuestionnaire(GetQuestionnaireCompleted, null // object state, not used in this example); private void GetQuestionnaireCompleted(IAsyncResult result)
{
try
{
questionnaire = this.service.EndGetQuestionnaire(ar);
}
catch (Exception ex)
{
// Do something to report the error.
}
}
var dispatcher = System.Windows.Deployment.Current.Dispatcher;
if (dispatcher.CheckAccess())
{
QuestionnaireView.DataContext = questionnaire;
}
else
{
dispatcher.BeginInvoke(
() => { Questionnaire.DataContext = questionnaire; });
}
this.questionnaireRepository.GetQuestionnaireAsync(
(result) =>
{
this.Questionnaire = result.Result;
});
this.questionnaireRepository.GetQuestionnaireAsync(
(result) =>
{
if (result.Error == null) {
this.Questionnaire = result.Result;
...
}
else
{
// Handle error.
}
})
var result =
interactionService.ShowMessageBox(
"Are you sure you want to cancel this operation?",
"Confirm",
MessageBoxButton.OK );
if (result == MessageBoxResult.Yes)
{
CancelRequest();
}
interactionService.ShowMessageBox(
"Are you sure you want to cancel this operation?",
"Confirm",
MessageBoxButton.OK,
result =>
{
if (result == MessageBoxResult.Yes)
{
CancelRequest();
}
});
public interface IInteractionRequest
{
event EventHandler<InteractionRequestedEventArgs> Raised;
} public class InteractionRequest<T> : IInteractionRequest
{
public event EventHandler<InteractionRequestedEventArgs> Raised; public void Raise(T context, Action<T> callback)
{
var handler = this.Raised;
if (handler != null)
{
handler(
this,
new InteractionRequestedEventArgs(
context,
() => callback(context)));
}
}
}
public IInteractionRequest ConfirmCancelInteractionRequest
{
get
{
return this.confirmCancelInteractionRequest;
}
} this.confirmCancelInteractionRequest.Raise(
new Confirmation("Are you sure you wish to cancel?"),
confirmation =>
{
if (confirmation.Confirmed)
{
this.NavigateToQuestionnaireList();
}
});
}
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger
SourceObject="{Binding ConfirmCancelInteractionRequest}"> <prism:PopupChildWindowAction
ContentTemplate="{StaticResource ConfirmWindowTemplate}"/> </prism:InteractionRequestTrigger>
</i:Interaction.Triggers> <UserControl.Resources>
<DataTemplate x:Key="ConfirmWindowTemplate">
<Grid MinWidth="250" MinHeight="100">
<TextBlock TextWrapping="Wrap" Grid.Row="0" Text="{Binding}"/>
</Grid>
</DataTemplate>
</UserControl.Resources>
[Import]
public QuestionnaireViewModel ViewModel
{
set { this.DataContext = value; }
}
[Export]
public class QuestionnaireViewModel : NotificationObject
{
...
}
public QuestionnaireView()
{
InitializeComponent();
} [ImportingConstructor]
public QuestionnaireView(QuestionnaireViewModel viewModel) : this()
{
this.DataContext = viewModel;
}
public QuestionnaireView()
{
InitializeComponent();
} public QuestionnaireView(QuestionnaireViewModel viewModel)
: this()
{
this.DataContext = viewModel;
}
public QuestionnaireView()
{
InitializeComponent();
} [Dependency]
public QuestionnaireViewModel ViewModel
{
set { this.DataContext = value; }
}
IUnityContainer container;
container.RegisterType<QuestionnaireViewModel>();
IUnityContainer container;
var view = container.Resolve<QuestionnaireView>();
private void NavigateToQuestionnaireList()
{
// Ask the UI service to go to the "questionnaire list" view.
this.uiService.ShowView(ViewNames.QuestionnaireTemplatesList);
}
public void ShowView(string viewName)
{
var view = this.ViewFactory.GetView(viewName);
this.MainWindow.CurrentView = view;
}
var changeTracker = new PropertyChangeTracker(viewModel); viewModel.CurrentState = "newState"; CollectionAssert.Contains(changeTracker.ChangedProperties, "CurrentState");
var changeTracker = new PropertyChangeTracker(viewModel); var question = viewModel.Questions.First() as OpenQuestionViewModel;
question.Question.Response = "some text"; CollectionAssert.Contains(changeTracker.ChangedProperties, "UnansweredQuestions");
// Invalid case
var notifyErrorInfo = (INotifyDataErrorInfo)question; question.Response = -15; Assert.IsTrue(notifyErrorInfo.GetErrors("Response").Cast<ValidationResult>().Any()); // Valid case
var notifyErrorInfo = (INotifyDataErrorInfo)question; question.Response = 15;
Assert.IsFalse(notifyErrorInfo.GetErrors("Response").Cast<ValidationResult>().Any());
var helper =
new NotifyDataErrorInfoTestHelper<NumericQuestion, int?>(
question,
q => q.Response); helper.ValidatePropertyChange(
6,
NotifyDataErrorInfoBehavior.Nothing);
helper.ValidatePropertyChange(
20,
NotifyDataErrorInfoBehavior.FiresErrorsChanged
| NotifyDataErrorInfoBehavior.HasErrors
| NotifyDataErrorInfoBehavior.HasErrorsForProperty);
helper.ValidatePropertyChange(
null,
NotifyDataErrorInfoBehavior.FiresErrorsChanged
| NotifyDataErrorInfoBehavior.HasErrors
| NotifyDataErrorInfoBehavior.HasErrorsForProperty);
helper.ValidatePropertyChange(
2,
NotifyDataErrorInfoBehavior.FiresErrorsChanged);
questionnaireRepositoryMock
.Setup(
r =>
r.SubmitQuestionnaireAsync(
It.IsAny<Questionnaire>(),
It.IsAny<Action<IOperationResult>>()))
.Callback<Questionnaire, Action<IOperationResult>>(
(q, a) => callback = a); uiServiceMock
.Setup(svc => svc.ShowView(ViewNames.QuestionnaireTemplatesList)) .Callback<string>(viewName => requestedViewName = viewName);
submitResultMock
.Setup(sr => sr.Error)
.Returns<Exception>(null);
CompleteQuestionnaire(viewModel);
viewModel.Submit();
// Simulate callback posted to the UI thread.
callback(submitResultMock.Object);
// Check expected behavior – request to navigate to the list view.
Assert.AreEqual(ViewNames.QuestionnaireTemplatesList, requestedViewName);
- 上一篇: procps包里面的sysctl命令
- 下一篇: printerror(ErrorUrl 404页面
相关文章
-
procps包里面的sysctl命令
procps包里面的sysctl命令
- 互联网
- 2026年04月04日
-
PROFIBUS电阻
PROFIBUS电阻
- 互联网
- 2026年04月04日
-
Program.cs文件分析
Program.cs文件分析
- 互联网
- 2026年04月04日
-
printerror(ErrorUrl 404页面
printerror(ErrorUrl 404页面
- 互联网
- 2026年04月04日
-
print spooler服务关闭 怎么开启
print spooler服务关闭 怎么开启
- 互联网
- 2026年04月04日
-
PQ3502转16进制显示值
PQ3502转16进制显示值
- 互联网
- 2026年04月04日






