Export (0) Print
Expand All

Upgrading from the Composite UI Application Block

This topic helps Composite UI Application Block developers familiarize themselves with the Composite Application Library, included with the Composite Application Guidance for WPF and Silverlight, by comparing the main components of both libraries.

Contents

Introduction

If you are a Composite UI Application Block developer and you want to build a pure Windows Presentation Foundation (WPF) or Silverlight application, you will notice that the Composite UI Application Block was not built to take advantage of WPF or Silverlight's core functionality because WPF and Silverlight did not exist at the time the application block was developed. For this reason, the Composite Application Library was created. The library helps you create complex WPF or Silverlight applications. This complexity occurs when there are multiple independently evolving pieces in an application that need to work together. The Composite Application Library has the same core concepts as the Composite UI Application Block such as modularity, user interface (UI) composition, services, dependency injection, and event brokering. These concepts are essential for building composite applications; however, the implementation of these core concepts differs between the Composite UI Application Block and the Composite Application Library. This topic explores the main differences.

You may be familiar with the Composite UI Application Block, sometimes known as CAB, and the Composite Application Library, sometimes known as Prism. Because the official names are so similar, this topic refers to the Composite UI Application Block as "the Block" and refers to the Composite Application Library as "the Library."

Intended Audience

This information is intended for software developers and architects who are familiar with the Block and who are interested in developing composite applications that take advantage of WPF or Silverlight capabilities using the Library.

Why Migrate from the Block to the Library?

The Library is not a new version of the Block. It is a new set of libraries and guidance, built from the ground up that is designed to help you develop new WPF or Silverlight composite applications. Although it is not a new version of the Block, it uses the same core concepts, such as modularity, user interface (UI) composition, services, dependency injection, and event brokering. These concepts are essential for building composite applications and the Library uses them; however, the implementation differs from the Block in several ways:

  • The Library incorporates customer feedback about the Block. Over the years, the patterns & practices team has received great feedback, positive and negative, about the Block implementation. Some of the negative feedback includes that it is too heavy, too complicated, too tightly coupled, and too hard to get going. Additionally, customers expressed a need for an approach that allows incremental adoption of the library and to work with existing libraries. The patterns & practices team determined that the best way to address the concerns and tackle the new ideas was with a clean break.
  • The Block was not built to support WPF or Silverlight. Although you can port the Block to WPF or Silverlight, the Block was not built to take advantage of WPF or Silverlight's core functionality. In many instances, the Block introduced mechanisms that are now native to WPF. For example, WPF introduces attached properties that aid in UI composition in a lighter-weight way than what existed in the application block. WPF and Silverlight are also different paradigms than Windows Forms. There are many flexible ways to compose your UI over the traditional way of using controls. WPF and Silverlight also introduce the idea of using templates to control the way your UI renders.
  • The Block relies on the Windows Forms development experience. The Block development scenarios depend on the tooling and productivity experience provided for Windows Forms by the Microsoft Visual Studio development system. Currently, the WPF and Silverlight developer experience is a very different paradigm. In Visual Studio 2008, a good portion of WPF or Silverlight application development still requires manually working with XAML. The built-in Visual Studio designers provide only a small subset of the capabilities Windows Forms developers are accustomed to. Tools such as Microsoft Expression Blend offer some of these capabilities, but they are not targeted for developers and do not integrate into the development environment. This experience will be improved in future versions of the platform. Based on the current state of WPF and Silverlight development, the transition from Windows Forms to WPF or Silverlight currently requires substantial effort and developers face a steep learning curve. For these reasons, this Library is optimized for new composite application development in WPF or Silverlight.

Moving from the Block to the Composite Application Library

The Block and the Library are targeted to build composite applications, so they have similar core concepts. These core concepts have different implementations. An important difference between both libraries is the composition container, as illustrated in Figure 1. The Block includes its own implementation for the composition container through a WorkItem (container) and the ObjectBuilder for dependency injection. However, the Library lets you use the container that you prefer (the container provided by default is the Unity Application Block container).

Ff648430.ffb3cd08-2e8a-4fbc-8d53-57155ee9156e(en-us,PandP.10).png

Figure 1

Comparing the Block and the Library implementation of core concepts

Application Structure

Both the Library and Block applications have a similar solution structure. The solutions are composed of a shell project, which is the startup project, and contains the shell view that defines the common layout; several modules that add views, services, and other application functionalities; and optionally, infrastructure projects to share common artifacts and constants definitions.

