Freigeben über


Useful tips when using Patterns and Practices Component Application Block (CAB)

The CAB is an excellent resource for application development.  With the proper techniques, it leads to clean, separated logic and definite, manageable layers of code.  However, there is one problem: little documentation.  With the Smart Client Factory, more example code has been added, and there is more guidance.  I thought that I would make a running list of the little problems I have found and solutions that I discovered.  I hope these are useful, because I have not seen any other listing with answers to these "basic" scenarios that seem necessary.  The documentation would be better with an extensive FAQ regarding issues like these.

1.  How to create a modal dialog:

Suppose you have a workSpace already defined (like a TabWorkSpace).  Inside of a WorkItem, in the Run(IWorkspace workSpace) method, create a new workspace of type WindowWorkSpace and using a WindowWorkSpaceInfo, set the value for modal to be true.  When you call the Show method, you'll see a new, modal window appear:

windowWorkSpace.Show(smartPart, workSpaceInfo);

2.  How to close a window:

Calling Deactivate() on the WorkItem only makes it not the active work item.  You need to do this from a WorkSpace method, assuming you only have one SmartPart:

this.WorkSpaces[ myWorkSpaceName ].Close( this.Items[ mySmartPartName ] );

Once all SmartParts are closed, the Workspace closes and then the UI window associated will go away if there are no more work items.

3.  How to correctly use the Event notification mechanism

You need to make sure that you use prototypes that follow this general pattern:

public

class TestStatus : EventArgs

{

}

public

class Test

{

[EventPublication("event://Status", PublicationScope.WorkItem)]

public event EventHandler<Status> StatusNotification;

}

[

EventSubscription("event://Status", Thread=ThreadOption.UserInterface)]

public void myClass_NotifyOnStatusChange(object sender, Status test)

{

Otherwise the event system will complain if the type of the event arguments involved to not have the correct parent class types.

4.  Serializing a set of work actions

Normally a UI has a flow, where the user does a task.  First selecting files, then clicking a button and then a dialog pops up.  Well, how do I chaing work-items together to make a coherent use-case for the user?  That's easy: using a WorkItem, create a new work item that gets added to the WorkItems property.  When the child's Run(IWorkSpace childWorkSpace) method is called, the parent WorkItem gets deactivated and the child "gains the focus" of the application.

class MainWorkItem : WorkItem

{

public void Run(IWorkspace tabWorkSpace)

{

currentWorkSpace = tabWorkSpace;

Login.

LoginWorkItem loginWorkItem = Parent.WorkItems.AddNew<LoginWorkItem>();

loginWorkItem.Run(currentWorkSpace);

if (loginWorkItem.LoginInSuccess == false)

{

loginWorkItem.Terminate();

    return;

}

SelectorWorkItem mainWorkItem = Parent.WorkItems.AddNew<SelectorWorkItem>();

mainWorkItem.Run(currentWorkSpace);

}

5.  Making data available from one WorkItem to its aggregate members

The members of WorkItems' collections (called Items[]) can receive references from their parent work items using declarative or programtic methods.  These members can be presenters, smartparts or any other object that is related to the WorkItem.  The items in the State or Items collections are available to the children WorkItems using the following declarative syntax, and the values are applied when the child objects are added to the Item collection (via Add() or AddNew())

[State("myStateName")]

public String StateValue

{  set { this.myVariable = value; }   get { return this.myVariable; }

 

[Dependency]

public MyClass MyClassInstanceProp

{

 set { this.myVariable = value; }  get{ return this.myVariable; }

}

When either of theses methods are declared, they are used at run time to fill in the values of the Properties with the values contained in the corresponding objects help by the parent's WorkItem. 

This same technique works to pass information from WorkItems to the instances that are contained in its WorkItems collection, when creating a child WorkItem from inside the context of the parent WorkItem.  Note that the Items[] and State[] collection is not shared amongst WorkItem instances, but is used by the ObjectBuilder to set the appropriate properties.

6.  Sharing data amongst all WorkItems, or pushing data up from a child WorkItem to a parent

 When you create an object instance and add it to the Services collection of the parent WorkItem, it will be available to all the WorkItems below that WorkItem.  This provides a convenient method of passing data back up from a child WorkItem to a more upper layer, by using a stateful Data Transfer Object.

You can automatically get a reference to this service by using the following declarative example:

[

ServiceDependency]

public IWorkItemTypeCatalogService myWorkItemCatalog

{

set { myCatalogService = value; }

}

7.  Using a specific instance of an object to meet a dependency on an abstract interface

If we register a concrete object that implements an interface, we would like to use that instance when a specific dependency on that interface is specified. By default, since the types are not the same, the existing object will not be used. That is, as below, the ObjectBuilder will look for an object of type ITestSelectorView, and find none, even if an instance of a specific object exists that satisfies this requirement. In this case, the name for for the instance is just for convenience, and is not needed.

[

DependencyAttribute(NotPresentBehavior= NotPresentBehavior.Throw, Name= "InstanceName")]

public ITestSelectorView ISelectorView

{ set { myView = value;} }

But, if we add this code to the WorkItem that creates the object, we can make sure it picks the correct instance, which implements the interface:

this

.InnerLocator.Add(new DependencyResolutionLocatorKey(typeof(ITestSelectorView), "InstanceName"), concreteObjectInstance);

This is very useful for making use of concrete instances to fulfill abstract requirements, and it is not overly complicated.

8.  Deactivating one WorkItem so that a new WorkItem will appear

If you activate the SmartParts in a different WorkItem, the current active WorkItem will be made inactive.  This is the primary way in which a use case works, from one WorkItem to the next.  To stop using a WorkItem and make it disappear (useful in the case of a modal WindowWorkSpace) you can call the Terminate() method of the related WorkItem.

9.  Using the State collection sparingly (to avoid writing Property accessors that cast the results accordingly)

You can encapsulate the WorkItem state within a separate class which can then be used to read and write important state variables from (without having to directly use the State collection).  This class then can be written to the State collection to make use of the StatePersistence service, without the overhead of having to use the State collection for every instance data member.    Since the presenter contains the presentation state, the WorkItem should contain the business logic state.

The alternative is to create a property that returns the correct type once the object instance is retrieved from the State collection.  This can get rather annoying quickly, and collapsing the related state into a single class is a more clean alternative.

10.  Getting more ideas for using the correct patterns in your design .....

Look at the sample application, especially in the Smart Client package from the Patterns and Practices group, and look for more rich sample applications that illustrate different models or outlines which can make for consistent designs.  The best way implement an application is to have all of the patterns established so that the implementation will be internally consistent.

This is one example: https://www.ideablade.com/cabana/cabana.html?=msp&p