SCSF. Chapter 1. D. Simplest Application Walkthrough, Classic CAB
The Walkthrough 演练 QuickStart application, which comes with the baseline CAB download and also the May 2007 SCSF, is most developers' first introduction to CAB. Its step-by-step instructions 指导 allow CAB code to come out of your fingertips within the first few minutes of touching it. Even though some of its architectural choices have been superseded, I'm going to start my discussion of CAB applications here, so you can see where we started from and understand why these further evolutions have taken place. For the rest of this book, I will be using the SCSF dialect 行话. You will probably want to open the finished Walkthrough sample application and follow my discussion.
演练快速开始程序,伴随着基本CAB和2007年五月的SCSF,是大多数开发者的第一次CAB介绍。他的逐步指导使得你可以在几分钟之内开始编写CAB代码。即使某些架构选择被替代了,我将要开始讨论CAB程序,因此你可以看到我们从哪里开始,并且这些演变是如何产生的。在这本书接下来的部分,我要使用SCSF行话。你会想完成这个演练样例程序,然后参与到我的讨论中来。
The Main function is shown on the facing page. It's in the file ShellApplication.cs, which takes the place of Program.cs in a CAB application. The static method Main tells the program loader where to start.
主函数在文件ShellApplication.cs中,替代了CAB程序中的Program.cs文件。静态Main方法告诉程序从哪里开始加载。
The class ShellApplication represents the main outer application of the shell. It derives from the base class FormShellApplication, which is part of CAB. In its generic type list, we pass the classes that we want used for the shell's root WorkItem and the Shell Form.
ShellApplication类展现了Shell的外部程序,它派生自CAB的基础FromShellApplication类。在它的泛型参数列表中,我们传参shell's root WorkItem和Shell Form.
For the former, we have a class called ShellWorkItem, which you'll find in the file ShellWorkItem.cs. In this application, this class doesn't do anything at all (other than) 除了 sit there and exist as the CAB application's root WorkItem. This pattern of deriving additional classes from WorkItem is not used today, as I discuss in Chapter 3. Today, we use plain old WorkItem for the class of the root WorkItem. But it was prevalent for development of CAB and the first six months or so of its released lifetime, so you will see it in many older applications and documentation.
对于前者,我们有一个名叫ShellWorkItem的类,定义在ShellWorkItem.cs中。在这个程序中,这个类不做任何事情,只是作为CAB程序的root workItem。这种派生于workItem的模式已经不再被使用,在Chapter 3中讨论过。今天,我们使用plain old WorkItem
The Shell Form is the main window that the shell application creates. This is the place where you put your workspaces and user interface items.
Shell Form是Shell程序创建的主窗口。你的workspaces和user interface 项就放在这里。
When the application starts running, the CAB framework creates an instance of the root work item and an instance of the Shell Form. Then it loads the modules called out in the ProgramCatalog.xml configuration file, which is the next thing we'll look at (see Figure 1-8).
当程序开始运行,CAB框架创建了一个root work item的实例和一个Shell Form的实例。然后加载了ProgramCatalog.xml配置文件中的modules,接下来我们就要讨论这部分。
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.CompositeUI.WinForms;
namespace ShellApplication
{
// This class is that base of the application. You pass it
// the types of the root WorkItem and the root Windows Form.
public class ShellApplication : FormShellApplication<ShellWorkItem,
ShellForm>
{
// Here's the program's entry point
[STAThread]
static void Main()
{
// Create an instance of the application class and call
// its Run method to start it
new ShellApplication().Run();
}
}
}
Figure 1-8. Architectural diagram of CAB Walkthrough sample.