Figure 2 shows the equivalent elements between the WPFQuickStart shipped with the Block in the Smart Client Software Factory and the WPFQuickstart rewritten to use the Library.

Ff648430.5c6913c8-2cdf-43f3-8ee7-6ba0c4cadd66(en-us,PandP.10).png

Figure 2

Block versus Library solutions
Ff648430.note(en-us,PandP.10).gifNote:
For more information about creating a solution that uses the Block, see How to: Create Smart Client Solutions.
For more information about creating a solution that uses the Library, see How to: Create a Solution Using the Composite Application Library.

Application Startup

This section compares how applications created using the Block and the Library are initialized:

  • Block. In a Block application, the application class should inherit from the FormShellApplication class to set up the environment, create the shell, and start the application. The Run method in the FormShellApplication class performs several initialization and preparation tasks, including loading the standard services provided in the application block.
  • Library. In an application that uses the Library, you use a bootstrapper class that is in charge of initializing the application. The Library includes a default abstract UnityBootstrapper class. Typically, you use this base class to create a derived bootstrapper class for an application that uses a Unity container. Many of the methods on the UnityBootstrapper class are virtual methods. You should override these methods as appropriate in your own custom bootstrapper implementation. If you are using a container other than Unity, you should write your own container-specific bootstrapper. Typically, the bootstrapper initializes the container used for dependency injection, registers services and region adapters, creates and shows the shell window, and loads modules.

The code in the following table shows the application startup code for both libraries.

Block

Library

public class CabApplication :
FormShellApplication<WorkItem, Shell>
{
    [STAThread]
    static void Main()
    {
        new CabApplication().Run();
    }
}
internal class Bootstrapper : UnityBootstrapper
{
    protected override IModuleCatalog GetModuleCatalog()
    {
        ModuleCatalog catalog = new ModuleCatalog()
            .AddModule(typeof(MyModule));
        return catalog;
    }

    protected override DependencyObject CreateShell()
    {
        Shell shell = new Shell();
        shell.Show();

        return shell;
    }
}

Ff648430.note(en-us,PandP.10).gifNote:
The preceding Library startup code is an example of a basic implementation of the Bootstrapper class. This class can be customized in several ways to suit your application needs, such as by specifying a different container, a different logger, or another module loading mechanism.
For more information about the Library bootstrapper, see the Bootstrapper technical concept.

Loading Modules

A module is a logical unit in your application. Modules promote separation of concerns in your application and assist with implementing a modularity design. In composite applications, modules are defined in such a way that they can be located and loaded by the application at run time. They help reduce the friction of maintaining, adding, and removing system functionality.

This section compares how modules can be loaded and the module-loading mechanisms provided by the Block and the Library:

  • Block. The Block dynamically loads the application modules, usually by reading the ProfileCatalog.xml file. It does not provide a way to load a module statically. There are cases where decoupling modules from the shell is not a requirement and loading the modules statically can help reduce complexity, simplify the application deployment, and simplify debugging.
  • Library. The Library lets you choose the way modules are loaded. In the Library, the process of loading modules consists of creating the module catalog, retrieving the modules if they are remote, loading assemblies into the application domain, and initializing the modules, as follows:
    • Building the module catalog. You can build the module catalog in the following ways:
      • Code. This specifies the modules to load directly from code.
      • XAML. This specifies the modules to load in a XAML file.
      • Configuration module catalog. This specifies the modules to load in a configuration file.
      • Directory module catalog. This examines a folder for the modules to load.
    • Loading the modules. This is the process of retrieving the module locally and loading into the application domain. The timing of module retrieval and loading depends on whether the module is loaded on-demand or when the application starts. Any module that is not marked OnDemand is retrieved and loaded as soon as possible. OnDemand modules are only retrieved and loaded when the application explicitly requests those applications to be retrieved and loaded.
    • Initializing the modules. Modules are initialized as soon as they are available.

Where did the WorkItems Go?

