复合 UI 应用程序块 CAB MDI 应用程序






4.79/5 (26投票s)
CAB 世界简介,通过一个示例 MDI 智能客户端应用程序进行演示
引言
本文试图阐明我被问到(以及我自己问自己)关于复合 UI 应用程序块(CAB)的许多问题。这是 Microsoft 模式与实践组牌库中的王牌,它提供了一个可重用的、基于组件的框架,用于使用最佳(我认为取决于你对最佳的定义)实践来构建复杂、智能客户端的 GUI。本质上,它允许使用更简单、独立开发、测试和部署的组件来构建复杂的 GUI。
最初针对 WinForms - 特别是使用 C# - CAB 现在正被推广用于更广泛的客户端技术,包括 WPF 和 WPF/E。当我写下这些文字时,正值一月份,圣诞节后的黑暗和寒冷,下一个版本已初步宣布将于 2007 年 4 月发布,所以不用等太久了。
希望本文能回答以下问题
- 这是什么?
- 我为什么要使用它?
- 它为什么这么复杂?
- CAB 提供了哪些部分,哪些部分需要我自己实现?
- 我该如何使用它?
复合 UI 应用程序块 (CAB)
复合 UI 应用程序块是一个 GUI 框架,用于创建复杂、模块化的智能客户端应用程序。它主要提供以下功能:
- 一个促进构建业务应用程序结构化方法的框架。
- 动态加载、独立但互操作的模块,托管在通用 Shell 中。
- 用于模块之间和模块部分之间松耦合通信的事件代理。
- 命令模式实现。
- 用于实现 Model-View-Control (MVC) 和 Model-View-Presenter (MVP) 模式的基类。
- 一个用于动态加载服务的基础设施。例如,身份验证、授权、模块定位和模块加载。
- 指导包,用于简化组件创建。
我们要分享一个 CAB 吗?
CAB 主要面向由开发团队基于用例驱动的方法设计的业务应用程序。但是,在开发你自己的(孤独的开发者)应用程序时,也没有理由不使用它。事实上,由于它提供了一个插件框架,它允许你开发一个应用程序,将来可以添加新的模块而无需更改主应用程序。
使用开发团队构建应用程序会带来其特有的问题。团队通常包括:
- 核心开发人员 - 构建 GUI 框架、通用服务等的架构师和首席开发人员。
- 应用程序开发人员 - 设计和编写业务相关的窗口,即实现业务用例。
团队成员来来往往(甚至可能被公共汽车撞倒),因此保持架构的一致性并使应用程序开发人员免受核心工作原理的复杂性影响非常重要。他们可以独立开发、测试和部署他们的模块,而无需过多担心 GUI 框架本身(或对其进行干扰并搞砸)。一旦核心开发人员构建了应用程序框架,应用程序开发人员的学习曲线就非常短。反之,核心框架中的任何更改都可以(大部分)独立于插件模块发生。
肯定有成百上千个 GUI 框架(包括我自己开发的一些)像 CAB 一样用于构建企业级 GUI,但如果 Microsoft 推广 CAB 及其最佳实践,那么它更有可能(无论对错)被开发者社区采纳。
(CAB 的)成本
为应用程序开发人员提供一个隔离的环境是有成本的。框架实现(CAB 和应用程序核心)必须提供一个相当通用的机制来处理事务。CAB 的核心是 ObjectBuilder,它广泛使用依赖注入(又称控制反转)和一系列策略,允许在实例化和准备使用对象时执行多个操作。它还通过以相反的顺序执行适当的管道进程来支持对对象实例的受控处置。其中很大一部分是使用面向切面编程(AOP)和反射以及上下文属性来实现的 - 因此,如果你深入研究核心 CAB 代码,请做好准备。
这使得应用程序开发人员非常容易,他们只需用适当的属性标记构造函数、事件处理程序、模块等。(尽管核心开发人员的生活要艰难一些!)
CAB 术语和定义
我无意在此过多细节,否则我只会抄袭其他文本。但是,我将简要总结你将在本文及其他关于 CAB 框架的文章中遇到的基本术语和定义。
正如我之前所说,CAB 主要供开发团队使用;我将尝试指出这些术语中哪些是针对性的,以及谁负责实现该部分。有三个不同的责任领域:
- CAB - 提供的 CAB 框架的一部分。
- 核心开发人员 (CD) - 负责应用程序框架的开发人员。
- 应用程序开发人员 (AD) - 负责特定功能领域或用例的开发人员。
请记住,这只是一个代表性的例子,但请查看链接的页面以获取参考资料和进一步阅读。注意各个组件之间明确的关注点分离。这对于 CAB 的构建方式至关重要,并避免了蜘蛛网架构。
Shell 应用程序 (CD)
这是托管应用程序的容器。它的任务是加载和启动主应用程序 Shell。
Shell (CD)
所有动态加载模块的通用主用户界面是 Shell。通常是一个窗体,它始终托管一个根 WorkItem,这是模块注册的所有服务、模块和 WorkItem 的根访问点。
工作区 (CD)
工作区是一个控件,主要负责保存和显示由 WorkItems 创建的用户界面元素。CAB 提供了一些标准工作区(选项卡、MDI、面板等),但你可以实现自己的。
服务 (CD)
服务类封装了客户端应用程序、特定模块或 WorkItems 的通用功能。安全和 Web 服务访问通常由服务类处理。
模块 (CD AD)
将 WorkItems 逻辑分组为单个部署单元是模块。这允许你轻松地将新的 GUI 功能分发到现有应用程序中。实际上就是一个插件。
ProfileCatalog (CD)
通常是一个配置文件 XML 文件,指定哪些模块和服务需要加载到应用程序中。如果你需要从其他地方(例如数据库或 Web 服务)加载配置,可以覆盖默认实现。
ModuleLoader (CAB)
CAB 提供的一项服务,负责加载 ProfileCatalog 中描述的模块。它动态加载程序集,如果找到 ModuleInit 类,则查找并实例化它。
ModuleInit (CD AD)
每个模块都需要一个 ModuleInit 类。它负责加载模块所需的所有服务、WorkItems 和 GUI 扩展(UIExtensionSite)。
UIExtensionSite (CD)
UIExtensionSite 是一个用于使用菜单、工具栏或状态栏扩展 Shell 的类。
WorkItem (CD AD)
WorkItem 是一个封装了用例所需所有逻辑的类。从技术上讲,它是一个容器,托管所有必需的对象,如视图及其呈现器或控制器、状态和命令。
命令 (CAB)
用于实现命令模式的类。CAB 支持手动创建的命令或通过将 `[CommandHandler]` 属性应用于命令处理方法来创建的声明式命令。你可以为一个命令注册多个调用方。
事件发布
CAB 提供了一个事件代理,可以轻松地从模块中的类发布事件。这为松耦合的 WorkItems 提供了通信方式。这是使用标记有 `[EventPublication]` 属性的 .NET 事件实现的。
事件订阅
EventSubscription 是事件的接收端。希望订阅特定事件的类只需实现一个事件处理程序 - 一个带有通用签名并标记有 `[EventSubscription]` 属性的方法。
模型/视图/控制器/呈现器
Model 是 WorkItem 包含和处理的业务类。例如,账户、客户。View 是一个用户控件,负责向用户呈现 Model 并允许用户修改其内容。Controller/Presenter 链接 Model 和 View。CAB 鼓励使用 MVC 或 MVP 模式,但不强制使用。但是,SCSF 倾向于 MVP,并提供向导来生成它们。顺便说一句,MS p&p 现在似乎更倾向于使用 WorkItemController。
SmartPart
SmartPart 只是一个用 `[SmartPart]` 属性标记的常规 UserControl 的另一个名称。
ObjectBuilder
ObjectBuilder 是 CAB 的核心组件。它基本上是一个用于创建需要特定构建策略的对象,或者在创建这些对象时需要实例化和初始化依赖对象等功能的工厂。这是通过依赖注入和 Object Builder 管道实现的。它使用通过 ContextAttributes 实现的 AOP 技术,这些属性允许在运行时注入或替换代码。这是最根本的“魔法发生”的要素。
智能客户端软件工厂 - SCSF
开始使用 CAB 有点麻烦,而且你需要做很多事情来构建应用程序框架。此外,创建新的模块和 WorkItems 比需要做的更繁琐。SCSF 是 VS 2005 的一个扩展,它使用 Guidance Packages(换句话说就是向导)极大地简化了这些任务。还可以创建自己的自定义 Guidance Packages 提供给应用程序开发人员。
那么,开始构建吧。
基本上,你有两个选项来构建 CAB 框架和应用程序:
- 自己从头开始构建
- 使用智能客户端软件工厂 (SCSF) 和 Guidance Packages(老式的向导)
我选择从头开始构建一个简单的应用程序,以帮助阐明 CAB 应用程序的构建方式以及各种组件的使用和配合 - SCSF 巧妙地隐藏了幕后操作,而我们在这篇文章中正试图一探究竟。现在已经有相当多的示例应用程序了,尤其是文章末尾链接的实践实验室。但在谷歌搜索一番后,似乎没有使用 MDIWorkspace 的示例,所以构建一个看起来很有用的。
我知道,我知道。随着 WPF 的出现及其对 MDI 支持的缺乏(因为 WPF 只渲染顶层窗口),MDI 可能不是最佳选择。但是,如果你计划构建一个自定义工作区的停靠框架,这可能是一个不错的起点。
我现在要坦白,这并不是一个功能齐全的 CAB 应用程序,也没有使用 CAB 提供的所有功能 - 状态、服务、动态模块加载等。它只是一个如何构建 CAB 应用程序的示例,该应用程序恰好使用了 MDIWorkspace。重点在于“简单”这个词。(应用程序,而不是作者)
Shell 应用程序
- 使用“新建项目”向导创建一个新的 C# Windows 应用程序,并将其命名为 SimpleMDIApp。
- 添加对以下 CAB 程序集的引用。你可以从 Microsoft 下载它们,或者从附加的源代码和项目文件中提取。
- Microsoft.Practices.CompositeUI.dll
- Microsoft.Practices.CompositeUI.WinForms.dll
- Microsoft.Practices.ObjectBuilder.dll
- 将 Program.cs 重命名为 SimpleMDIShellApp.cs
- 将生成的代码替换为以下内容:
using System; using System.Windows.Forms; using Microsoft.Practices.CompositeUI; using Microsoft.Practices.CompositeUI.UIElements; using Microsoft.Practices.CompositeUI.WinForms; using Microsoft.Practices.CompositeUI.Commands; using Microsoft.Practices.CompositeUI.Services; namespace SimpleMDIApp { public class SimpleMDIShellApp : FormShellApplication<WorkItem, SimpleMDIShellForm> { [STAThread] public static void Main() { new SimpleMDIShellApp().Run(); } } }
这是最基本的 Shell 应用程序,但它需要一个 Shell - 在本例中是 SimpleMDIShellForm - 来提供菜单、工具栏和工作区。
Shell
重命名现有的、生成的 Form.cs 为 SimpleMDIShellForm.cs。你现在可以构建并运行应用程序,但它并不那么令人兴奋。它只是显示 Form1,但它运行在 CAB 框架中。
通过打开 SimpleMDIShellForm.Designer.cs 来添加菜单和状态栏,并将现有代码更改为:
namespace SimpleMDIApp { partial class SimpleMDIShellForm { /// <summary /> /// Required designer variable. /// </summary /> private System.ComponentModel.IContainer components = null; /// <summary /> /// Clean up any resources being used. /// </summary /> /// true if managed resources should be disposed; otherwise,false protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// <summary /> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary /> private void InitializeComponent() { this.menuMain = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.windowToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.statusStrip1 = new System.Windows.Forms.StatusStrip(); this.menuMain.SuspendLayout(); this.SuspendLayout(); // // menuMain // this.menuMain.ImeMode = System.Windows.Forms.ImeMode.HangulFull; this.menuMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.fileToolStripMenuItem, this.windowToolStripMenuItem}); this.menuMain.Location = new System.Drawing.Point(0, 0); this.menuMain.MdiWindowListItem = this.windowToolStripMenuItem; this.menuMain.Name = "menuMain"; this.menuMain.Size = new System.Drawing.Size(735, 24); this.menuMain.TabIndex = 0; this.menuMain.Text = "menuStrip"; // // fileToolStripMenuItem // this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; this.fileToolStripMenuItem.Size =new System.Drawing.Size(35, 20); this.fileToolStripMenuItem.Text = "File"; // // windowToolStripMenuItem // this.windowToolStripMenuItem.Name = "windowToolStripMenuItem"; this.windowToolStripMenuItem.Size=new System.Drawing.Size(57,20); this.windowToolStripMenuItem.Text = "Window"; // // statusStrip1 // this.statusStrip1.Location = new System.Drawing.Point(0, 384); this.statusStrip1.Name = "statusStrip1"; this.statusStrip1.Size = new System.Drawing.Size(735, 22); this.statusStrip1.TabIndex = 1; this.statusStrip1.Text = "statusStrip1"; // // SimpleMDIShellForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(735, 406); this.Controls.Add(this.statusStrip1); this.Controls.Add(this.menuMain); this.MainMenuStrip = this.menuMain; this.Name = "SimpleMDIShellForm"; this.Text = "Simple CAB MDI Application"; this.menuMain.ResumeLayout(false); this.menuMain.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.MenuStrip menuMain; private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; private System.Windows.Forms.StatusStrip statusStrip1; private System.Windows.Forms.ToolStripMenuItem windowToolStripMenuItem; } }
所有这些基本上只是在原始窗体代码中添加了一个 MenuStrip、StatusStrip 和两个 MenuItems。它还更改了窗体标题和一些样式。重要的是名为 fileToolStripmenuItem 的 ToolStripMenuItem。
你现在可以构建并运行它了 - 它现在只是一个简单的应用程序窗体,带有一个没有功能的菜单和状态栏。
模块
虽然本示例不会使用模块做任何有用的事情,但我包含它是因为了解 CAB 中事件发生的顺序对于充分利用它很重要。
插入一个新的 C# 类,命名为 `SimpleMDIModuleInit`,如下所示:
using System;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.Commands;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.ObjectBuilder;
namespace SimpleMDIApp
{
public class SimpleMDIModuleInit : ModuleInit
{
private WorkItem workItem;
[InjectionConstructor]
public SimpleMDIModuleInit([ServiceDependency] WorkItem workItem)
{
this.workItem = workItem;
}
public override void AddServices()
{
base.AddServices();
}
public override void Load()
{
base.Load();
}
}
}
这里有几件有趣的事情值得注意。CAB 中的 `ModuleLoader` 会找到并实例化派生自 `ModuleInit` 的类的实例。然后会调用重写的 `AddService` 和 `Load` 方法,让你有机会在 Shell 被创建和显示之前创建任何通用服务或进行初始化。CAB 会反射该类,并识别 `InjectionConstructor` 和 `ServiceDependency` 属性来实现这一点。模块加载顺序和服务细节超出了本文的范围。
MDI 工作区
为了完成 Shell,我们只需要一个工作区 - 在本例中是 MDI 工作区。很简单!
namespace SimpleMDIApp
{
public partial class SimpleMDIShellForm : Form
{
private WorkItem m_workItem;
private IWorkspace m_mdiWorkspace;
public SimpleMDIShellForm()
{
InitializeComponent();
}
[InjectionConstructor]
public SimpleMDIShellForm(WorkItem workItem,
IWorkItemTypeCatalogService workItemTypeCatalog) : this()
{
this.m_workItem = workItem;
m_mdiWorkspace = new MdiWorkspace(this);
}
}
}
这添加了一个注入构造函数(通过属性),CAB 通过反射找到它。它在 Shell 被创建和显示之前被调用。构造函数创建一个新的成员 MDI 工作区并存储 WorkItem。
我们现在差不多完成了 Shell 和 Workspace 的工作。我们只需要一种方法来关闭它并创建新的 `WorkItems`(视图/窗体)。因此,在 `SimpleMDIShellForm` 中添加以下命令处理程序来处理这个问题:
[CommandHandler("FileExit")]
public void OnFileExit(object sender, EventArgs e)
{
Close();
}
[CommandHandler("FileNewContact")]
public void OnFileNew(object sender, EventArgs e)
{
ContactWorkItem contactWorkItem =
m_workItem.WorkItems.AddNew<contactworkitem />();
contactWorkItem.Show(m_mdiWorkspace);
}
如你所见,命令处理程序是具有通用签名的方法,并用 `[CommandHandler]` 属性标记,该属性将命令标识为一个字符串。我们将要创建的 WorkItem 称为 `ContactWorkItem`,并在 OnFileNew 命令处理程序中引用它。我们将在下一节中创建它。
为了让这些方法被调用,我们需要触发命令 `FileExit` 和 `FileNewContact` - 我们将从 Shell 菜单栏执行此操作。修改 `SimpleMDIShellApp` 并添加以下两个方法:
protected override void AfterShellCreated()
{
base.AfterShellCreated();
// Register the UIExtensionSites
ToolStripMenuItem fileItem =
(ToolStripMenuItem)Shell.MainMenuStrip.Items["fileToolStripMenuItem"];
RootWorkItem.UIExtensionSites.RegisterSite("FileDropDown",
fileItem.DropDownItems);
AddMenuStripButton(RootWorkItem, "FileNewContact", "New");
AddMenuStripButton(RootWorkItem, "FileExit", "Exit");
}
private void AddMenuStripButton(WorkItem workItem, string commandName,
string text)
{
ToolStripButton button = new ToolStripButton();
button.Text = text;
button.ToolTipText = text;
// Add the button to the MainToolBar
workItem.UIExtensionSites["FileDropDown"].Add(button);
// Associate the Click event of the button to a command
workItem.Commands[commandName].AddInvoker(button, "Click");
}
`AfterShellCreated` 是 `FormShellApplication` 的一个重写方法,它(显而易见地)在 Shell 窗体创建后被 CAB 调用。我们的实现获取对根“文件”菜单项的引用,将其注册为“FileDropDown”,并使用我们自己的帮助方法 `AddMenuStripButton` 来关联添加一个菜单项和命令,该命令与菜单点击事件相关联。请注意命令的作用域。它们在此文件中定义和添加,但其接收命令处理程序位于 `SimpleMDIForm` 中。这里使用 `fileToolStripMenuItem` 字符串来引用 File 菜单名称属性。
有许多方法可以将菜单和命令添加到你的应用程序中。这里的方法是一种非常简单的演示方式。另一种方法是使用中心定义的常量来表示命令字符串,并在相关的 `WorkItem` 模块中执行 `UIExtensionSite` 注册和菜单/命令工作。你还可以根据用户角色和权限动态地从配置文件加载菜单。
WorkItem、View 和 Controller
现在 Shell 和应用程序已经完成,核心开发人员的工作也完成了。我们只需要创建一个 WorkItem 和 Form View 来在 MDI 工作区中显示 - 这是应用程序开发人员的任务。这代表了我们应用程序中的一个业务组件,在本例中,它只是一个允许用户输入新联系人详细信息的窗体。实际上,这可能也用于显示现有的联系人详细信息。
创建一个名为 *ContactView.cs* 的新 User Control 并向其添加一些控件。

查看生成的代码并将其修改为:
using System;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.CompositeUI.UIElements;
using Microsoft.Practices.ObjectBuilder;
namespace SimpleMDIApp
{
[SmartPart]
public partial class ContactView : UserControl
{
private ContactController controller;
public ContactView()
{
InitializeComponent();
}
[CreateNew]
public ContactController Controller
{
set { controller = value; }
}
}
}
标准生成的代码与 CAB 启用控件之间的区别在于类的 `[SmartPart]` 属性。SmartPart 本质上是 CAB 应用程序的视觉组件。此外,这个 `WorkItem` 将使用 MVC 模式,因此 `Controller` 属性被标记为 `[CreateNew]` 属性。CAB 使用依赖注入在运行时连接松耦合的组件,在这种情况下,View 持有其 `Controller` 的引用。
现在创建名为 ContactController.cs 的 Controller 类:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.CompositeUI;
namespace SimpleMDIApp
{
public class ContactController : Controller
{
public ContactWorkItem ContactWorkItem
{
get { return base.WorkItem as ContactWorkItem; }
}
}
}
在这个最基本的示例中,这个控制器只有一个返回其 WorkItem 的属性。
拼图的最后一块是 WorkItem 本身 - 这个类封装了各种视觉和非视觉组件的业务逻辑。创建一个名为 `ContactWorkItem` 的新类:
using System;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.Commands;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.CompositeUI.UIElements;
using Microsoft.Practices.CompositeUI.Utility;
using Microsoft.Practices.CompositeUI.WinForms;
using Microsoft.Practices.CompositeUI.EventBroker;
using System.Drawing;
namespace SimpleMDIApp
{
public class ContactWorkItem : WorkItem
{
private static Point pos = new Point(0, 0);
private ContactView m_contactView;
private WindowSmartPartInfo m_smartPart;
public void Show(IWorkspace parentWorkspace)
{
m_smartPart = new WindowSmartPartInfo();
m_smartPart.ControlBox = true;
m_smartPart.Location = pos;
m_smartPart.Title = "Untitled - Contact";
pos = Point.Add(pos, new Size(10, 10));
m_contactView = Items.AddNew<contactview />();
parentWorkspace.Show(m_contactView, m_smartPart);
this.Activate();
}
protected override void OnActivated()
{
base.OnActivated();
}
protected override void OnDeactivated()
{
base.OnDeactivated();
}
protected override void OnTerminated()
{
base.OnTerminated();
}
}
}
我们的公共 Show 方法负责创建视图并将其添加到 MDI 工作区。它实例化一个 `WindowSmartPartInfo` 对象,该对象管理 SmartPart 属性,并将新创建的(使用泛型)`ContactView` 添加到父工作区。在本例中,它是 `SimpleMDIShellForm` 中创建的 MDI 工作区。
Show 方法是从 `SimpleMDIShellForm` 中的 `OnFileNew` 事件处理程序调用的,以响应点击菜单按钮时触发的 `FileNewContact` 命令。
几点说明:- OnActivate 和 OnDeactivate CAB 可重写方法,在 WorkItem 获取或失去焦点时调用。
- OnTerminated 在(本例中)应用程序关闭时调用。这取决于你如何管理 WorkItems 的生命周期。
- 静态 Point 成员是为了让新的 View 不会仅仅隐藏之前创建的 View。
- 这使用了 MVC 模式,但 CAB 不强制使用。事实上,在使用 SCSF 时,采用的是 MVP。
你现在应该能够构建并运行这个令人兴奋(!)的应用程序了。请记住,这只是一个起点,用于理解 CAB 应用程序中组件的结构和组织 - 在本例中使用了 `MDIWorkspace`。它只涉及 CAB 提供的功能,但它是一个有用的学习练习。
示例代码
附件的示例应用程序与本文中的代码基本相同,但进行了一些补充。最主要的是使用配置文件来设置 Shell 窗体的菜单。有四个辅助类:- ShellItemsSection.cs
- UIElementBuilder.cs
- MenuItemElement.cs
- MenuItemElementCollection.cs
这些用于构建菜单,从 App.config 加载服务和模块。
此外,还有一些代码可以在 `ContactWorkItem` 处于活动状态时更新状态栏以显示联系人姓名。
快进到结尾,戴西
本文深入探讨了 CAB 的一些机制和组件,以及它们如何在 MDI GUI 应用程序中使用。希望它能帮助你理解如何使用松耦合的 GUI 组件,通过开发团队和结构化的方法构建复杂的 GUI 智能客户端。
一些有用的链接和参考文献。
- CAB msdn2.microsoft.com/en-us/library
- 泛型 msdn2.microsoft.com
- 反射 msdn2.microsoft.com/en-us/library/f7ykdhsy.aspx
- 依赖注入 dotnet.sys-con.com
- SCSF msdn.microsoft.com/practices
- GAT/GAX msdn.microsoft.com/vstudio/teamsystem/Workshop/gat
- MS 模式与实践 msdn.microsoft.com/practices/comm/smartclientblogs
- AOP www.c-sharpcorner.com