Silverlight
复合 Web 应用程序与 Prism
Shawn Wildermuth
鏈 枃璁 ㄨ :
|
本文使用以下技术: Silverlight 2,Prism |
内容
为什么 Prism?
引入依赖关系注入
启动行为
模块化
复合 UI
事件聚合
委托命令
上的换行
您的第一个经验 Silverlight 时可能出现的小: 视频播放机、 简单图表应用程序或甚至菜单。这些应用程序的类型是简单和设计,简单,对它们进行分段严格具有不同职责的层中多余。
问题出现,但是,当您试图将紧密耦合的样式应用于大型应用程序。移动部件的数随着应用程序开发的简单样式位于分开。在补救方法的一部分分层 (请参阅我的文章"模型-视图-ViewModel Silverlight 2 应用程序中"),但是一个紧密耦合体系结构是只是一个需要大型 Silverlight 项目中解决的问题的一个数。
在这篇文章,我将介绍如何构建应用程序使用复合应用程序库从 Prism 项目的组合技术。我开发该示例是一个简单的编辑器的数据库中的数据。
要求更改,以及项目逐渐成熟最好如果您可以更改应用程序的部分而无需在整个系统级联这些更改。modularizing 应用程序允许您分别生成应用程序组件 (和松散耦合),并将应用程序的整个部分而不影响代码的其余部分。
此外,您可能不希望加载一次应用程序的所有片段。假设一个客户管理应用程序的用户登录和可以然后管理他们的目标客户管道,以及检查任何其潜在购买者的电子邮件。如果用户检查电子邮件一天多次,但管理管道只有每一天或两为什么加载代码以管理管道,直到需要时它?如果应用程序支持按需加载的应用程序可以通过 modularizing 应用程序来解决的情况的部分,这会很好。
在 Microsoft,模式与实践小组创建项目称为 Prism (或 CompositeWPF) 的用来解决问题,这些对于 Windows 演示基础 (WPF) 的应用程序,Prism 已更新,以及支持 Silverlight。框架和指南构建应用程序的混合 Prism 包。调用组件应用程序库 (CAL) 的该框架使下列:
- 应用程序的模块化: 生成应用程序分区的组件。
- UI 复合: 允许松散地耦合到窗体的应用程序的其余部分的离散不知情的用户界面的组件。
- 服务的位置: 分隔水平的服务 (渚嬪的方式 日志记录和身份验证) 与垂直服务 (业务逻辑) 将升级清理应用程序的分层。
CAL 编写这些相同的设计原则,记住,并且应用程序开发人员 buffet 样式框架 — — 您的需要和将其余部分。图 1 显示了 CAL 相对于自己的应用程序的基本版式。
图 1 复合应用程序库
CAL 支持这些服务以帮助您撰写您的应用程序从较小的部分。这意味着 CAL 句柄的部分加载 (和如果),以及提供基本功能。您可以决定的这些功能可以帮助您的方式可能会收到您的作业和它的执行。
我在本文中的示例使用的 CAL,很多。则它是一个外壳程序的应用程序使用在运行时加载几个模块,视图置于区域 (如 图 2 所示) 和鏀 寔鏈嶅姟 CAL。但我们的代码前,您需要了解依赖关系注入 (也称为控制反转或 IoC) 有关的一些基本概念。许多 CAL 的功能依赖于依赖关系注入,以便了解基础知识将帮助您开发的 Silverlight 项目与 Prism 体系结构。
图 2 复合应用程序结构
引入依赖关系注入
在典型的开发,项目开头的入口点 (可执行文件、 一个 default.aspx 页等)。您可能开发应用程序为一个巨大的项目,而在大多数情况下进行某种级别的模块化存在,您的应用程序加载大量项目的一部分的程序集。主程序集知道哪些程序集,它需要并创建这些部分的硬引用。编译时知道所有引用的程序集,主项目,并由用户界面以静态控件组成。应用程序是代码的在控件中需要它,并通常知道的所有代码,它可能使用。这将成为问题,但是,因为开发主应用程序项目内发生。一个单一的应用程序随着生成时间并相互冲突的更改可以减缓开发。
依赖关系注入旨在反向这种情况下,通过提供在运行时设置依赖项的说明。而不是项目控制这些依赖项的一段代码调用一个容器负责插入它们。
但为什么这一点非常重要?为一首先 modularizing 您的代码应使它更易于测试。能够交换出项目的依赖项启用清洗器测试,以便只有要测试代码测试发生故障而不是依赖项的嵌套链中的某个位置的代码的来源。下面是一个具体示例。假设您有一个组件,其他开发人员使用查找特定公司的地址。您的组件取决于为您检索数据的数据访问组件。测试您的组件时, 首先测试对数据库,并且的一些测试。但由于架构和生成数据库的不断变化,您不知道是否测试失败因为您自己的代码或数据访问代码。与您的组件的数据的硬依赖访问组件,测试应用程序成为不可靠,原因改动时跟踪失败在您的代码或其他的代码中。
您的组件可能如下所示:
public class AddressComponent
{
DataAccessComponent data = new DataAccessComponent();
public AddressComponent()
{
}
...
}
而不是一个硬连线的组件,您可能接受代表您的数据访问的接口,如下所示:
public interface IDataAccess
{
...
}
public class AddressComponent
{
IDataAccess data;
public AddressComponent(IDataAccess da)
{
data = da;
}
...
}
通常,接口使用以便可以创建一个允许您调整您的代码的版本。 这种方法通常被称为"模拟。 模拟创建实际上不代表实际的版本的依赖项的实现方法。 按其原义,您正在创建一个模拟实现。
此方法很好的因为依赖项 (IDataAccess) 到项目中插入对象的构造期间。 IDataAccess 组件的实现将取决于要求 (测试或实际)。
这实质上是依赖关系注入的工作原理,但如何处理该注入? 容器的工作是处理类型这就允许您注册类型,然后解决它们的创建。 渚嬪的方式 假定具体实现 IDataAccess 接口的类。 在启动应用程序的过程中,可以判断容器以注册类型。 其他任何地方在应用程序需要类型,您可以让容器解析类型,如下所示:
public void App_Startup()
{
container.RegisterType<IDataAccess, DbDataAccess>();
}
...
public void GetData()
{
IDataAccess acc = container.Resolve<IDataAccess>();
}
根据情况 (测试或生产) 您可以仅通过更改注册换出 IDataAccess 的实现。 此外,容器可以处理构造注入的依赖项。 如果需要由容器的构造函数创建的对象需要一个接口,容器可以解决,它解析类型,并将它传递到在的构造函数中,如 图 3 所示。
图 3 由容器的类型解析
public class AddressComponent : IAddressComponent
{
IDataAccess data;
public AddressComponent(IDataAccess da)
{
data = da;
}
}
...
public void App_Startup()
{
container.RegisterType<IAddressComponent, AddressComponent>();
container.RegisterType<IDataAccess, DbDataAccess>();
}
public void GetAddresses()
{
// When we ask the container to create the AddressComponent,
// it sees that a constructor takes a IDataAccess object
// so it automatically resolves that dependency
IAddressComponent addr = container.Resolve<IAddressComponent>();
}
请注意 AddressComponent 的构造函数采用 IDataAccess 的实现。 当构造函数创建 AddressComponent 类解析过程中时,它自动创建 IDataAccess 的该实例,并将它传递给该 AddressComponent。
注册与容器类型时, 还会告诉容器以特殊方式处理该类型的生存期。 渚嬪的方式 如果与日志记录组件使用想要将其视为单一实例,以便在需要日志记录的应用程序的每一部分没有得到自己的副本 (这是默认行为)。 涓烘 可以提供 LifetimeManager 抽象类的实现。 支持多个生存期管理器。 ContainerControlledLifetimeManager 是每个进程单一实例,PerThreadLifetimeManager 单一实例每个线程。 ExternallyControlledLifetimeManager,容器存放在单一实例对弱引用。 如果外部释放该对象时容器中创建一个新的实例,否则将返回该弱引用中包含活动对象。
您可以使用 LifetimeManager 类通过指定其注册为类型时。 下面是一个示例:
container.RegisterType<IAddressComponent, AddressComponent>( new ContainerControlledLifetimeManager());
在 CAL,IoC 容器基于完全一致框架从模式与实践组。 我将在下面的示例使用完全一致的容器,但也有打开源替代在完全一致 IoC 容器 Ninject、 以及 Spring.NET、 Castle,StructureMap 数。 如果您熟悉和已使用而不是完全一致的 IoC 容器,您可以提供您自己的容器 (尽管需要花费一些更多的工作)。
启动行为
通常在 Silverlight 应用程序中启动行为就是创建主 XAML 页的类,并将它分配给应用程序的 RootVisual 属性。 在一个复合应用程序中此项工作仍然是需要但而不是创建 XAML 页类的复合应用程序通常使用引导类来处理启动行为。
若要启动,需要一个从 UnityBootstrapper 类派生的新类。 此类是在 Microsoft.Practices.Composite.UnityExtensions 程序集中。 引导程序包含处理启动行为的不同部分的可重写方法。 通常,您不会覆盖每个启动方法,仅在的需要。 您必须重写两个方法都是 CreateShell 和 GetModuleCatalog。
CreateShell 方法是创建的主 XAML 类。 这通常称为外壳程序因为它是可视容器应用程序的组件。 我的示例包括引导程序创建外壳类的新实例并为其 RootVisual 返回此新的外壳类之前,如下所示:
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
Shell theShell = new Shell();
App.Current.RootVisual = theShell;
return theShell;
}
protected override IModuleCatalog GetModuleCatalog()
{
...
}
}
在下一部分中我将解释 GetModuleCatalog 方法返回要加载的模块的列表。
现在,您有一个引导程序类,可以使用它在 Silverlight 应用程序的启动方法。 通常,创建引导程序类的新实例并调用其运行方法,如 图 4 所示。
图 4 创建引导程序的实例
public partial class App : Application
{
public App()
{
this.Startup += this.Application_Startup;
this.Exit += this.Application_Exit;
this.UnhandledException += this.Application_UnhandledException;
InitializeComponent();
}
private void Application_Startup(object sender, StartupEventArgs e)
{
Bootstrapper boot = new Bootstrapper();
boot.Run();
}
...
}
此外参与注册与容器的类型的不同部分,该应用程序需要的引导程序。 要实现此目的,您重写引导程序 ConfigureContainer 的方法。 这样,您有机会注册要使用的应用程序的其余部分的任何类型。 图 5 显示了代码。
图 5 注册类型
public class Bootstrapper : UnityBootstrapper
{
protected override void ConfigureContainer()
{
Container.RegisterType<IShellProvider, Shell>();
base.ConfigureContainer();
}
protected override DependencyObject CreateShell()
{
// Get the provider for the shell
IShellProvider shellProvider = Container.Resolve<IShellProvider>();
// Tell the provider to create the shell
UIElement theShell = shellProvider.CreateShell();
// Assign the shell to the root visual of our App
App.Current.RootVisual = theShell;
// Return the Shell
return theShell;
}
protected override IModuleCatalog GetModuleCatalog()
{
...
}
}
此处,代码注册为实现我们的示例中创建并不是 CAL 框架的一部分 IShellProvider 接口的类接口。 这样我们可以使用它选择我们 CreateShell 方法的实现中。 我们可以解析该接口,然后使用它创建外壳程序的实例,因此我们可以将其分配给 RootVisual 并将其返回。 此方法可能看起来额外的工作,但随着您深入 CAL 如何帮助您生成应用程序,变得清晰此引导程序如何帮助您。
模块化
在典型的.NET 环境下程序集是工作的主要单位。 这一指定允许开发人员在他们的代码分别从每个其他工作。 在 CAL,这些单位工作的每个模块,并且使用模块 CAL,它需要一个可以进行通讯模块的启动行为的类。 此类还需要将支持 IModule 接口。 IModule 接口要求一个方法调用允许模块以设置本身在应用程序的其余部分中使用的初始化。 该示例包含一个 ServerLogger 包含模块的日志记录功能,我们的应用程序。 ServerLoggingModule 类支持 IModule 接口,如下所示:
public class ServerLoggerModule : IModule
{
public void Initialize()
{
...
}
}
问题是我们不知道我们要在我们的模块中初始化。 因为 ServerLogging 模块似乎逻辑我们想要注册,我们的日志记录类型。 我们要使用这样的人需要日志记录功能可以只使用我们的实现不知道准确的执行日志记录类型注册类型的容器。
我们可以通过创建采用 IUnityContainer 接口的构造函数来获取容器。 如果您记得讨论的依赖关系注入,容器使用构造函数注入添加知道它的类型。 IUnityContainer 代表容器应用程序中的,以便我们添加的构造函数,如果我们可以再将其保存和使用我们的初始化中类似于这样:
public class ServerLoggerModule : IModule
{
IUnityContainer theContainer;
public ServerLoggerModule(IUnityContainer container)
{
theContainer = container;
}
public void Initialize()
{
theContainer.RegisterType<ILoggerFacade, ServerBasedLogger>(
new ContainerControlledLifetimeManager());
}
}
一旦初始化,本模块负责实现应用程序的日志记录。 但如何执行此获取加载模块吗?
用于撰写的应用程序 CAL 时您需要创建一个包含应用程序的所有模块 ModuleCatalog。 通过重写引导程序的 GetModuleCatalog 调用创建此目录。 在 Silverlight,可以填充此目录与代码或 XAML。
使用代码,创建 ModuleCatalog 类的新实例并填充它的模块。 渚嬪的方式 看:
protected override IModuleCatalog GetModuleCatalog()
{
var logModule = new ModuleInfo()
{
ModuleName = "ServerLogger",
ModuleType = "ServerLogger.ServerLoggerModule, ServerLogger, Version = 1.0.0.0"
};
var catalog = new ModuleCatalog();
catalog.AddModule(logModule);
return catalog;
}
此处,只需添加一个模块称为 ServerLogger,ModuleInfo 的 ModuleType 属性中定义的类型。 鍙 ﹀ 的方式 您可以指定模块之间的依赖关系。 由于某些模块可能依赖于其他,使用相关性有助于知道的顺序将依赖项的目录。 使用 ModuleInfo.DependsOn 属性,您可以指定所需加载另一个模块的命名的模块。
可以直接从一个 XAML 文件加载目录,如下所示:
protected override IModuleCatalog GetModuleCatalog()
{
var catalog = ModuleCatalog.CreateFromXaml(new Uri("catalog.xaml",
UriKind.Relative));
return catalog;
}
XAML 文件包含您可以使用代码创建相同的类型信息。 使用 XAML 的好处是您可以更改其动态。 (假设检索 XAML 文件,从一个服务器或基于另一个位置上的用户登录) catalog.xaml 文件的一个示例如 图 6 所示。
图 6 catalog.xaml 文件示例
<m:ModuleCatalog
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:m="clr-namespace:Microsoft.Practices.Composite.Modularity;
assembly=Microsoft.Practices.Composite">
<m:ModuleInfoGroup InitializationMode="WhenAvailable">
<m:ModuleInfo ModuleName="GameEditor.Client.Data"
ModuleType="GameEditor.Client.Data.GameEditorDataModule,
GameEditor.Client.Data, Version=1.0.0.0"/>
<m:ModuleInfo ModuleName="GameEditor.GameList"
ModuleType="GameEditor.GameList.GameListModule,
GameEditor.GameList, Version=1.0.0.0"
InitializationMode="WhenAvailable">
<m:ModuleInfo.DependsOn>
<sys:String>GameEditor.Client.Data</sys:String>
</m:ModuleInfo.DependsOn>
</m:ModuleInfo>
</m:ModuleInfoGroup>
</m:ModuleCatalog>
在此 XAML 目录组中将包含两个模块,依赖于第二个模块第一个。 可以使用基于角色或权限,特定 XAML 目录时,您可以使用代码。
一旦目录由引导程序加载,它将尝试创建模块类的实例,并允许其自身的初始化。 在下面的代码示例,类型具有应用程序将引用 (因此,已加载到内存中) 为该工作目录。
这是此工具将变为 indispensible 到 Silverlight。 尽管该程序集的工作单位,您可以指定一个包含模块的.xap 文件。 涓烘 您指定在 ModuleInfo Ref 值。 Ref 值是一个包含模块的.xap 文件路径:
protected override IModuleCatalog GetModuleCatalog()
{
var logModule = new ModuleInfo()
{
ModuleName = "ServerLogger",
ModuleType =
"ServerLogger.ServerLoggerModule, ServerLogger, Version= 1.0.0.0",
Ref = "ServerLogger.xap"
};
var catalog = new ModuleCatalog();
catalog.AddModule(logModule);
return catalog;
}
当您指定一个.xap 文件时,引导程序知道程序集不可用,并转到该服务器和异步检索.xap 文件。 加载后.xap 文件,Prism 将加载程序集和创建模块类型并初始化模块。
包含多个模块的.xap 文件,您可以创建一个包含 ModuleInfo 对象的一组 ModuleGroup,并设置该 Ref 从单个的.xap 文件中加载所有这些模块 ModuleGroup 的:
var modGroup = new ModuleInfoGroup();
modGroup.Ref = "MyMods.xap";
modGroup.Add(logModule);
modGroup.Add(dataModule);
modGroup.Add(viewModule);
var catalog = new ModuleCatalog();
catalog.AddGroup(modGroup);
对于 Silverlight 的应用程序这是一个方法撰写您从多个的.xap 文件的应用程序它允许您单独组成应用程序的版本不同的节。
创建到可以存放在.xap 文件中的 Silverlight 模块时, 创建一个 Silverlight 应用程序 (不 Silverlight 库)。 然后,您引用了要放入.xap 文件中的所有模块项目。 您需要删除 app.xaml 和 page.xaml 文件,因为此.xap 文件将不是加载和运行类似于一个典型的.xap 文件。 .xap 文件是只是一个容器 (可能是一个.zip 文件) 并不重要。 此外,如果您引用已在主项目中引用的项目可以更改为本地复制这些引用 = 在属性中的错误,因为您不需要程序集 (主应用程序已加载它们,目录不会加载它们的第二次以便).xap 文件中。
但是,通过网络加载一个巨大的应用程序使用多个调用尚未纳入一样,它将帮助性能。 这是 ModuleInfo 的 InitializationMode 属性进入播放。 InitializationMode 支持两种模式: WhenAvailable,其中.xap 文件是异步加载,然后初始化 (这是默认行为),和 OnDemand,在.xap 加载中显式请求时。 因为模块目录不知道之前初始化的模块中的类型,解决与 OnDemand 初始化的类型将失败。
对模块和组的点播支持允许您加载大型应用程序中的某些功能,根据需要。 启动时间加速,并可以加载其他所需的代码,用户与应用程序交互。 这是一个很好的功能,用于分隔各部分的应用程序的授权后。 只需要用户不必下载的代码,它们将永远不会使用应用程序的几个部分。
若要加载模块根据,需要访问 IModuleManager 接口。 大多数情况下,您请求此需要加载上请求一个模块的类的构造函数中。 然后您使用 IModuleManager 加载模块通过调用传给加载模块,如 图 7 所示。
图 7 调用传给加载模块
public class GameListViewModel : IGameListViewModel
{
IModuleManager theModuleManager = null;
public GameListViewModel(IModuleManager modMgr)
{
theModuleManager = modMgr;
}
void theModel_LoadGamesComplete(object sender,
LoadEntityCompleteEventArgs<Game> e)
{
...
// Since we now have games, let's load the detail pane
theModuleManager.LoadModule("GameEditor.GameDetails");
}
}
模块是只需 modularization 在应用程序中的单位。 在 Silverlight,将模块得起来就像一个库项目,但您可以使用模块初始化的额外工作,分隔模块从主项目。
复合 UI
一个典型的资源管理器应用程序在左的窗格中显示列表,或树的信息以及右侧包含有关在左窗格中选定项的详细信息。 在 CAL,这些区域称为区域。
通过使用 RegionManager 类上的附加的属性 CAL 支持直接在 XAML 中的定义的区域。 此属性可以指定您外壳程序中的区域,然后指明应视图承载在区域中。 渚嬪的方式 我们外壳叿鏈如下所示変 LookupRegion 和 DetailRegion,两个区域:
<UserControl
...
xmlns:rg=
"clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;
assembly=Microsoft.Practices.Composite.Presentation">
...
<ScrollViewer rg:RegionManager.RegionName="LookupRegion" />
<ScrollViewer rg:RegionManager.RegionName="DetailRegion" />
</UserControl>
可以将一个 RegionName 应用于在一个 ItemsControl 和其派生的控件 (渚嬪的方式 列表框) ; 选择器和其派生的控件 (例如 TabControl) ; ContentControl 和其派生的控件 (渚嬪的方式 ScrollViewer)。
一旦您定义的区域,您可以直接模块载入其视图区域通过该 IRegionManager 界面如下所示:
public class GameListModule : IModule
{
IRegionManager regionManager = null;
public GameListModule(IRegionManager mgr)
{
regionManager = mgr;
}
public void Initialize()
{
// Build the View
var view = new GameListView();
// Show it in the region
regionManager.AddToRegion("LookupRegion", view);
}
}
此功能允许您在应用程序的视图可以显示,然后定义如何视图放在该区域,允许外壳程序是完全 ignorant 视图的模块中定义的区域。
该区域的行为可能不同具体取决于承载控件类型。 该示例使用一个 ScrollViewer,以便可以一个和只有一个视图添加到区域。 与之相反,ItemControl 区域允许多个视图。 添加时,每个视图,它显示为该 ItemsControl 的新项目。 该工具也更容易生成功能,例如仪表板。
如果您使用的 MVVM 模式定义您的视图您可以混用区域和服务位置方面容器以使您的视图查看 ignorant 的每个模型,然后让它们在运行时中加入该模块。 渚嬪的方式 如果更改该 GameListModule 我可以注册容器的视图和视图模型然后再将它们应用到区域的视图之前加入如 图 8 所示。
图 8 加入一个区域中的视图
public class GameListModule : IModule
{
IRegionManager regionManager = null;
IUnityContainer container = null;
public GameListModule(IUnityContainer con, IRegionManager mgr)
{
regionManager = mgr;
container = con;
}
public void Initialize()
{
RegisterServices();
// Build the View
var view = container.Resolve<IGameListView>();
// Get an Implemenation of IViewModel
var viewModel = container.Resolve<IGameListViewModel>();
// Marry Them
view.ApplyModel(viewModel);
// Show it in the region
regionManager.AddToRegion("LookupRegion", view);
}
void RegisterServices()
{
container.RegisterType<IGameListView, GameListView>();
container.RegisterType<IGameListViewModel, GameListViewModel>();
}
}
此方法允许您使用的同时保持严格的 MVVM 分离 UI 复合。
事件聚合
多个视图在通过 UI 复合应用程序中后,您面临的一个常见的问题。 即使独立于视图,以更好地对它们进行测试和开发支持在构建有通常的视图不能完全独立的接触点。 它们逻辑上是相结合,因为它们需要进行通信,但您希望为松散耦合尽可能的逻辑的耦合而不考虑它们。
要启用松散耦合和视图间的通信,CAL 支持服务调用事件聚合。 事件聚合允许到发布服务器和全局事件的使用者代码的不同部分的访问。 这种访问提供一种通信不被紧密耦合的简单方法,并且通过使用 CAL 的 IEventAggregator 接口来实现的。 IEventAggregator 允许您发布和订阅的事件在您的应用程序的不同模块。
您可以进行通信之前,您将需要从 EventBase 派生的类。 通常,您可以创建一个简单的事件从该 CompositePresentationEvent <t> 派生的类。 此泛型类可以指定您要发布该事件的有效负载。 在这种情况下,GameListViewModel 即将发布,以便在要更改其上下文为用户选择一个游戏的其他控件可以订阅该事件在选择游戏之后的事件。 我们的事件类别如下所示:
public class GameSelectedEvent : CompositePresentationEvent<Game>
{
}
一旦事件定义时,事件聚合器可以发布事件调用其 GetEvent 方法。 这将检索要汇总的单独事件。 第一个调用此方法的用户创建在单一实例。 从事件,可以调用 Publish 方法将创建事件。 发布该事件就像激发事件。 您不需要发布之前需要将信息发送该事件。 渚嬪的方式 在该 GameList 中选择一个游戏时, 我们的示例将发布所选的游戏使用新的事件:
// Fire Selection Changed with Global Event
theEventAggregator.GetEvent<GameSelectedEvent>().Publish(o as Game);
组成应用程序的其他部分,您可以订阅在发布事件之后调用该事件。 事件订阅方法允许您指定发布该事件时调用该方法,一个选项,使您得以请求调用该事件的线程语义 (渚嬪的方式 UI 线程通常用于),按住是否已在聚合函数对传入的信息的引用,以便它不属于垃圾回收:
// Register for the aggregated event
aggregator.GetEvent<GameSelectedEvent>().Subscribe(SetGame,
ThreadOption.UIThread,
false);
作为订阅者,还可以指定只有在特定情况下被称为筛选器。 假设返回应用程序和调用仅在特定的数据状态的筛选器的状态的事件。
该事件聚合函数允许您不会导致紧密耦合模块之间的通信。 如果您永远不会发布的事件订阅,或发布的不订阅事件,您的代码将永远不会失败。
委托命令
在与适用于 WPF) 不同的 Silverlight,不存在命令基础结构,则返回 True。 这将强制内含代码来完成将直接使用命令的基础结构在 XAML 中将更容易完成的任务视图中的使用。 直到 Silverlight 支持此功能,CAL 支持可帮助解决此问题的类: 在 DelegateCommand。
要开始使用 DelegateCommand,您需要您 ViewModel 中定义该 DelegateCommand,以便您可以数据绑定到该。 在该 ViewModel,可以创建一个新的 DelegateCommand。 在 DelegateCommand 期望的要发送到它的数据类型 (通常对象是否使用任何数据) 和一个或两个回调方法 (或 lambda 函数)。 这些方法的第一个是命令被触发时要执行该操作。 (可选),您可以指定第二个回调被调用以测试是否可以激发命令。 其目的是启用禁用的用户界面中的对象 (按钮,例如) 时,它是以触发该命令无效。 渚嬪的方式 我们 GameDetailsViewModel 包含支持数据保存到一个命令:
// Create the DelegateCommand
SaveCommand = new DelegateCommand<object>(c => Save(), c => CanSave());
在执行 SaveCommand 时它会在我们 ViewModel 调用 Save 方法。 然后调用 CanSave 方法以确保是有效的命令。 这样,如果有必要禁用 UI DelegateCommand。 为查看更改的状态,您可以调用 DelegateCommand.RaiseCanExecuteChanged 方法强制 CanSave 方法启用或禁用 UI 为所需的一个新的检查。
若要将此绑定到 XAML,请使用该 Click.Command 附加 Microsoft.Practices.Composite.Presentation.Commands 命名空间中的属性。 然后绑定命令为命令在您 ViewModel,有的值如下所示:
<Button Content="Save"
cmd:Click.Command="{Binding SaveCommand}"
Style="{StaticResource ourButton}"
Grid.Column="1" />
现在,触发 Click 事件时执行命令。 如果您希望,您可以指定命令的参数被发送到命令,以便可以重用它。
您可能想知道,CAL 中存在的唯一命令是一个按钮 (或任何其他的选择器) 的 Click 事件。 但是,类可用于编写您自己的命令是相当简单。 代码示例包括一个命令。 SelectionChanged 上列表框/组合框 此命令称为该 SelectorCommandBehavior 并从该 CommandBehaviorBase <t> 派生类。 查看自定义命令行为实现将您提供一个编写您自己的命令行为的起始位置。
上的换行
开发大型 Silverlight 应用程序时,有明确的问题点。 通过创建松散耦合模块化与应用程序,您获得的好处,可以更改灵活的响应。 Microsoft Prism 项目提供了工具和指南以允许该灵活性来向您的项目的图面。 尽管 Prism 不是一种全效方法,模块化的 CAL 都意味着可以使用什么适合您的特定方案和将其余部分。
Shawn Wildemuth 是一个 Microsoft MVP (C#),并在 founder Wildermuth 咨询服务。 他是几个的书籍和大量文章的作者。 鍙 ﹀ 的方式 Shawn 当前运行在 Silverlight 教程,教学周围的国家/地区的 Silverlight 2。 他可以在联系 shawn@wildermuthconsulting.com.