In the Block, a WorkItem is a controller and a run-time container for components—objects and services—that work together to fulfill a use case. The Library does not directly support the concept of a work item. This section describes how to address the scenario that WorkItem addresses using the Library and the Unity container:

  • Block. Applications built using the Block have at least one WorkItem. WorkItems are typically thought of use-case controllers providing the user interface processes necessary for completing a use case and putting all the required parts together for doing so.
  • Library. There is no concept of WorkItem in the Library. The reasons for this are the following:
    • WorkItems combined many responsibilities, such as controller and dependency injection, into one class; in the Library, it was preferred not to have a single class with this set of responsibilities.
    • When developing simple applications and on most scenarios, there is no need of scoped containers, such as WorkItems; therefore, every component can be registered on a single root container.
    • If you need to develop complex applications or you want to use an approach similar to the one used in the Block, it is possible to do it with the provided infrastructure.

    In the Library, container functionality is expected to be a separate responsibility handled by a discrete service, unlike with WorkItem. To support a Use Case controller scenario, you need a container, such as Unity, capable of supporting hierarchies. This means that if you use a hierarchy-supporting container, you can develop a Use Case controller that stores its associated state, services, and views in a child container.

    Ff648430.note(en-us,PandP.10).gifNote:
    For information about how to implement the WorkItems functionality in the Library, see Emulating WorkItems Using Controllers with Scoped Containers.

Workspaces and Regions

UI composition is usually a challenge in composite applications, where different modules contribute views to a common shell. Both libraries provide mechanisms to address this challenge, which are described in this section:

  • Block. In Block applications, workspaces are components that encapsulate a particular visual way of displaying controls and SmartParts. Every module is able to dynamically add or remove views at run time in a loosely coupled way by using the Workspaces collection of the WorkItem and specifying the name of the workspace where the view will be displayed.
  • Library. Regions in the Library are used in a similar way. To specify a WPF or Silverlight control as a region, you need to add the RegionManager.RegionName attribute to it. This attribute is an attached property that indicates that a region has to be created and associated with a control when the view is built. The attribute value provides the name for that region.

    The following mapping can be made between the Block workspaces and the Library regions:

    • DeckWorkspace workspace maps to ContentControl region. Both can show a single active view at the same time.
    • TabWorkspace workspace maps to a TabControl region. Both are based on the TabControl control and can show views inside a tabbed page.
    • The MdiWorkspace and ZoneWorkspace workspaces can be mapped to an ItemsControl region, considering that they can show more than one active view at the same time. However, these workspaces have substantial differences with the ItemControl region:
      • The MdiWorkspace shows each view in a multiple document interface (MDI) child form.
      • The ZoneWorkspace displays multiple views in a tiled layout.
        Ff648430.note(en-us,PandP.10).gifNote:
        For more information about the workspaces available in the Block, see Design of Workspaces.

The code in Figure 3 shows how workspaces are defined in the Block and the code in Figure 4 shows how regions are defined in the Library.

Ff648430.701c0d0d-1537-4686-a65f-e25a7e260244(en-us,PandP.10).png

Figure 3

Defining workspaces in the Block

Ff648430.9f36540b-3afb-4c5b-9e1f-ac068e183306(en-us,PandP.10).png

Figure 4

Defining regions in the Library

To access regions, the Library provides the RegionManager class. The RegionManager is responsible for maintaining a collection of regions and creating new regions for WPF controls. The RegionManager finds an adapter mapped to a WPF or Silverlight control and associates a new region to that control. The Library comes with support for ItemsControl, ContentControl, and Selector-derived controls.

The code in the following table shows how to show a view in the Block using the Workspaces collection, compared to the RegionManager of the Library.

Block

Library

// In the module WorkItem
private void AddViews()
{
    IWorkspace workspace = Workspaces["MainWorkspace"];
    workspace.Show(SmartParts.AddNew<MyView>());
}
// In the module class
IRegionManager regionManager = container.Resolve<IRegionManager>();

IRegion mainRegion = regionManager.Regions["MainRegion"];
mainRegion.Add(container.Resolve<MyView>());

Ff648430.note(en-us,PandP.10).gifNote:
For more information about regions and UI composition in the Library, see the UI Composition technical concept.

Providing Metadata to a View

