一个应用程序的用户界面(UI)可以通用以下几种模式之一来构建:
窗体所需要所有的控件都包含在一个单独的XAML文件中,在设计时组合这个窗体。
窗体的逻辑区域被分割到单独的部分中,通常指哟过户控件。这些部分被窗体引用并且在设计时组合窗体。
窗体的逻辑区域被分割到单独的部分中,通常指哟过户控件。这些部分是不被窗体所知道的,并且在运行时动态的将它们添加到窗体中。使用这种方法的应用程序称为复合应用程序。
一个复合应用程序的UI是从被认为是通常包含在程序模块中的View的可视化组件松耦合的组合起来的,但是它们不需要是。如果你将应用程序切分成模块,你需要某种方式来松散的组合UI,但是你可能甚至在View不在模块中时也选择使用这种方式。为了用户,该应用程序提供了一个无缝的用户体验,并提供了一个完全集成的应用程序。
为了组合你的UI,你需要一个允许你创建一个能松散耦合在运行时创建可视化元素的架构。另外,这个架构应该体统这些元素以一种松散耦合方式通信的策略。
Stock Trader RI 就是通过加载多个来自不同模块中的通过Shell暴露给Region的View来组合起来的,如下图所示。
UI布局概念
在一个组合应用程序中的根对象被认为是Shell。Shell在应用程序中扮演这一个母版页的角色。Shell包含了一个或者多个区域。区域是在运行时会被加载的内容的占位符。区域被附加到UI元素上,例如内容空,列表项控件,Tab控件或者一个自定义控件并且管理UI元素的内容。区域的内容可以被自动的加载或者按需要加载,这取决于应用程序的需求。
通常,一个区域的内容是一个View。一个View封装了UI的你想要尽可能与程序的其他部分保持松耦合关系的一部分,你可以将一个View定义成一个用户控件,数据模板,捉着甚至是一个自定义控件。
一个区域管理着View的显示和布局。区域可以通过它们的名称以一种松耦合的方式被访问并且支持动态的添加和移除Views,一个区域附加的一个宿主控件,将区域想象成一个动态加载View的容器。
接下来的几节将会介绍复合应用程序开发中的高级核心概念。
Shell
shell是应用程序中一个包含着主要UI内容的跟对象。在WPF应用程序中,shell是一个Window对象,在Silverlight程序中,shell是RootVisualUserControl。
shell扮演着提供应用程序布局结构的母版页的角色。shell包含着一个或者多个命名的区域,这些区域可以指定将会显示的View。它也可以定义具体的*的UI元素,例如背景,主菜单和工具条。
shell订阅了应用程序的整体外观。它可能会定义在它的布局中展现的可见的样式和边框。它也可能定义了将会应用到添加到shell中的View的样式,模板和主题等。
通常,shell是WPF应用程序项目的一部分或者Silverlight项目的主要内容。包含shell的程序集也许会也或许不会依赖包含着将会被加载到shell的区域内的Views的程序集。
Views
View是复合应用程序中UI结构中主要的单元。你可以将view定义成一个用户控件,数据模板,或者一个自定义控件。一个View封装了UI的你想要尽可能与程序的其他部分保持松耦合关系的一部分。你可以选择基于封装或者一块功能的View中发生的内容,或者你可以选择定义一些东西作为一个view因为你将会在应用程序中产生这个View的多个示例。因为WPF和Silverlight的内容模型,Prism类库定义一个view时不需要什么指定的东西。最贱的订阅一个view的方法就是定义成一个用户控件。为了往UI中添加一个view,你只需要一种方法去构造它并将它添加到容器中。WPF和Silverlight提供了实现这些内容的机制。Prism类库添加了定义了可以在运行时动态的添加到一个区域的能力。
组合Views
支持特定功能的视图可能会变得复杂。在这种情况下,你可能要将View拆分成几个子View,并由父View处理利用子View来构建自身。应用程序可能在设计时静态的配置这些,或者它可能支持在运行时通过一个包含的区域将子View添加到模块中。当你有一个在某个类中未能完整的定义View的类时,你可以选用一个组合View。在许多的情况下,一个组合view扶着这构建子view及协调tam之间的交互。你可以通过使用Prism类库命令和事件聚合组合来定义与其他子view以及它们的父view之间松耦合的子view。
Views和设计模式
虽然Prism类库并没有要求你使用它们,但是,你在实现一个view时考虑使用一种UI设计模式。Stock Trader RI和QuickStarts演示了使用MVVM作为实现一个View布局和View逻辑之间清晰的分离的模式。
推荐使用MVVM UI设计模式因为它原生的适应于XAML平台,WPF,Silverlight,以及Silverlight for Windows Phone 7。系统的依赖属性和平台丰富的数据绑定支持使得View和ViewModel之间能够以一种松耦合分方式进行通信。
view和逻辑的分离对于可测试性与可维护性非常重要,它提升了开发与设计工作流的效率。
你可以使用一个用户控件或者一个自定义控件并且将所有的逻辑卸载后台代码文件中来创建一个View,你的View将会非常难测试因为你必须在单元测试的逻辑中创建一个View的实例。这是一个问题,特别如果view源自,或依赖于运行的WPF或Silverlight组件作为其执行上下文的一部分。为了保证你可以在单元测试中在一个不依赖这些的隔离的环境中测试view逻辑。你需要能够创建视图的模拟,以消除执行上下文,这需要将view和逻辑拆分到单独的类中。
如果你将一个view作为一个数据模板,这将会使得它自身没有相关的代码。因此,你需要将这些相关的逻辑放到其他的地方。同样布局与逻辑清晰分离也是可测试性所需要的也有助于使view更易于维护。
注意:
单元测试和UI自动测试是两种不同测试覆盖的测试类型。
单元测试的最佳实践建议的对象单独进行测试。为了达到目的隔离,你需要一个样机或存根每个外部依赖。然后,单元测试颗粒可以运行侵害测试对象。
UI自动化测试运行该应用程序,适用于用户界面的手势,然后测试预期的结果。这种类型的测试验证UI元素已正确连接到应用程序逻辑。
view与逻辑的分离提供了关注点的清晰分离。另外对于可测试性的考虑,这种分离使得UI设计者与开发者相互独立。更多的关于MVVM模式,请看第五章“实现MVVM模式”。
命令,UI触发器,动作和行为
当在后台文件中实现了view的逻辑,你可以将UI交互服务添加到事件上。但是,当使用MVVM时,view model不能直接的处理UI引发的事件。为了在viewmodel中路由处理这些事件动作,你可以使用命令,UI触发器,动作,以及行为。
命令
命令分开了调用从逻辑中执行命令的逻辑对象和语义。内建的命令表明了一个动作是否可用的能力。UI中的命令绑定到了ViewModel中的ICommand属性。关于命令的更多信息。请看第5章“实现MVVM模式”中的“命令”一节。
UI触发器,动作和行为
触发器,动作和行为是Microsoft.Expression.Interactivity 命名空间下的一部分并且同Expression Blend一起发布。并且它们也是Expression SDK的一部分。触发器,动作和行为提供了一个处理UI事件或者命令的全面的API,并且通过路由将它们绑定到DataContext暴露的ICommand属性提供的方法。有关 UI触发器,动作和行为的更多信息,请看第5章“实现MVVM模式”中“从View调用命令对象”和“从View调用命令方法”这两节。
数据绑定
数据绑定是XAML平台的框架中最重要的功能之一。为了能够在XAML平台上成功的开发应用程序。你需要对数据绑定有一个深刻的理解。
数据绑定拥有依赖属性系统提供给的固有的属性变化通知机制的全部有点。当结合了CLR中INotifyPropertyChanged接口的实现时,在数据绑定中变更通知可以做到目标与源对象之间无代码的交互。
数据绑定通过使用一个值转换从一个类型转换到另一个类型从而可以使得不同目标和源类型之间的绑定。数据绑定在它的管道中保留有多重校验的钩子,你可以在用户输入时进行数据验证。
强烈的鼓励你去读一读MSDN上的"Dependency Properties Overview" and "Data Binding Overview"。这两个主题的完全理解对于在XAML平台上开发应用程序是非常关键的。有关数据绑定的更多信息,请看第5章”实现MVVM模式“的”数据绑定“一节。
区域(Regions)
在Prism类库中区域贯穿着区域管理,区域,区域适配器,接下来的几节将会介绍它们是如何在一起工作的。
区域管理
RegionManager类负责创建和维护承载控件的区域的集合。RegionManager使用了一个控件指定适配器来关联区域和承载控件。下面的插图显示了由RegionManager类创建的区域,控件和适配器之间的关系。
RegionManager类可在代码或者XAML中创建区域。RegionManager.RegionName附加属性用来应用到承载控件的附加属性中在XAML中创建区域。
应用程序可以包含一个或者多个RegionManager类的示例。你可以指定你自己的区域要注册到的RegionManager类的实例。这在你想要在可视化书中移动控件并且不想在附加属性值被删除后再次声明区域时非常有用。
RegionManager类提供了一个RegionContext附加数据来使得区域之间共享数据。
区域实现
区域是一个实现了IRegion接口的类。Region术语代表了一个可以拥有在UI上呈现的动态数据的容器。区域使得Prism类库在UI容器中通过预定义占位符来的放置模块中的动态内容。
区域可以承载任何类型的UI内容。模块中包含的UI内容可以呈现为一个用户控件,一个关联了数据模板的数据类型,一个自定义控件,或者它们的组合。这使得你可以定义UI区域的内容,然后将模块中的内容放置到UI区域中。
一个区域可以包含零个或多个子项。这取决于区域管理的承载控件的类型,可以显示一个或者多个子项。例如,ContentControl控件只能展示一个对象,然而,加载的区域可以包含多个子项,并且,ItemsControl可以展示多个子项。允许每一个都在UI中是可见的。
在下面的插图中,Stock Trader RI Shell包含了四个区域:MainRegion, MainToolbarRegion, ResearchRegion, 和ActionRegion.这些区域是被不同的模块发布的,并且这些内容可以随时被改变。
用户控件模块到区域映射
为了演示模块和内容是如何跟区域关联在一起的,情况下面的插图,它展示了WatchModule 和 NewsModule 在Shell中对于区域的关联关系。
MainRegion 包含了WatchModule中的 WatchListView 控件,ResearchRegion 包含了NewsModule中的 ArticleView 控件 。
在基于Prism窗口的应用程序中,这样的映射关系将会是设计过程的一部分,因为设计人员和开发人员使用它们来决定将内容呈现到指定的区域中。这使得设计人员来决定需要的全部空间以及确保在可用控件中展示额外项目的空间。
默认功能性区域
当你需要完全的理解区域实现来使用它们的时候,理解控件和区域之间是如何被关联的以及默认区域的功能将会很有用。例如,如何定位区域和实例化View,如何在View激活时被通知,或者view的生命周期被激活。
区域适配器
为了将UI控件暴露为一个区域,它需要一个区域适配器。区域适配器扶着创建一个区域和关联控件。这允许你使用IRegion接口通过统一的方式管理UI控件中的内容。每一个区域适配器适配一个指定的UI控件。Prism类库提供了以下三个区域适配器:
- ContentControlRegionAdapter。这个适配器适配System.Windows.Controls.ContentControl控件及其派生控件。
- SelectorRegionAdapter。这个适配器适配System.Windows.Controls.Primitives.Selector的派生控件,例如System.Windows.Controls.TabControl控件。
- ItemsControlRegionAdapter。这个适配器适配System.Windows.Controls.ItemsControl控件及其派生控件。
注意:
Silverlight版本的Prism中支持名为TabControlRegionAdapter的第四个适配器,这是因为SilverLight中的TabControl控件没有继承Selector类,并且与WPF版本的控件行为不相同。
区域行为
Prism类库介绍了区域行为的概念,这里有一些区域的多数的功能性的可插拔的组件,区域行为的引入被用来支持View发现以及区域上下文,并创建一个在WPF和Silverlight中一致的API。另外,行为提供了一种扩展区域实现的高效的方式。
区域行为是一个附加到区域中使得区域拥有额外功能的类。这种行为被附加到区域中并且在区域的生命周期中一直保持活动状态。例如,当AutoPopulateRegionBehavior附加到一个区域,它将会自动实例化,并且添加添加任何以名称注册到区域中的ViewTypes。在区域的声明周期中,它一直监视RegionViewRegistry用以新的注册,添加或替换一个已存在的自定义的区域行为是非常容易的,无论是在一个系统层面还是一个单独的基于区域的。
接下来的几节描述自动添加到所有区域中的默认行为。唯一一个附加到派生自Selector的控件行为是,SelectorItemsSourceSyncBehavior。
注册行为
RegionManagerRegistrationBehavior负责确保区域添加到正确的RegionManager中。当一个view或一个控件作为另一个控件或者区域的子项添加到可视树中时,这个控件中定义的任何区域都应该被注册到父控件的RegionManager中,当子控件被移除后,这些注册的区域将会被卸载。
自动填充行为
有两个类负责View发现功能的实现。一个是AutoPopulateRegionBehavior。当它被添加到一个区域时,它将会检索区域名称下所有注册的view类型。然后,它创建这些view的实例并将他们添加到区域中。在区域被创建之后,AutoPopulateRegionBehavior监视RegionViewRegistry用以区域名称中任何新注册的view类型。
如果你想要在view发现过程中处理更过控件,考虑创建你自己的关于IRegionViewRegistry和 AutoPopulateRegionBehavior的实现。
区域上下文行为
区域上下文的功能被包含在两个行为中:SyncRegionContextWithHostBehavior 和BindRegionContextToDependencyObjectBehavior。这些行为负责监视上下文的区域发生的变化以及通过附加到view的上下文依赖属性来同步上下文信息。
激活行为
RegionActiveAwareBehavior 负责当view在激活或者非激活时通知view,view必须实现IActiveAware接口来接受这些变化的通知。激活通知是单向的(它从行为传递到view)。view不能通过改变IActiveAware接口的激活属性来影响它的活动状态。
区域生命周期行为
RegionMemberLifetimeBehavior负责着决定某项当它不再是活动的时候是否应该从区域中移除。RegionMemberLifetimeBehavior
指定控件的行为监视着ActiveViews集合来发现那些转变为非活动状态的项目。这个行为检查IRegionMemberLifetime或RegionMemberLifetimeAttribute(按该顺序)移除的项目,已决定它是否应该在移除后保持生命周期。
如何集合中的项目是System.Windows.FrameworkElement类型的对象,它也会检查她的IRegionMemberLifetime或RegionMemberLifetimeAttribute.的DataContext属性。
这些区域项按一下顺序被检查:
- IRegionMemberLifetime.KeepAlive 值
- DataContext's IRegionMemberLifetime.KeepAlive 值
- RegionMemberLifetimeAttribute.KeepAlive 值
- DataContext's RegionMemberLifetimeAttribute.KeepAlive 值
控件指定的行为
SelectorItemsSourceSyncBehavior只用于那些派生自Selector的控件,比如WPF中的Tab控件。他负责区域中的Views和选择容器中项目之间的同步,并且负责区域中活动的Views和选中项之间的同步。
TabControlRegionSyncBehavior只用于Silverlight,它为Siliverlight中的Tab控件提供了与SelectorItemsSourceSyncBehavior相同的行为。
扩展区域实现
Prism类库提供了允许你去自定义或者扩展API提供的默认行为的扩展点。例如,你可以开发你自己的区域适配器,区域行为,改变API转换URI导航的方式,或者在Silverlight Frame Navigation中扩展API的导航工作。关于扩展Prism类库的扩展相关的内容,请看“扩展Prism”。
View 组合
view 组合是view的构建工作。在一个组合应用程序中,来自多个模块中中的views需要在运行时在应用程序UI中指定的地方展示。为了达到这个目的,你需要定义这些view将会显示的位置,以及这些view将会如何被创建及如何在这些位置展示。
View可以通过视图发现自动的方式或通过view注入以编程的方式创建和在指定位置展示。这两种技术确定了Views如何单独映射到该应用程序的用户界面中指定的位置。
View 发现
在视图发现中,你在区域的名称和view的类型之间建立RegionViewRegistry关系,当一个区域被创建时,这个区域将会查找与此区域相关联的ViewTypes并且自动的实例化它们和加载相应的views。因此,在视图发现过程中,你不必显示的控制,对应的View将会被加载以及展示。
View 注入
在视图注入中,你需要以编码的方式获得一个区域的引用,然后通过程序的方式将view添加到区域中。通常,这些在一个模块初始化时或者在作为一个用户动作的结果时进行。你将会在代码中通过名称查询一个指定区域的RegionManager,然后将view注入到其中。在视图注入中,你需要在views被加载和展示时更过的手动控制。你也拥有从区域中移除这些view的能力。然而,在视图注入中,你不能将一个view添加到一个还没有被创建的区域中。
导航
Prism 4.0的类库中包含了导航相关的API。这些导航API通过使你用URI导航到一个区域的方式简化了视图注入的过程。这些导航API实例化了View,添加它到区域中,然后激活它。另外,这些导航API允许返回到区域中创建上一个view。关于导航API的更过信息,请看第8章"导航"
何时使用View发现 VS View 注入
为区域选择使用哪种视图加载策略取决于应用程序的需求以及区域的功能。
在以下情况下使用视图发现方式:
- 期望或者要求自动的加载view
- view的单例将会被加载到区域中。
在以下情况下使用视图注入的方式:
- 你的应用程序使用了导航API
- 你需要明确或者通过编程方式控制一个view在创建和展示的时候,或者你需要工一个区域中移除一个view;例如,作为应用程序逻辑或者导航的结果。
- 你需要展示一个区域中同一个view的多个实例,这些不同的实例绑定不同的数据。
- 你需要控制将view添加到哪一个区域实例中。例如,你想要将一个客户详细信息视图添加到一个特定的客户详细区域。
UI布局场景
在组合应用程序中,来自多个模块中的视图在运行时在应用程序UI中指定的位置被展示。为了达到这点,你需要定义这些视图将会被展示的位置以及这些视图将会如何被创建及在这些位置展示。
视图和UI中的的位置之间的解耦使得应用程序的界面和布局发展为在区域中显示的独立的视图。
接下来的几描述了在你开发一个组合应用程序中将会遇到的核心场景。在适当的时候,Stock Trader RI系统的例子将会演示这种场景的解决方案。
实现Shell
Shell 是应用程序的主界面中包含的根对象,在一个WPF程序中。Shell是一个Window对象。在一个Silverlight程序中,shell是一个RootVisualUserControl。
shell可以包含已命名的区域,模块可以指定视图显示到这些区域中。它也可以定义一个确定的*的UI元素,例如主菜单和工具栏。shell定义了应用程序的整个框架和界面,这个ASP.NET的主页面控件有些相似。它可以定义在自身布局中展示的样式和边框,并且,它也可以定义添加到shell中的视图可以应用的样式,模板,和主题等。
你不需用使用一个不同的shell作为应用程序界面的一部分来使用Prism类库。如果你正在构建一个完整的组合应用程序,实现一个提供了良好定义的跟shell并且实现建立应用程序主UI的模式。然而,如果你在添加Prism类库的功能到一个已存在的应用程序,你不要必须改变应用程序的基础结构来添加一个shell。而是,你可以修改已存在的窗口或者控件的定义来添加可以引入视图的区域。
你也可以在应用程序中存在不止一个应用shell。如果你将会=设计为用户打开多余一个的*窗口的应用程序,每一个*的窗口扮演着包含内容的shell。
Stock Trader RI Shell
WPF Stock Trader RI 使用一个shell作为它的主窗口。在下面的插图中,shell和视图被高亮显示。shell是WPF Stock Trader RI程序启动时的主窗口,它包含了所有的视图。它定义了模块添加它们的视图到的区域和一些列的*的UI项,包括CFI Stock Trader title 和 Watch List tear-off 标题。
Stock Trader RI中的Shell.xaml,它的后置代码文件Shell.xaml.cs和它的视图墨香ShellViewModel.cs提供了shell的实现。Shell.xaml文件包含了shell的布局和部分UI元素,包括添加了模块的视图的区域的定义。
下面的XAML展示了Shell结构以及主要的XAML元素。注意这个RegionName附加属性被用来定义四个区域以及窗体背景图片提供了shell的背景。
<Window x:Class="StockTraderRI.Shell"> <!—shell background --> <Window.Background> <ImageBrush ImageSource="Resources/background.png" Stretch="UniformToFill"/> </Window.Background> <Grid> <!-- logo --> <Canvas x:Name="Logo"> <TextBlock Text="CFI" ... /> <TextBlock Text="STOCKTRADER" .../> </Canvas> <!-- main bar --> <ItemsControl x:Name="MainToolbar" cal:RegionManager.RegionName="{x:Static inf:RegionNames.MainToolBarRegion}"> </ItemsControl> <!-- content --> <Grid> <Controls:AnimatedTabControl x:Name="PositionBuySellTab" cal:RegionManager.RegionName="{x:Static inf:RegionNames.MainRegion}"/> </Grid> <!-- details --> <Grid> <ContentControl x:Name="ActionContent" cal:RegionManager.RegionName="{x:Static inf:RegionNames.ActionRegion}"> </ContentControl> </Grid> <!-- sidebar --> <Grid x:Name="SideGrid"> <Controls:ResearchControl cal:RegionManager.RegionName="{x:Static inf:RegionNames.ResearchRegion}"> </Controls:ResearchControl> </Grid> </Grid> </Window> |
Shell的后置代码实现非常简单。Shell被导出,目的是当启动引导程序创建它时,它的依赖将会被MEF解析。shell的唯一依赖---ShellViewModel--在构造中被注入,如下所示。
C# Shell.xaml.cs | |
---|---|
[Export] public partial class Shell : Window { public Shell() { InitializeComponent(); } [Import] ShellViewModel ViewModel { set { this.DataContext = value; } } } |
在后置代码文件中的少量的代码和视图模型说明了组合应用程序结构的能力和简单性。这种缺少代码也说明了在shell和组成它的视图之间的解耦。定义区域
定义区域
你通过一个命名的位置定义视图将会显示的布局,叫做区域。区域扮演为一个用于在运行时展示一个或多个视图的占位符。模块可以定位和添加内容到布局中的区域而不需要知道区域是如何以及在哪里展示的。这使得布局的变化将不会影响将内容添加到布局的模块。
区域通过为WPF或Silverlight中控件分配一个区域名称来,要么在前面展示的Shell.xaml文件中XAML中或者在代码中来定义。区域可以通过它们的名称来被访问。在运行时,视图被添加到命名的区域控件中,这些区域控件然后根据视图的实现的布局策略来显示这些视图。例如。一个Tab控件区域将会将它的子视图在一个分页排列中布局展示。区域支持添加或者移除视图。视图可以通过代码或者自动的被创建和展示在区域中。在Prism类库中,前面的实现方式是通过视图注入,后面的方式是通过视图发现。这两种技术确定视图如何单独映射到该应用程序的用户界面内,在指定的区域。
应用程序的Shell定义了*别的应用程序的布局;例如。通过指定主要内容和导航内容的位置,就像下面展示的。这些高级别视图中布局类似地被定义,从而允许整体的UI被递归组合。
某些时候区域用于定义多个逻辑相关的视图的位置。这种区域控件通常在派生自ItemsControl控件通过根据它的实现策略来展示视图。例如在Stack或者Tabed布局排列。
区域也可以用于定义一个单一视图的位置;例如,通过使用一个ContentControl。在这种场景中,区域控件只能在同一时刻展示一个视图,即使有多个视图映射到了这个区域位置。
Stock Trader RI Shell Regions
Stock Trader RI展示了单一视图和多个视图布局方式,你可以在程序的shell中看到这两种方式,它们定义了应用程序的高级别的视图的位置。下面的插图展示了Stock Trader RI中shell定义的区域。
Stock Trader RI中在购买或者销售股票时也展示了多视图布局。购买/出售区域是一个集合样式区域展示了集合中多个购买/出售视图(OrderCompositeView)的一部分,如下所示
shell的ActionRegion包含OrdersView,OrdersView包含Submit All and Cancel All 按钮和OrdersRegion,OrdersRegion附加到了显示多个OrderCompositeViews的ListBox控件。
IRegion
一个区域是一个实现了IRegion接口的类,区域是承载了通过一个控件显示的内容的容器。下面的代码展示了IRegion接口。
public interface IRegion : INavigateAsync, INotifyPropertyChanged { IViewsCollection Views { get; } IViewsCollection ActiveViews { get; } object Context { get; set; } string Name { get; set; } Comparison<object> SortComparison { get; set; } IRegionManager Add(object view); IRegionManager Add(object view, string viewName); IRegionManager Add(object view, string viewName, bool createRegionManagerScope); void Remove(object view); void Deactivate(object view); object GetView(string viewName); IRegionManager RegionManager { get; set; } IRegionBehaviorCollection Behaviors { get; } IRegionNavigationService NavigationService { get; set; } } |
在XAML中添加一个区域
RegionManager 应用了一个在XAML中简化的创建区域的附加属性。为了使用这个附加属性,你必须在XAML中引入Prism类库的命名空间憨厚使用RegionName附加属性。下面的实例展示了如何在一个AnimatedTabControl的窗口中使用这个附加属性。这个实例在XAML中消除了魔法字符串。
注意使用X:Static扩展标记来引用MainRegion字符内容。
XAML (WPF) | |
---|---|
<Controls:AnimatedTabControl x:Name="PositionBuySellTab" cal:RegionManager.RegionName="{x:Static inf:RegionNames.MainRegion}"/> |
Silverlight4不支持X:Static。因此,你需要为区域名称使用字符串值,或者,可选的,为每个区域名称定义一个应用程序级别的字符串资源。RegionName付附加属性可以绑定到这个字符串资源来解析区域名称。
XAML (Silverlight) | |
---|---|
<Controls:AnimatedTabControl Regions:RegionManager.RegionName="MainRegion" /> |
使用代码添加一个区域
RegionManager可以不使用XAML而直接注册区域。下面的代码示例展示例如如何从后置代码文件中将一个区域添加到一个控件中。首先是要获取一个区域管理的引用。然后,使用RegionManager静态方法SetRegionManager和SetRegionName,区域附加到界面的ActionContent控件然后区域被命名为AcontionRegion。
C# | |
---|---|
IRegionManager regionManager = ServiceLocator.Current.GetInstance<IRegionManager>(); RegionManager.SetRegionManager(this.ActionContent, regionManager); RegionManager.SetRegionName(this.ActionContent, "ActionRegion"); |
在区域加载时显示区域内的视图
在视图发现机制中,模块可以将视图(视图模型或者展现模型)注册到指定名称的位置。当这个位置在运行时被展现的时候,任何注册到这个区域的视图将会被自动地创建和展现。
模块在一个注册表中注册视图。父视图查询这个注册表来发现这些注册到指定位置的视图。在它们被发现之后,父视图将会使用这些视图来替换掉屏幕上的控件站位符。
在应用程序加载以后,组合视图被通知来处理已经被添加到注册表中的心视图的位置。
下面的插图展示了视图发现方式。
Prism类库定义了一个标准的注册表,RegionViewRegistry,来为这些命名的位置注册视图。
为了在一个区域中显示一个视图,使用区域管理来注册视图,就像下面的代码展示。你可以在区域中直接注册一个视图类型,在这种情况下,通过依赖注入容器这个视图将会被构建并且在承载控件的区域加载的时候将视图添加到这个区域中。
C# | |
---|---|
// View discovery this.regionManager.RegisterViewWithRegion("MainRegion", typeof(EmployeeView)); |
可选的,你可以提供一个返回显示视图的委托,例如所示的例子。区域管理器将会在区域创建的时候展示视图。
C# | |
---|---|
// View discovery this.regionManager.RegisterViewWithRegion("MainRegion", () => this.container.Resolve<EmployeeView>()); |
UI Composition QuickStart 在员工模块的ModuleInit.cs文件中有一个展示了如何使用RegisterViewWithRegion方法
的演示。
编程的方式显示区域内的视图
在视图注入的方式中,视图管理它们的模块通过编程的方式添加到一个命名的位置或从这个位置移除。为了使用这点,应用程序在UI中包含了一个命名的位置的注册表。一个模块可以使用这个注册表查找某个位置然后通过编程将视图注入到其中。为了保证注册表中的位置可以被简便的访问,每个命名的位置附着于用于注入的视图的通用接口。下面的插图展示了视图注入方式。
Prism类库为访问这些位置定义了一个标准的注册表,RegionManager,和一个标准的接口,IRegion。
为了使用视图注入来将一个视图添加到区域中,从区域管理中获得区域,然后调用Add方法,如下代码所示。在视图注入中,视图只有在模块加载时或者预定义动作完成时被添加到了区域中之后才能被展示。
C# | |
---|---|
// View injection IRegion region = regionManager.Regions["MainRegion"]; var ordersView = container.Resolve<OrdersView>(); region.Add(ordersView, "OrdersView"); region.Activate(ordersView); |
另外,Stock Trader RI, the UI Composition QuickStart中都有书图注入的演示。
导航
Prism 4.0类库包含了为WPF和Silverlight应用程序中实现导航功能提供了丰富和兼容的导航API。
区域导航是视图注入的一种形式。当一个导航请求被处理时,它将会尝试查找能够满足该请求的区域的视图。如果它找不到合适的视图,它将会调用应用程序容器来创建这个对象,然后将这个对象注入到区域中然后激活它。
下面来自Stock Trader RI 中的 ArticleViewModel 代码示例展示了如何初始化一个导航请求。
C# | |
---|---|
this.regionManager.RequestNavigate(RegionNames.SecondaryRegion, new Uri("/NewsReaderView", UriKind.Relative)); |
关于区域导航的更多信息,参考第8章“导航”。View-Switching Navigation QuickStart 和 State-Based Navigation QuickStart 也演示了程序导航的实现。
区域中视图排序
无论使用视图发现还是使用视图注入,应用程序都可能需要对在TabControl,ItemsControl,或任何其他的显示多个活动视图的控件中的视图进行排序。默认情况下,视图按它们被注册和添加到区域中的顺序进行显示。
当一个组合应用程序构建时,视图经常从不同的模块中进行注册。声明模块间的依赖关系可以缓解这个问题,但是当模块和视图都没有让你和实际的相互依存关系时,声明一个人工的模块间依赖耦合是不必要的。
为了允许视图可以参与它们自身的排序,Prism类库提供了ViewSortHintAttribute属性,这个属性包含了一个允许视图声明一个它应该如何在区域中排序的Hint的字符串属性。
当展现视图时,Region类使用一个使用了默认的按提示来排序视图的视图排序惯例。这是一个简单的区分大小写的排序顺序。用于hint属性的视图被排到那些没有hint属性的视图的前面。同样,这些没有hint属性的视图按它们被添加到区域的顺序进行显示。
如果你想要改变视图的排列顺序机制,Region类提供了一个你可以设置自己的Comparision<object>委托方法的SotrComparision属性。他对于表明区域反映在UI上Views和ActiveViews的排列顺序的属性是非常重要的。因为这些适配器例如ItemsControlRegionAdapter直接绑定到这些属性。一个自定义的区域适配器可以实现它自己的重载了区域如何排列视图的排序和筛选方式。
View Switching QuickStart演示了在导航区域的左手边的一个简单的数字模式来定义视图的顺序。下面的代码实例展示了ViewSortHint应用了每个导航视图。
C# | |
---|---|
[Export][ViewSortHint("01")]publicpartialclassEmailNavigationItemView [Export][ViewSortHint("02")]publicpartialclassCalendarNavigationItemView [Export][ViewSortHint("03")]publicpartialclassContactsDetailNavigationItemView [Export][ViewSortHint("04")]publicpartialclassContactsAvatarNavigationItemView |
多个区域之间共享数据
Prism类库提供了视图之间通信的多种实现方式。取决于你的应用场景。区域管理提供了RegionContext属性作为这些方式之一。
RegionContext在你想要在同一个承载的区域内的父视图和子视图之间共享上下文的时非常有用。RegionContext是一个附加属性。在区域控件的上下文属性设置值,那样可以使得所有在区域控件中展示的子视图之间获得数据可用。区域上下文可以是任意的非常简单的或者负责的对象并且可以是一个数据绑定的值。RegionContext可以用于无论是视图发现中还是视图注入中。
注意:
在Silverlight和WPF中,DataContext属性被用来设置视图的本地数据上下文。它允许视图使用数据绑定来同视图模型,本地表现,或者模型之间进行通信。RegionContext用来在多个视图之间共享上下文,并且不支持定位到单独的视图。它提供了一种在多个视图之间共享上下文的简单机制。
下面的代码展示了如何在XAML中使用RegionContext附加属性。
XAML | |
---|---|
<TabControl AutomationProperties.AutomationId="DetailsTabControl" cal:RegionManager.RegionName="{x:Static local:RegionNames.TabRegion}" cal:RegionManager.RegionContext="{Binding Path=SelectedEmployee.EmployeeId}" ...> |
你也可以在代码中设置RegionContext,如下所示。
C# | |
---|---|
RegionManager.Regions["Region1"].Context = employeeId; |
为了在视图中取回RegionContext,使用RegionContext类的GetObservableContext静态方法。它将视图作为参数传递并且然后访问它的Value属性,如下所示。
C# | |
---|---|
private void GetRegionContext() { this.Model.EmployeeId = (int)RegionContext.GetObservableContext(this).Value; } |
RegionContext的值可以从视图中通过简单的分配一个新值的Value属性改变。视图可以选择通过订阅GetObservableContext方法返回的ObservableObject对象的PropertyChanged事件来在RegiongContext属性发生改变时背通知。这使得多个视图在它们的RegionContext发现变化时保持同步。下面的代码演示了订阅PropertyChanged事件。
C# | |
---|---|
ObservableObject<object> viewRegionContext = RegionContext.GetObservableContext(this); viewRegionContext.PropertyChanged += this.ViewRegionContext_OnPropertyChangedEvent; private void ViewRegionContext_OnPropertyChangedEvent(object sender, PropertyChangedEventArgs args) { if (args.PropertyName == "Value") { var context = (ObservableObject<object>) sender; int newValue = (int)context.Value; } } |
注意:
RegionContext被设置为区域的承载的内容对象的附加属性。这意味着内容对象必须派生自DependencyObject。视图是一个可是控件,它最终派生自DependencyObject。如果你选择使用WPF或者Silverlight数据模板来定义你的视图,内容对象将会表现为视图模型或者展现逻辑。如果你的视图模型或表现模型需要取回RegionContext,它需要派生自DependencyObject基类。
创建区域的多个实例
范围内的区域是仅适用于视图注入。如果你需要一个拥有它自己的区域实例的视图的时候你应该使用它们。定义区域的视图的附加属性自动继承它们父区域的RegionMangaer。通常,这是一个在Shell窗口中注册的全局的RegionManager。如果应用程序创建了视图的多个实例,每个实例将会尝试注册它的区域中的RegionManager。RegionMangaer允许名称不重复的区域;因此,第二次注入将会产生一个错误。
作为代替,使用区域范围可以是每个视图拥有它自己的RegionManager并且它的区域将会注册到RegionManager中而不是注册到父RegionMangaer,如下图所示。
为了创建视图的一个本地RegionManager。指定的新的RegionManager应该在你添加视图到区域中时创建。如下所示。
C# | |
---|---|
IRegion detailsRegion = this.regionManager.Regions["DetailsRegion"]; View view = new View(); bool createRegionManagerScope = true; IRegionManager detailsRegionManager = detailsRegion.Add(view, null, createRegionManagerScope); |
Add方法将返回新RegionManager该视图可以保留进一步访问本地范围。
创建Views
应用程序的视觉展现可以采用多种形式,包括用户控件,自定义控件,和数据模板,仅举几例。在Stock Trader RI中,用户控件常用于在主窗口中展现不同的部分,但是这不是一个标准。在你的应用程序中。你应该你应该使用你最熟悉和最适合你工作的方式。无论您的应用程序为主视觉表现,你将不可避免地使用用户控件,自定义控件和数据模板的组合在整体设计中。下面的插图展示了Stock Trader RI中中使用的几项。这个插图也作为后面几节的服务,在后面将会单独描述每项。
用户控件
Blend和Visual Studio 2010都为创建用户控件提供了丰富的支持。使用这些工具创建的用户控件因此被推荐使用Prism类库创建UI内容。如本主题前面提到的,Stock Trader RI广泛的使用它们创建了将会插入到区域中的内容。WatchListView.xaml用户控件就是内部包含了WatchModule的一个简单的UI展现的很好的例子。这种控件是简单的使用这种模型的简单控件。
自定义控件
在一些情况下,一个用户控件太受限了,在这些情况下,自定义布局或者可扩展性比这种易创建性更重要了。这就是自定义控件非常有用的地方了。在the Stock Trader RI中,饼状图控件就是一个很好的例子。这个控件是从所述位置导出的数据组成的。并且显示为一个图标的整体组合。创建这种类型的控件比创建用户控件更具一些小的挑战性。相比于用户控件,它限制了Blend和Visual Studio2010对可视化设计的支持。
数据模板
数据模板是这种数据驱动应用程序的一个重要的部分。在the Stock Trader RI中,使用基于集合的控件对数据模板的使用非常流行。在许多情况下,你可以使用一个数据模板来创建一个完整的可视化展现而不需要创建任何类型的控件。ResearchRegion使用了一个数据模板来显示文章并与一个物品风格相结合,提供了一种指示其项被选定。
Blend对于数据模板有丰富的可视化设计支持。Visual Sutdio 2010只提供了使用XAML编辑器来编辑数据模板的支持。
资源
资源例如样式,资源字典和控件模板可以分散在应用程序中。在一个组合的应用程序中尤其是这样。当你考虑在哪里放置资源,特别要注意UI元素和它们所需要的资源之间的依赖关系。在Stock Trader RI 解决方案中,如下面的插图所示,包含了表明各种资源可以存在的地方的标签。
应用程序资源
通常,应用程序资源是可以被整个程序访问的资源。这些资源往往集中在应用程序根上,但是它们也可以提供一个为模块和控件的基本类型的默认样式。这样的一个例子是应用到根应用文本框中键入文本框样式。这种风格将提供给所有文本框的应用程序,除非被模块或控件水平的样式覆盖。
模块资源
模块资源扮演着与应用程序根资源相同的角色,它们而已被模块中所有的项目使用。使用这种级别的资源可以为整个模块提供统一的界面并且也可以允许重用在跨越一个或多个可视化组件的指定实例。模块级别的资源的使用应该被包含在各自的模块中。创建模块之间的依赖关系可能会导致难以在用户界面元素显示在错误的定位问题。
控件资源
控件资源通常包含在空间类库中,并且可以用于整个控件库中所有的控件。这些资源趋于用于最有限的区域内,因为控件库通常包含特定的控件并且不包含用户控件(在基于Prism类库的应用程序中,用户控件通常放在它们被使用的模块中)。
UI设计指导
本主题的目的是为XAML设计人员和使用Prism类库开发WPF或Silverlight应用程序的开发人员体统一些高级别的指导。本主题描述了UI布局,可视化展现,数据绑定,资源和展现模型。在阅读了本主题之后,你将会对于如何基于Prism类库设计应用程序的UI有更深层次的理解。以及理解一些可以帮助你创建易维护UI的组合应用程序的技术。
用户界面设计指导
基于WPF和Silverlight标准规则的使用Prism类库创建的组合应用程序的布局---使用了包含相关项目的面板概念的布局。然而,在组合应用程序中,面板内的各种内容是动态的并且在设计时是并不被知道的,这就要求设计人员和开发人员创建可以包含布局内容的页面框架然后设计适应这些布局的元素。作为一个设计人员或者开发人员,这意味这你必须考虑Prism类库中的两个主要的布局概念:容器组合和区域。
容器组合
容器组合在实际上仅仅是WPF和Siliverlight本质上提供的容量模型的一种扩展。container次可以代表任何元素,包含窗口,页面,用户控件,面板,自定义控件,控件模板,或者数据模板,它可以包含其他元素。
你如何想象你的UI可能因实现而不同,但是你将会发现反复出现的主题。你将会创建一个窗口,页面或者包含了固定内容和动态内容的用户控件。固定的内容将会包括包含的UI内容的整个架构,动态内容将会是区域内的一个位置。
例如,WPF Stock Trader RI有一个名为Shell.xaml的窗口包含了应用程序的整体结构。下面的插图展示了在Blend中加载的Shell。注意只有一些固定的UI部分是可见的。Shell的其他部分是在应用程序加载模块时动态的插入到不同的区域内的。
在这样类型的应用程序中设计时的经验会有一些限制,但是在实际上你所知道的将会在运行时被放到不同的区域内的内容正是你需要设计的。来看这样的一个例子,比较以下下一张插图的主页面的设计和后面的一张插图中运行时的视图。在设计人员的视图中,与运行时的包含了一个包含一个位置数据的Tab控件,一个趋势线,饼图和与选中的股票相关的新闻区域的界面相反,这个页面绝大部分是空的。在设计人员的视图与运行时的视图的不同展示了设计人员和开发人员在它们使用Prism类库创建应用程序时面临的调整。
设计时看不到这些项,因此确定它们多大以及它们怎样适应整个应用程序的界面就有一些困难。在你创建你的容器的布局时考虑以下几点:
- 是否有任何尺寸的限制,是否可能限制内容的大小?如果有的话,考虑使容器支持滚动。
- 考虑使用Expand而和ScrollViewer来整合需要将大量的动态的适应到有限的空间中的情况。
- 密切关注在屏幕内容增长时内容将会如何放大来确保你的应用程序界面可以适应任何分辨率。
在设计时整合应用程序视图
前面两个插图中,在工作的挑战之一就是在运行时组合高级别的视图。在组合应用程序中的每一个UI元素都必须被单独的设计。这使得很难想像在运行时的组合页码将会是什么样子。为了能够想象到组合视图的在组合状态下的样子。你可以创建一个包含了你想要测试的所有UI元素的视图的测试工程。
另外,考虑使用Blend工具中的设计时的样例数据功能和Visual Studio 2010来使用数据填充UI元素。设计时数据对于使用数据模板,集合控件,图,表等工作非常有帮助。更多信息,请看“设计时样例数据指导”一节。
布局
当你设计一个组合应用程序的布局时考虑以下几项:
- Shell定义应用程序的主要布局。每个布局的面是一个区域并且应该保持为一个空的容器。不要在设计时替换区域内的内容因为内容将会在运行时加载。
- Shell应该包含背景,标题和页脚。将Shell想象成ASP.NET中的母版页。
- 如果shell区域有固定尺寸,视图应该使用动态大小。
- 视图可能会要求固定高度和动态宽度。这一点的一个例子就是Stock Trader RI中侧边栏的PositionPieChart视图。
- 其他视图可能要求动态高度和宽度。例如。Stock Trader RI中一侧边栏的NewsReader。它自身的高度取决于标题的长度,并且它的宽度应该一直适应区域的大小(侧边栏的宽度)。相同的应用项就是PositionSummaryView视图中的表格的宽度应该适应屏幕的大小以及高度应该适应表格中行数量。
- 视图通常应该有透明的背景,使得Shell背景来作为应用程序的可视化的背景。
- 经常为指定颜色,画刷,字体和字体大小使用命名的资源,而不是直接在XAML中指定属性值。这样可以使应用程序更便于维护。这也使得应用程序可在运行时反映资源字典的变化。
动画
当你在在Shell或者视图中使用动画的时候考虑以下几点:
- 你可以在Shell的布局上使用动画,但是你必须对它的内容和视图单独的应用动画。
- 每个视图单独的设计和设置动画
- 使用平滑柔和的动画来提供一个UI元素进入视图或从视图中移除的可视化线索,这给应用程序一种光滑的外表和感觉。
Blend提供了一系列丰富的行为,缓动函数,和一个优秀的动画编辑体验以及基于可视化状态变化或事件的转换UI元素。更多信息,请看MSDN上的"VisualStateManager Class"
运行时优化
为性能优化考虑以下几项:
- 将任何的通用资源放置在App.xaml文件或使用合并资源字典来避免样式的重复
- 在Silverlight中,避免为指定的应该与应用程序其他部分(例如,应用程序主题)区别样式的静态的文本使用非系统的字体。在这种情况下,确定是否将文本转换为路径或者使用嵌入的字体会更好。嵌入一种字体可能影响到下载.xap文件的大小因为有些字体文件相当大。为了最小化下载.xap文件的大小,Blend允许你之下在字体的符号特征来替代整个字体。更多信息请看"Using Custom Fonts in Silverlight."
设计时优化
接下来的几节将会描述设计时的场景并且为最大限度地利用了设计时体验提供解决方案。
拥有许多XAML资源的大的解决方案
在应用程序中有许多XAML资源是解决方案的一部分,可视化设计器加载时间将会收到影响,某些情况下将会非常严重。因为可视化设计器必须转化合并XAML资源,这会存在性能的下降。解决这个问题的方案就是将所有的XAML资源移到另一个解决方案中,编译那个解决方案,然后从那个大的方案的DLL中引用新的XAML资源。因为XAML资源在引用的二进制程序集中,可视化设计器将会不转换XAML资源,这样就提高了设计时的性能。当移动XAML资源到一个扩展的程序集时,你可能需要考虑为你的资源暴露CompoentResourceKeys。更多信息,请看MSDN上的“"ComponentResourceKey Markup Extension”。
XAML 资产
XAML对于创建例如图片,图形和3D场景等资产来说是一种有力和富有表达性的语言。一些开发者和设计者偏向创建XAML资产来替代使用.ico,.jpg,或者.png图片文件。一个原因是选择XAML的方式利用了依赖于XAML渲染解决方案的优势。另外一个原因是它们可以使用Expression工具包中的工具来创需要的资产来设计它们的应用程序。
如果解决方案中有许多这样的资产,设计器在在设计时的加载效率就会受到影响,将这些资产转移到其他的单独的DLL中可以解决这个性能问题。将资产移动到单独的DLL中还可以在多个解决方案中达到重用这些资产的目的。
可视化设计与引用程序集
一个移动XAML资源和资产到一个引用的二进制程序集中的不幸的负面影响就是Blend和Visual Studio 2010 的编辑器可能不会列出位于二进制引用的程序集资源。这意味着你将不能够从工具提供的资源选择器中挑选一个命名的资源了。相应的,你将需要资源名称的类型。
Silverlight 设计时App.xaml 资源
Silverlight的组合应用,可以构成多种方式,以允许组件的运行时的延迟加载,或减少初始的.xap下载大小。你可以使用一个策略是创建主Silverlight应用程序,然后创建每个附属程序集的模块。当你添加附属程序集时,你可以选择其中一个应用程序项目模板或者Silverlight类库模板。
为附属程序集选择一个Silverlight应用程序项目模板可以提供一个部署的好处:当程序集创建时将会被打包成一个.xap文件。然而,这样的做法对于可视化设计器的负面影响就是当不知一个Silverlight应用程序表现在单独的解决方案中时。主Silverlight应用程序需要将它的应用程序资源在App.xaml中合并。这样的问题就是可视化设计器会消耗暴露在App.xaml中的资源。因为在解决方案中有不止一个Silverlight应用程序。可视化设计器不会尝试从另外的Silverlight应用程序中使用资源;想法,它只会使用正在活跃着的Silverlight应用程序的那些资源。
Blend 4提供了这个问题的一个解决方法。当Blend察觉到这种情况是,它会弹出一个让你选择一个用于解决方案中所有工程之间的设计时资源字典的对话框。
Visual Studio 2010 没有这样的功能;因此,附属的Silverlight应用程序集将不会有非常丰富的设计时体验触发你在本地程序集中合并了应用程序级的资源。如果你为了一个更好的可视化设计体验而选用合并应用程序级资源。你必须记住在你部署你的应用程序之前将它们移除。
创建设计友好的Views指导
以下是设计一个友好的应用程序拥有的一些特征:
- 同使用Visual Studio 和Blend设计器它提供了丰富的编辑体验
- 它是工具可用的(Tooling-enabled).例如,它允许你使用绑定构建工具
- 它在需要时提供了一个设计时的样例数据
- 它允许在设计时执行代码而不引起未处理的异常
以下动作在编辑期间将会执行多次。设计者不友好的用户代码将会引起一个或多个这些动作失败,这将会削减开发者好设计者的生产率和创造率。
- 设计界面动作:
- 构建对象
- 加载对象
- 设置属性值
- 执行设计界面的手势
- 使用一个控件作为跟元素
- 在一个控件中承载另一个控件
- 重复性的打开关闭XAML文件
- 重新构建项目
- 重新加载设计器
- 绑定构建动作:
- 发现DataContext
- 列出可用的数据源
- 列出数据源类型属性
- 设计时样例数据动作:
- 在设计界面使用控件正确的展示样例数据
设计时编码
为了给你一个丰富的设计时的体验,Visual Studio和Blend设计器在设计时实例化了对象并且运行代码。然而,由于在类型被实例化之前尝试访问其引用而引起的空引用异常而导致加载失败的高百分率以及不必要的设计时异常。
下表列出了差的设计时体验的主要原因。通过避免以下问题及使用技术减轻这些问题,设计时体验和生产率将会极大的增强,并且开发人员与设计人员之间的工作流程也将会更加顺畅。
避免这些用户代码 |
Visual Studio 2010 |
Blend 4 |
在设计时旋转(Spinning)多个线程,例如,在设计时的Loaded事件或者构造方法中中实例化和开始一个Timer。 |
X |
X |
在设计时使用引起栈溢出的控件。 使用尝试递归加载自身的控件 |
X |
X |
在转换器或者数据模板选择器中抛出空引用异常 |
X |
X |
在构造方法中抛出空引用异常或其他异常。 它们通常有以下引起:
|
X |
X |
在控件或者用户控件的Loaded事件中抛出空引用异常或者其他异常。这种常在你假设控件的状态在运行时可能是true但是在设计时却不是true时发生 |
X |
X |
在设计时尝试获取Application或Application.Current对象 |
X |
X |
在WPFUserControls中使用StaticResource. |
√ |
X |
创建非常大的项目 |
√ |
X |
设计时 用户代码问题缓解
少量的预防性代码实践将会消除前面表格中描述的大部分的问题。然而,在你可以缓解设计时用户代码问题之前,你必须理解你的应用程序控件和代码将会被设计器在的一个未初始化的应用程序域中隔离的执行。在这里,未初始化的意思是指通常的启动,引导程序,或者初始化代码还未运行时。
当你的应用程序在运行时执行的时候,App.xaml.cs文件中的启动代码将会运行。如果你的程序中有依赖哪里的代码,这些代码将会不在设计时执行。如果你还没有预料到你这里的代码,将会发生不必要的异常。(这就是为什么在设计时尝试获取Application或Application.Current对象将会引发异常的原因了),为了缓解这些问题:
- 用于不要假设引用的对象将会在设计时代码中被实例化。在设计时可以被执行的代码,一直执行在获取任何引用对象前检查是否为空。
- 如果你的代码访问了Application或者Application.Current对象,在获取这个对象之前进行是否为空的检查。
- 如果你的构造方法或者Loaded事件处理程序中需要运行复杂的代码或者需要访问数据库或调用网络结果的代码,考虑以下几种解决办法:
- 将代码包装到一个通过调用以下之一的System.ComponentModel DesignerProperies方法来确定代码是否在设计是运行的检查中
- WPF:DesignerProperties.GetIsInDesignMode
- Silverlight:DesignerProperties.IsInDesignTool
- 不是在构造器或者Loaded事件处理程序中直接运行代码,而是将这些调用抽象到接口后面的一个类中,然后使用许多技术之一来解析不同的依赖信息在设计时,运行时和测试时。例如,不是直接调用数据服务来检索数据,而是将数据服务调用包装到一个通过接口暴露方法的类中,然后,在设计是,使用模拟或这设计时对象来解析这个接口。
- 将代码包装到一个通过调用以下之一的System.ComponentModel DesignerProperies方法来确定代码是否在设计是运行的检查中
设计时理解用户控件代码何时
Blend和Visual Studio 都使用根对象的模型在设计器面板中展示。这对于提供被要求的设计体验就非常必要了。因为跟对象是被模拟的,它的构造器和Loaded事件代码在设计时不会被执行。然而,场景中的剩余的控件被正常的构造,并且它们的Loaded事件会像运行时一样引发执行。
在下面的插图中,根Windows构造器和Loaded事件代码不会被执行。子用户控件的构造器和Loaded事件将会被执行。
这些概念非常重要,尤其是如果你正在构建组合应用程序或者构建那些在运行时动态构建的应用程序。
大多数的视图是单独编码和设计的。因为它们被单独的设计,通常在设计器中它们就是根对象。因此,它们的构造器和Loaded事件代码永远不会执行。
然而,如果你将相同的用户控件放到设计界面的另外的一个控件中,这个被隔离的用户控件的代码在设计时就会立刻执行。如果你没有遵循上述剪切设计时代码问题的实践,承载控件将会变得不友好并且引起设计器的加载问题。
设计时属性
内建的“d:”设计时属性提供了一个成功达到设计时工具体验的平滑的道路。
我们需要解决的问题是如何提供一个Shape来在设计时绑定这个构造工具。在这种情况下,Shape就是一个实例化的可以在绑定的构建器上反映的类型,然后在构建绑定时列出这些属性。
Shape也提供了设计时样例数据,样例数据在“设计时样例数据指导”一节讲到。
“d:”属性和扩展标记是设计命名空间的一个别名,这个设计了一系列的属性。下面是在MSDN上一些涉及到“d:”属性和扩展标记的连接:
“d:”属性和扩展标记不能在代码中被创建和扩展;它们只能在XAML中使用。“d:”属性和扩展标记不被编译到你的应用程序中;它们只是在Visual Studio 和Blend工具中使用。
d:DataContext属性
d:DataContext,为控件和子控件指定设计时数据上下文。当指定d:DataContext时,你也应该一同提供与运行是DataContext一样的Shape的设计时DataContexgt。
如果为一个控件同时指定了DataContext和d:DataContext,开发工具将会使用d:DataContext。
d:DesignInstance 标记扩展
如果扩展标记对于你来说还是新知识,请阅读MSDN上的"Markup Extensions and WPF XAML"。
d:DesignInstace返回一个你将会在设计器中想要将其指定为控件绑定的数据源的类型(“Shape”)实例。这个类型不需要去创建来用于确定Shape。下表解释了 d:DesignInstace扩展标记的属性。
扩展标记属性 |
定义 |
Type |
将被创建的类型的名称。类型是构造方法中默认的参数。 |
IsDesignTimeCreatable |
指定的类型是否可用被创建?如果不可以, 一个仿类型而不是真实的类型将会被创建。默认值是false |
CreateList |
如果是真,返回一个制定类型的一般集合。默认值是false |
d:DataContext常用场景
下面的三个示例代码演示了一个连接最多的视图和视图模型的可重复的模式,以及可用的设计工具。
PersonViewModel 是PersonView运行是的一个依赖项。然而,在实例的视图模型是非常的简单的。真实世界里的视图模型通常会拥有一个或多个额外需要解析的依赖,并且这些依赖项通常被注入到它们的构造器中。
当PersonView被构建时,它依赖的PersonViewModel将会被构建并且它的依赖关系将会通过一个依赖注入容器被解析。
注意:
如果视图模型没有需要解析的额外的依赖项,视图模型可以在视图的XAML中北实例化,并且不会要求它的DataContext和d:DataContext。
C# PersonViewModel.cs | |
---|---|
[Export] public class PersonViewModel { public String FirstName { get; set; } public String LasName { get; set; } } |
C# PersonView.xaml.cs | |
---|---|
[Export] public partial class PersonView : UserControl { public PersonView() { InitializeComponent(); } [Import] public PersonViewModel ViewModel { get { return this.DataContext as PersonViewModel; } set { this.DataContext = value; } } } |
这是连接视图和视图模型的很好的模式;然而,它使得在设计时视图并不知晓它的DataContext的shape(视图模型)。
在下面的XAML实例中,你可以看到d:DesignInstance扩展表演用于Grid来返回一个PersonViewModel的仿的实例。这个实例通过d:DataContext被暴露。作为结果,Grid的子空间将会继承d:DataContext,使得设计工具可以发现并使用它的类型和属性,结果就是使得开发者和设计者的更生产率的设计体验。
XAML PersonView.xaml | |
---|---|
<UserControl xmlns:local="clr-namespace:WpfApplication1" x:Class="WpfApplication1.PersonView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Border BorderBrush="LightGray" BorderThickness="1" CornerRadius="10" Padding="10"> <Grid d:DataContext="{d:DesignInstance local:PersonViewModel}"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="100" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" Grid.Row="0" Content="First Name" /> <Label Grid.Column="0" Grid.Row="1" Content="Las Name" /> <TextBox Grid.Column="1" Grid.Row="0" Width="150" MaxLength="50" HorizontalAlignment="Left" VerticalAlignment="Top" Text="{Binding Path=FirstName, Mode=TwoWay}" /> <TextBox Grid.Column="1" Grid.Row="1" Width="150" MaxLength="50" HorizontalAlignment="Left" VerticalAlignment="Top" Text="{Binding Path=LasName, Mode=TwoWay}" /> </Grid> </Border> </UserControl> |
注意:
附加属性和视图模型定位器解决方案
在开发者社区里还有几种可选的用于关联视图和视图模型的技术。其中一种解决方案在运行时起到很强的作用但是在设计时通常不起作用。这种解决方法之一就是使用附加属性和视图模型定位器来关联到视图的DataContext。视图模型定位器被要求使用是因为那样视图模型可以被构建以及它的依赖项可以被解析。
这种解决方案的问题是你必须也要包含d:DataContext-d:DesignInstance组合因为可视化设计器工具不能反映d:DesignInstance的附加属性的结果。无论你在应用程序中实现的哪种技术来解决设计时shape,最重要的目标就是在应用程序中保持一直。一致性可以使得应用程序维护起来更加见到那并且将产生成功的工作流程。
设计时样例数据指导
WPF和Silverlight 设计团队发布了深入的,基于场景针对训练的文章讨论了在WPF和Silverlight项目中使用样例数据。这篇文章
"Sample Data in the WPF and Silverlight Designer,"可以在MSDN上获取。
使用设计时样例数据
如果你使用一个可视化设计工具,比如Blend或者Visual Studio 2010,设计时样例数据变得非常重要。视图可以被数据和图片填充,这使得设计任务变得更容易更快度的完成。这样的结果就是生成率和创造力的提高。
包含数据模板的空集合控件只有在被填充了数据之后才会可见。这使得编辑空集合控件的任务话费更多的时间因为你需要运行应用程序来查看最近一次修改后在运行时的样子。
样例数据源
你可以使用以下任何一种来源的样例数据:
- Expression Blend XML样例数据
- Expression Blend 4和Visual Studio 2010 XAML 样例数据
- XAML 资源
- 代码
这些来源的每一种数据将会在下面讲述。
Expression Blend XML 样例数据
Expression Blend中为您提供了快速创建一个XML模式和填充相应的XML文件的能力。这种实现不依赖任何项目类。
样例数据类型的目的是让设计人员能够快速的开始它们的项目,不需要等待一个开发人员或者在应用类可以被使用之前。
虽然大多数的样例数据都支持Blend和Visual Studio 设计器,但是XML样例数据只是Blend的功能且并不支持在Visual Studio 设计器中呈现。
注意:
在项目构建时XML样例数据不会被编译或者添加到程序中;然而,XML S车马会被编译到构建的程序集中。
Expression Blend 4 and Visual Studio 2010 XAML 样例数据
在Blend 4和Visual Studio 2010的开始,d:DesignData扩展标记被添加尽量,这使得设计时可以加载XAML样例数据。
样例数据XAML文件包含了一个或多个类型及关联到其属性的值的实例的XAML。
d:DesignData有一个使用一个统一资源定位符(URI)来获的位于项目中XAML文件中的样例数据的Source属性。d:DesignData扩展标记加载 XAML文件,转换然后返回一个对象图表。这个对象图标可以通过d:DataContext属性,CollectionViewSource d:DesignSource 属性, 或 DomainDataSource d:DesignData 属性被使用。
d:DesignData扩展标记客服的挑战之一就是它得意创建非可创建的样例数据。例如,WCF Rich Internet Application (RIA)服务实体---派生的对象不能在代码中创建。另外,开发人员可能会有他们自已的不能创建的类型,但是仍然希望拥有这些类型的样例数据。
你可以在Solution Explorer 中通过设置样例数据文件Build Action属性来改变d:DesignData来如何处理你的样例数据文件,如下:
- Build Action = DesignData – 仿制类型将会被创建
- Build Action = DesignDataWithDesignTimeCreatableTypes – 真实类型将会被创建
注意:
在下面的插图中,Custom Toll属性是空的。这就要求样例数据能够恰当的工作。默认情况下,Blend 常常会设置这个属性为空值。
当你使用Visual Studio 2010来添加一个样例数据文件时,你通常会添加一个资源字典项并且从哪里编辑它。在那种情况下,你必须设置Build Action并且清空Custom Tool属性。
Blend提供了快速创建和绑定XAML样例数据的工具。XAML样例数据可以在Visual Studio 2010 设计器中被使用和查看,如下图所示。
在生产了样例数据之后,数据将会显示在数据面板中,如下图所示。
然后你可以将它拖到视图中的根元素上,例如UserControl,并且将它设置到d:DataContext属性。你也可以将样例数据拖拽到items Controls上,并且Blend将会将数据与控件进行关联。
注意:
XAML样例数据不会被编译或者包含到构建的程序集中。
XAML资源
你可以在XAML中创建一个包含了期望类型的实例的资源,然后将那个资源绑定到DataContext属性或者一个集合控件上。
这种技术可以用于快速创建用于如果不使用样例数据将会花费很长时间来编辑数据模板的舍弃式样例数据。
代码
如果你喜欢在代码中创建样例数据,你可以写一个暴露属性或方法来为它们的使用者返回数据的类。例如,你可以写一个在类中默认的空构造方法中填充了多个Customer类的实例的Customers类。每一个Customer实例将会有合适的属性值来设置。
你可以使用样例数据类的一种方法是使用前面讲到的d:DataContext,d:DesignInstace组合,确保将
d:DesignInstanceIsDesignTimeCreatable属性设置为true。
IsDesignTimeCreatable必须设置为true的原因是你想要使用者构造方法来执行那样类中的代码将会运行。如果使用者被作为一个模仿类型来对待,使用者的代码将永远不会运行并且只有“shape”将会被工具发现。
下面的XAML实例实例化了Customers类,并且将其设置到d:DataContext属性。Grid的子控件可以使用Customers类暴露的数据。
XAML | |
---|---|
<Grid d:DataContext="{d:DesignInstance local:Customers, IsDesignTimeCreatable=True}"> |
UI布局关键决定
当你开始一个组合应用程序项目时,需要做一些将来会很难改变的UI设计决策。一般来说,这些决策时广泛应用的并且他们的一致性将会帮助开发者与设计者的生产力。
以下是一些重要的UI布局决策:
- 决定应用程序流并相应地确定区域
- 决定每个区域将会使用的加载的视图类型
- 决定你是否想要使用区域导航API
- 决定你将会使用那种设计模式(MVVM,展现模式等等)
- 决定样例数据策略
更多信息
关于扩展Prism类库的更多信息,请参考"Extending Prism."
关于命令的更多信息,请参考第5章, "Implementing the MVVM Pattern."的"Commands"
关于绑定的更多信息,请参考 第5章,"Implementing the MVVM Pattern.""Data Binding"
关于区域导航的更多信息,请参考第8章, "Navigation."
关于本主题中讨论的指导内容,请参考以下:
- MSDN上的"Dependency Properties Overview":
- http://msdn.microsoft.com/en-us/library/ms752914.aspx
- 数据绑定; 请看:
- MSDN上的"Data Binding Overview":
http://msdn.microsoft.com/en-us/library/ms742521.aspx - MSDN杂志中的"Data Binding in WPF":
http://msdn.microsoft.com/en-us/magazine/cc163299.aspx
- MSDN上的"Data Binding Overview":
- MSDN上的"Data Templating Overview" :
http://msdn.microsoft.com/en-us/library/ms742521.aspx - MSDN上的"Resources Overview":
http://msdn.microsoft.com/en-us/library/ms750613.aspx - MSDN上的"UserControl Class":
http://msdn.microsoft.com/en-us/library/system.windows.forms.usercontrol.aspx - MSDN上的"VisualStateManager Class":
http://msdn.microsoft.com/en-us/library/cc626338(v=VS.95).aspx - MSDN杂志中的"Customizing Controls For Windows Presentation Foundation":
http://msdn.microsoft.com/en-us/magazine/cc163421.aspx - MSDN 主题"ComponentResourceKey Markup Extension":
http://msdn.microsoft.com/en-us/library/ms753186.aspx - MSDN上的"Design-Time Attributes in the WPF Designer":
http://msdn.microsoft.com/en-us/library/ee839627.aspx - MSDN上的"Design-Time Attributes in the Silverlight Designer":
http://msdn.microsoft.com/en-us/library/ff602277(VS.95).aspx - "Using Custom Fonts in Silverlight":
http://silverlight.net/learn/learnvideo.aspx?video=69800 - 学习 Visual Studio WPF 和 Silverlight 设计器
它包含关于布局,资源,数据绑定,样例数据,数据绑定调试,对象数据源,和主细表的辅导和文章.
- http://blogs.msdn.com/b/wpfsldesigner/archive/2010/01/15/learn.aspx