During program startup, the shell application loads the modules that are specified in the startup file ProfileCatalog.xml. Each <ModuleInfo> element specifies a module file to load into the CAB application. The file looks like this:
在程序启动期间,shell程序加载了在启动文件ProfileCatalog.xml中指定的modules.每个配置文件中的<ModuleInfo>元素指定了一个module文件加载到CAB程序中。配置文件就像以下形式。
<?xml version="1.0" encoding="utf-8" ?>
<SolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile" >
<Modules>
<ModuleInfo AssemblyFile="MyModule.dll" />
</Modules>
</SolutionProfile>
When a module is loaded into the shell application's address space, the loader looks for classes that derive from the CAB base class ModuleInit. Such a class is shown on the facing page. This is the place where you put initialization code.
当module被加载到程序地址空间,加载器找到从CAB基类ModuleInit中派生的类。下面写了一个这样的类。你的初始代码就写在这里。
The first thing you see when you look at the class is a property called ParentWorkItem, which is of class WorkItem. It has the curious property of being write-only; that is, it can be set from outside but not read. It is marked with a [ServiceDependency] attribute. This causes a dependency injection, an important part of the loose coupling strategy of CAB. The module loader in CAB sees the attribute and knows that it means that the module needs the specified type—in this case, WorkItem. The term Service is ambiguous here. It's used in the CAB meaning of "any class accessed through this loose coupling mechanism," not specifically the Services collection discussed in Chapter 2. So when the module is loaded, the CAB framework sees that it depends on the WorkItem type, which means the WorkItem that caused the module to be loaded—in this case, the root WorkItem. It therefore sets the property with a reference to that work item. This reminds me somewhat of the old IConnectionPoint interface in ActiveX controls. It also allows a type viewer application to examine the object and see what services it depends on.
在这个类中你首先需要关注的是一个名叫ParentWorkItem的属性,类型为WorkItem。它有一个奇怪的只写属性;就是说,它可以在外部被设置,但是不可读。它被标记为[ServiceDependency]特征。这产生了依赖注入,一个CAB松耦合策略的主要部分。CAB中的module加载器读取这个特征,然后得知这意味着这个module需要指定类型,workItem。这项条款是引起歧义的。它在CAB中使用意味着:“任何类都适用松耦合机制”。当module加载时,CAB框架得知它依赖于workItem类型,这意味着WorkItem使得module被加载--在这个案例中,是root workitem。因此设置该属性引用了workItem。这让我有点想起了ActiveX中的old IConnectionPoint接口。它允许类型查看器程序去检查这个对象以及服务的依赖。
Once the module is loaded in the process's address space, the framework calls its Load method, which we override to make a place for our module's initialization code. First, we call the base class as usual. Next, we create a child work item and place it into the WorkItems collection (which means child WorkItems) of the parent WorkItem that we got from the injection. This technique creates the WorkItems chain that we will see in Chapter 3. This sample program again uses the pattern of deriving a specialized class from WorkItem—in this case, MyWorkItem. As we will see in Chapter 3, this pattern is no longer used.
一旦module被加载到程序地址空间中,框架调用加载的方法,那些我们在module初始化代码中重写的方法。首先,我们像以往一样,我们创建一个子workItem并把它放到workItem集合中,那些我们通过注入获取的parent WorkItem。这项技术创建了我们在第三章中看到的workItem链。该样例程序再次使用了从指定workItem派生的模式。在文中为MyWorkItem。我们将在第三章中看到,该模式不再被使用。
In the sample application, the child WorkItem wants to display a SmartPart, so it needs to know the workspace that it's supposed to use for this purpose. In this example, our initialization code fetches the workspace from the parent WorkItem by means of a string name (again, loose coupling) and passes it into the child WorkItem in its Run method. This method is not a standard part of the WorkItem; instead, it's been added for this purpose. We don't usually follow this pattern any more. As we'll see in Chapter 4, the child WorkItems will generally query for their workspaces directly.
在样例程序中,子WorkItem想展示一个SmartPart,因此它需要知道为此目的而使用的workspace。在这个例子中,我们的初始化代码在Run方法中通过parent workItem取得workspace并把它传递给子workItem。这个方法并不是workItem的标准部件;反而,它是为此目的才添加的。我们通常不再遵循该模式。我们将在第四章中看到,子workItem直接查找workspaces。
public class MyModuleInit: ModuleInit
{
private WorkItem parentWorkItem;
// This write-only property is marked as being dependent on the
// ParentWorkItem service. This causes the CAB framework to inject
// that into the property at creation time.
[ServiceDependency]
public WorkItem ParentWorkItem
{
set { parentWorkItem = value; }
}
// We override the Load method to perform the module's
// initialization
public override void Load()
{
// Call the base class
base.Load();
// Create a new child work item and place it into the parent
// WorkItems collection.
MyWorkItem myWorkItem =
parentWorkItem.WorkItems.AddNew<MyWorkItem>();
// Call the new work item's Run method, thus starting it up.
// In this design, the Run method requires the workspace in which
// it shows its user interface.
myWorkItem.Run(parentWorkItem.Workspaces["tabWorkspace1"]);
}
}
Our WorkItem code is shown on the facing page. The ModuleInit calls the Run method of the WorkItem, passing it a Workspace, which is a little confusing. The base class has a Run method, which fires an event and calls OnRunStarted, but it doesn't take a parameter as this one does. This sample has overloaded the Run( ) method to accept an IWorkspace interface. Most of the other examples use a Show( ) method. The modern way of doing this is to place all of this logic into a Controller object, associated with the WorkItem at creation time, as discussed in Chapter 3.
我们的WorkItem代码展示在下面。ModuleInit启动WorkItem的Run方法,传递了一个Workspace,这有点令人困惑。基类有一个Run方法,它触发一个事件并且启动OnRunStarted方法,但它并不带一个参数。这个例子重写了Run()方法并集成了IWorkSpace接口。大部分其他例子使用了一个Show()方法。现在流行的方式是把这些逻辑放在Controller对象中,在workItem创建的时候就产生关联,这在第三章中讨论过。
This example uses the Module – View – Presenter (MVP) architecture, which I discuss in section 4. For now, look at the Run method and note that it creates a thing called a view and a thing called a presenter. Note that, after (or sometimes during) the creation process, it adds these new things to its own Items collection. There's no reason that the WorkItem couldn't retain references to these objects in member variables, and some of them indeed will do so. However, adding them to the Items collection is the first time the CAB framework gets a look at them—for example, to place them in other collections or to perform dependency injection. To work with CAB, they need to first be shown to CAB, as baby Mowgli had to be shown to the Seeonee wolf pack in Rudyard Kipling's Jungle Book. ("Look well, O Geeks.")
这个例子用Module - View - Presenter(MVP)架构,我在第四节中讨论过。目前,看看Run方法并且注意它创建了一个view和一个presenter。注意到这点,在创建过程之后,就把新东西都加到了项目集合中。没有理由WorkItem不能保持对这些对象在成员变量中的引用,并且部分确实这样做了。无论如何,把它们加到集合中是CAB框架第一察觉到它们--例如,把它们放在其他集合中或执行依赖注入。与CAB协作,需要第一次被展现给CAB。
The WorkItem code looks like this:
public class MyWorkItem: WorkItem
{
// This method is called at startup time by
// the ModuleInit code. Production applications tend
// to call this method Show( ) instead.
public void Run(IWorkspace tabWorkspace)
{
// Create the new view control class that we use to display this
// information. Add it to our Items collection
IMyView view = this.Items.AddNew<MyView>();
// Create the presenter object that will manipulate the view
// Add it to out Items collection as well.
MyPresenter presenter = new MyPresenter(view);
this.Items.Add(presenter);
// Tell the workspace to show that view
tabWorkspace.Show(view);
}
}
This sample uses the Model – View – Presenter design pattern, shown in the diagram in Figure 1-9. You can find it discussed in excruciating detail by Martin Fowler online at http://www.martinfowler.com/eaaDev/ModelViewPresenter.html.
Figure 1-9. Model-View-Presenter patterns used in a CAB application.
The basic idea is to implement a three-tier architecture in the client application. The WorkItem is the model referred to in the diagram. It contains the program's data, the state with which the user is concerned. The business logic lives in the presenter, here represented by the MyPresenter class we saw being created on the preceding pages. This class takes the model's state, thinks about it, and modifies the actual presentation done by the view. The view is a new object of class MyView. It is a Windows Forms control that is a SmartPart. It contains the actual controls that display data to the user.
基本的思想是在客户端实现这个three-tier架构,WorkItem是表中涉及到的model。它包含程序的数据,用户有关的状态。业务逻辑存在于presenter中,这里我们之前创建的页面是由MyPresenter类所展现的。这个类包含model的状态,想想吧,通过view来修改实际的展示。view是一个名叫MyView的新对象。这个一个SmartPart也是一个winForm控件。它包含了展示数据给用户的实际控件。
We first have to define an interface to govern the communication between the presenter and the view. In this example, it's the IMyView interface shown at the top of the facing page. It contains one property of type string, called Message, and one event, called Load. The presenter doesn't need or want to know how these are implemented in the view.
我们首先需要定义一个接口去管理presenter和view之间的通信。在例子中,是IMyView接口。它包含了一个string类型的属性,叫Message,和一个叫Load的事件。presenter不需要知道怎样去实现这个view。
If you didn't have this separate layer of the presenter, you would have the model interacting directly with the view. That is not an evil thing in and of itself. But this means that the view and the model are directly coupled, which makes it harder to develop them.
如果你不想分离presenter层,你需要使model与view直接相互影响。这并不是一件糟糕的事情,但是这意味着view和model是直接耦合,并且更难开发。
The presenter's code is shown on the facing page. At the time of its construction, it is given by the work item that constructs the view that it is to govern. It saves a reference to this view and hooks up an event handler for the view's Load event. In that handler code, it sets the view's Message property when it receives the event.
presenter的代码在下面。在构造函数时,它被workItem传递,构造并管理view。它保存了一个view的引用并且为view的加载世界挂钩了一个事件句柄。当接受到事件时会设置事件属性。
First, we define the interface to be used between the presenter and its views. In the case of the sample, it looks like this:
第一,我们定义了presenter与views之间的接口。在这个例子中,如下:
Code View:
public interface IMyView
{
event EventHandler Load;
string Message { get; set; }
}
The presenter code looks like this:
public class MyPresenter
{
IMyView view;
// When this presenter is constructed, we get passed
// the view on which we are to display public
MyPresenter(IMyView view)
{
// Save a reference for future use this.view = view;
// Hook up a handler to the event fired by the view
view.Load += new EventHandler(view_Load);
}
// The control fired this event. Respond to it.
void view_Load(object sender, EventArgs e)
{
view.Message = "Hello World from a Module";
}
}
Finally, we reach the view itself. The view is meant to be a pure presentation object. It represents one particular way of looking at (and possibly modifying) data. If you remember the document-view architecture from the MFC, you'll have a decent mental model here. As an application designer might choose to present several different views of a document (a zoomed-out page print view, a zoomed-in details view, etc.), so an application designer here may choose to present views in any way that she thinks will make the user's life easier.
最后,我们到了view本身。view意味着一个纯的展示对象。它展示了一个特定的查看数据的方法。如果你记得MFC中的document-view架构,你会有一个正确的思维概念。作为一个程序设计者可能选择展示多种不同的文档。因此一个程序设计者可能选择各种使用户方便的展现方式。
The view's code is shown on the facing page. Note that it derives from the Windows Forms base class UserControl and implements the interface IMyView. You can see the Message property implemented in code beneath it. The Load event is not shown in the code. The UserControl base class's Load event is considered the implementation of this interface member.
view的代码在下面。注意它是派生于winform基础用户控件类并实现了IMyView接口。你可以看到Message属性在代码中实现了。加载事件没有在代码中呈现。用户控件基础类实现了接口成员。
Looking at the property Message, you can see that it sets the property of a label that the UserControl contains. The hiding of this implementation is, to my mind, the main benefit of the MVP architecture. The presenter doesn't know or care how this feature is implemented. If the user interface design requires moving things around such that the message appears on Label 2 instead of Label 1, that doesn't affect the presenter in any way.
看下Message属性,你可以看到它设置了UserControl包含的一个label的属性。这种隐藏的实现,在我看来,就是MVP架构的主要优点。presenter不知道或者不关心这特点是如何实现的。如果用户接口设计需要把例如信息显示从label1移动到label2,这不会在任何情况下影响presenter。
The key part of this design from a CAB standpoint is the presence of the [SmartPart] attribute. It tells CAB to treat it as SmartPart and thus to manipulate 操作 it in accordance 一致 with the commands of the workspace in which it appears.
CAB的设计观点的关键部分是[SmartPart]特征的存在。它告诉CAB作为SmartPart去处理,并且因此像workspace的命令呈现的那样去操作它。
[SmartPart]
public partial class MyView : UserControl, IMyView
{
public MyView()
{
InitializeComponent();
}
public string Message
{
get
{
return this.label1.Text;
}
set
{
this.label1.Text = value;
}
}
}


浙公网安备 33010602011771号