This section compares how metadata can be used to customize a view:

  • Block. To specify advanced/custom properties for a SmartPart in the Block (to determine how it will be displayed), you have to create an appropriate SmartPartInfo instance for the workspace you are using and apply it.
  • Library. The Library does not have a similar approach to provide metadata to a view. That approach is not flexible enough for WPF or Silverlight. With WPF and Silverlight, you can create attractive, advanced, and effective user interfaces. You can easily use WPF bindings to provide the metadata for the view that gets placed into a region.

    For example, you can use the TabSmartPartInfo component, provided by the Block, to specify whether the tab page where the SmartPart will be shown should be activated (the ActivateTab property), as well as its location related to other tabs (the Position property). This component inherits from the generic SmartPartInfo class, whose Title property determines the text on the tab for this page.

    In the Library, to achieve the same result, you can use WPF or Silverlight bindings to pass metadata to the view through its DataContext property. In this way, you can set the text on the tab for the item as well as other properties. An example of this can be seen in the View Injection UI Composition QuickStart, where the metadata is passed to the view through its DataContext property in the form of the ProjectListPresentationModel presentation model, which holds the HeaderInfo property. This metadata is consumed on the EmployeeDetailsView view where the HeaderInfo property of the model is bound to the Header property of the TabItem control.

Presentation Patterns

When building composite applications, presentation design patterns such as Model-View-Presenter (MVP) and Model-View-Controller (MVC) are often used to separate the UI logic from the UI layout and presentation. This section describes which Presentation Model patterns and variants are best-suited to each library:

  • Block. The Block and the Smart Client Software Factory usually use the Supervising Presenter variant of the Model-View-Presenter pattern to implement the views. The main goal of this pattern is to remove the most complex logic from the view that requires a major unit testing effort. This complex logic is moved to a Presenter class, which improves testability.
  • Library. The Library can use many presentation patterns, but because WPF has extensive data binding, it is well-suited to the Supervising Presenter and the Presentation Model variants. The Presentation Model differs from the Supervising Presenter on its approach of the Model-View-Presenter, by combining the "Model" and the "Presenter" into a single class. This single Presentation-Model class contains the state of the view and implements its behavior. Compared to the Supervising Presenter, the Presentation Model is more complex because it also implements the state of the view; however, a simpler aspect of this model is that the state synchronization is almost entirely responsible for the view. Instead of a Presenter class directing the view to change its state, the Presentation Model simply changes its own state depending on data binding (or equivalent) to update the view accordingly. By using this approach, you can take advantage of all the new features of WPF data binding.

Registering Services

A service is a supporting class that provides functionality to other components in a loosely coupled way through an interface. A service is typically a singleton class. This section describes how to register services in the Composite UI Application Library and the Library:

  • Block. Services can be global or local to a particular module depending on the WorkItem where they are created. There are many ways to register services in the Block; the following are some examples:
    • You can register services in the App.config file.
    • You can register services by marking the service class with the [Service] attribute.
    • You can programmatically register services using the Services collection of the WorkItem.
  • Library. The Library itself is container agnostic, so it has no concept of global or local services because service scoping should be handled by the container. The Library is not involved in registering types; it only has a façade for resolving types internally. If you are using the Unity container, supported by the Library, you can use Unity hierarchical containers to have service scoping. In Unity, services can be registered from the App.config file and programmatically in either of the following two places:
    • Bootstrapper class. Usually infrastructure services, such as those used for application startup, are registered or services that are consumed by multiple modules.
    • Module classes. Usually services consumed by a particular module are registered, but services consumed by other modules can also be registered.

The code in the following table compares the Block with the Library using Unity, to show the way that services are registered in both libraries.

Block

Library

// Register an instance.
ExampleService service = new ExampleService();
rootWorkItem.Services
  .Add<IExampleService>(service);

// Register a type.
rootWorkItem.Services
  .AddNew<ExampleService, IExampleService>();
// Register an instance.
ExampleService service = new ExampleService();
container.RegisterInstance<IExampleService, ExampleService>
                (new ContainerControlledLifetimeManager());

// Register a type.
container.RegisterType<IExampleService, ExampleService>
                (new ContainerControlledLifetimeManager());

Ff648430.note(en-us,PandP.10).gifNote:
For more information about services in the Library, see How to: Register and Use Services.

Wiring Views and Services Using Dependency Injection

The Dependency Injection pattern is a specialized version of the Inversion of Control pattern where the concern being inverted is the process of obtaining the needed dependency.

This section illustrates how dependencies can be injected in both libraries—the Block and the Library—to wire views and services:

  • Block. To be able to inject dependencies in a Block application, you must add attributes, such as [ServiceDependency], [CreateNew], [ComponentDependency] and [InjectionConstructor], to your code.
  • Library. By using the Library, you can choose the dependency injection container that your application will use because it is unaware of the container. The library provides support for the Unity container; however, because the container is accessed through the IServiceLocator interface, the container can be replaced. To do this, your container must implement the IServiceLocator interface. Usually, if you are replacing the container, you will also need to provide your own container-specific bootstrapper. Containers are used to satisfy dependencies between components; satisfying these dependencies typically involves registration and resolution.

    For services to be available to be injected, they must be registered with the container. Registering a service involves passing the container a service interface and a concrete type that implements that service. There are primarily two means for registering services: through code or through configuration. The specific means vary from container to container. After a service is registered, it can be resolved or injected as a dependency. When a service is being resolved, and the container needs to create a new instance, it will inject the dependencies into these instances.

    Ff648430.note(en-us,PandP.10).gifNote:
    For information about injecting dependencies in Unity, see Setting Up the Unity Container in the Unity documentation.

The code in the following table shows how to implement a class that injects dependencies in the Block and in the Library using the Unity container.

Block

Library

public class MyClass
{
    private IExampleService _service;
    private OtherClass _myObject;
    
    [InjectionConstructor]
    public MyClass([ServiceDependency]
                    IExampleService service, [CreateNew] OtherClass myObject)
    {
        _service = service;
        _myObject = myObject;
    }
}
public class MyClass
{
    private IExampleService _service;
    private OtherClass _myObject;
    
    public MyClass(IExampleService service,
                    OtherClass myObject
                   )
    {
        _service = service;
        _myObject = myObject;
    }
}

Ff648430.note(en-us,PandP.10).gifNote:
Although only constructor injection is used in the preceding samples, other injection patterns, such as property injection or method injection, can be used in an application that uses the Library, if supported by the container used. Unity supports the aforementioned injection strategies.

Event Broker

An event broker system allows you to support a loosely coupled publish/subscribe event mechanism. This section describes the event broker system for the Block and the Library:

  • Block. In the Block, the WorkItem has the EventTopics collection where all publishers and subscribers to an event are located. You use the attributes [EventPublication] and [EventSubscription] to select the publisher and subscribers of the event (the most common way), or you can do it programmatically using the EventTopics collection. This implementation uses the .NET Framework events.
  • Library. The Library implements this system through the EventAggregator service that does not use .NET Framework events. To create an event, you should create a class that inherits from the CompositePresentationEvent<T> class, where T is the type of the event's payload (the argument that will be passed to the subscribers when raising the event; it does not need to implement EventArgs).

The code in the following table shows how to publish and subscribe events in the Block and in the Library.

Block

Library

// Publishing
[EventPublication("MyEvent", PublicationScope.Global)]
public event EventHandler<DataEventArgs<int>> MyEvent;

// Subscribing
[EventSubscription("MyEvent",
                 Thread = ThreadOption.UserInterface)]
public void OnMyEvent(object sender,
                       DataEventArgs<int> e)
{ ... }
// Getting the event aggregator service
IEventAggregator eventAggregator = 
               container.Resolve<IEventAggregator>();

// Publishing
int arg = 1;
eventAggregator.GetEvent<MyEvent>().Publish(arg);

// Subscribing
eventAggregator.GetEvent<MyEvent>()
       .Subscribe(MyHandler, ThreadOption.UIThread,
                  false, arg => arg == 1);

// Defining the event
public class MyEvent : CompositePresentationEvent<int>
{ ... }

// Defining the handler
public void MyHandler(int arg)
{ ... }

As seen in the preceding code, the Library does not require attributes in your code to be able to use the event aggregator service. Also notice that the Subscribe method of the Event Aggregator service is overloaded and can receive a Predicate<TPayload>, where you can provide a filter expression to decide whether the subscriber should receive a particular callback.

Ff648430.note(en-us,PandP.10).gifNote:
For more information about subscribing and publishing to events in the Block, see How to: Subscribe to Events and How to: Publish Events.
For more information about subscribing and publishing to events in the Library, see How to: Subscribe and Unsubscribe to Events.

Commands

Commands handle UI actions. They are a loosely coupled way to bind the UI to the logic that performs the action. This section compares how commands can be implemented in the Block and the Library:

  • Block. The Block uses commands to associate an event handler with more than one UI element and to associate one UI element with multiple command handlers. The [CommandHandler] attribute identifies a command handler for a specific command. The Block creates a command for the handler and registers the handler in the command's collection of the WorkItem. The Command class exposed by this collection provides a method to create invokers to connect events and the command code.
  • Library. The Library lets you use commands routed outside the boundaries of the logical tree (for example, in presenter or controller classes). It introduces several new commands that do not require handling logic in the code-behind. They are custom implementations of the ICommand interface defined by WPF and Silverlight, and they implement their own routing mechanism to get the command messages delivered to objects outside the logical tree.

    The Library includes the following commands:

    • DelegateCommand. This command allows an external class to handle the commanding logic instead of requiring a handler in the code-behind.
    • CompositeCommand. This command can have multiple child commands.

The code in the following table compares commands in the Block and the Library.

Block

Library

// Adding the invoker
ToolStripMenuItem item = new
                      ToolStripMenuItem("My Command");
rootWorkItem.Commands["MyCommand"]
                      .AddInvoker(item, "Click");

// Registering the command handler
[CommandHandler("MyCommand")]
public void OnMyCommand(object sender, EventArgs e)
{ … }
// Defining the command
public static class ExampleCommands
{
    public static CompositeCommand MyCommand =
           new CompositeCommand();
}

// Adding the invoker
ExampleCommands.MyCommand.RegisterCommand(
               new DelegateCommand<int>(MyDelegate));

// Defining the handler
public void MyDelegate(int arg)
{ … }

// Executing the command
int arg = 1;
ExampleCommands.MyCommand.Execute(arg);

Ff648430.note(en-us,PandP.10).gifNote:
For more information about commands in the Library, see the Commands technical concept.

Emulating WorkItems Using Controllers with Scoped Containers

The Block contains the WorkItem and WorkItemController, which are often used with implementing workflow scenarios, as discussed later. This section describes how to emulate this in the Composite Application Library with a controller and scoped Unity containers.

Imagine a scenario where you have product orders, each one associated to a single customer. There is an order workflow, in which orders pass through different states—created, updated, processed, canceled, or shipped—and the state of each order has to be maintained separately.

Sometimes you need to have a different set of objects, views, and services for each customer, such as in this example scenario to maintain the state of each order. This can be achieved by using controllers with scoped containers. To do this, you typically create a child container and add access to that child container through the IUnityContainer interface, as shown in the following code.

IUnityContainer childContainer = container.CreateChildContainer();
childContainer.RegisterInstance<IUnityContainer>(childContainer);

To implement controllers with scoped containers in the example scenario

  1. First, create an instance variable of dictionary type to store the created controllers. The dictionary will be used to verify whether an order has an existing controller associated with it. The instance variable declaration can be seen in the following code.
    private Dictionary<string, OrdersController> controllers = new Dictionary<string, OrdersController>();
    
  2. Create a method that handles the creation of the controllers with scoped containers. Following the orders scenario, when the user clicks an order to display it—for edition, for example—the ShowOrder method is invoked. This method creates a key because if the order has been previously opened for edition; the application should continue working with that same instance of the order. The ShowOrder method is shown in the following code.
    public void ShowOrder(Order order)
    {
        // Construct a key to register the controller.
        string key = string.Format("Order#{0}", order.ID);
    
        // Is the controller already made for this order?
        // If so, return the existing one.
        OrdersController controller;
        if (controllers.ContainsKey(key))
        {
            controller = controllers[key];
        }
        else
        {
          // If not, create the controller with a scoped container.
            IUnityContainer childContainer = container.CreateChildContainer();
            childContainer.RegisterInstance<IUnityContainer>(childContainer);
            childContainer.RegisterInstance<Order>(order);
            controller = childContainer.Resolve<OrdersController>();
            controllers.Add(key, controller);
        }
        controller.Activate();
    }
    

The ShowOrder method takes an order instance as a parameter, creates a key for it, and checks whether a controller associated to that order has already been created. If an associated controller is found in the dictionary, this existing controller is retrieved; if an associated controller is not found in the dictionary, a new controller with a scoped container is created.

Finally, the Activate method of the controller is invoked.

More Information

For more information and to download the Library, see Composite Application Guidance for WPF and Silverlight.

For more information about the Block included with the Smart Client Software Factory, see Composite UI Application Block. To download the Block included with the Smart Client Software Factory, see Smart Client Software Factory.

For more information about composite UI deliverables from patterns & practices, see "Guidelines for Choosing Composite UI Guidance Assets from patterns & practices" in When to Use This Guidance.


Home page on MSDN | Community site

Show:
© 2014 Microsoft