^ - ^ .NET, Lead my own life~~~~

博客园 首页 新随笔 联系 订阅 管理
New feature in Visual Studio 2005,i just used this tool for developing Smart Client project in Microsoft Corp. 

Composite UI Application Block Hands On Labs

printable version  

Lab 1: Introduction to Developing with the Composite UI Application Block

After completing this lab, you will be able to:

  • Understand the Composite UI Application Block.
  • Use the Module Loading feature of the Composite UI Application Block.
  • Understand the recommended project layout and code structure.

Scenario

You will focus on understanding the purpose and features of the Composite UI Application Block, and how you can use the application block when you develop applications.

Estimated Time To Complete this lab: 30 minutes.

Exercise 1: Concepts and Features of the Composite UI Application Block

In this exercise, you will learn about the parts of the Composite UI Application Block and the concepts behind them. Subsequent exercises will focus on using the Microsoft Visual Studio 2005 development system and .NET Framework version 2.0 to implement those concepts.

Introduction

  1. The Composite UI Application Block is designed to help you build complex Windows Forms-based applications. It assists you by providing an architecture and implementation that uses the common patterns found in front-end, line-of-business (LOB) applications.
    Building an application with the Composite UI Application Block provides the following benefits:
    • Quality and consistency for architecture teams
    • Increased productivity and faster ramp-up time for large developer teams
    • Consolidation of operational efforts for operations teams
    The application block is designed to support the development of smart client line-of-business applications, such as those used in the following scenarios:
    • Online transaction processing (OLTP) front-ends
    • Rich client portal scenarios
    • UI-intensive, information worker standalone applications (such as those used in call centers)

     

    It is important to understand that the Composite UI Application block is not a replacement for UIProcess, but rather a larger architecture that UIProcess will become part of in later versions of the application block.

Design Overview

  1. One of the key design goals of the Composite UI Application Block is to support the development of applications through the use of independent, but collaborating, modules. Composite UI Application Block applications could include some of the following components:
     
    • SmartParts
      • SmartParts provide the visual elements in your applications. These visual elements can be entirely independent of the application in which they are hosted, which allows them to be developed independently as well.
     
    • Workspace 
      • Workspace is the container and manager for a SmartPart. It can control how the SmartPart is displayed or hidden and how it is laid out. You can create and use the following types of Workspaces:
        • WindowWorkspace
          The WindowWorkspace lets you show and hide controls framed in a window.
     
        • MdiWorkspace
          The MdiWorkspace lets you show and hide controls and SmartParts in MDI child forms. It is based on the WindowWorkspace type and extends this to display the SmartParts as MDI child forms.
     
        • TabWorkspace
          The TabWorkspace lets you show and hide controls and SmartParts inside tabbed pages.
     
        • DeckWorkspace
          The DeckWorkspace lets you show and hide controls and SmartParts in an overlapped manner with no visible frame. Its implementation of the IWorkspace methods show, hide, and close the SmartParts in a manner similar to a deck of cards. Showing a SmartPart causes it to be displayed on top of the previous SmartPart. Hiding a SmartPart displays the next top-most SmartPart in the deck. Closing a SmartPart removes it from the deck and displays the next top-most SmartPart.
     
        • ZoneWorkspace
          The ZoneWorkspace lets you create a tiled Workspace layout. For example, you can use multiple splitter controls to create an Outlook-style user interface with a navigation zone on the left and two horizontal zones on the right. You can give a ZoneName to each content area where you can show a SmartPart by using a new property provided by the containing Workspace for each of its container controls. These controls include a Panel, a FlowLayoutPanel, and the panels of a SplitContainer. You can then use the ZoneWorkspace instance to access a zone by name and add SmartParts to the zone at design time or at run time. Child container controls are also given an IsDefaultZone property that you can set to true to identify one of the container controls as the default zone in which to display SmartParts.
     
    • WorkItems
      • A WorkItem is a run-time container of components that are collaborating to fulfill a use case. It contains the following code and classes:
        • Start-up (or initialization) code that initializes the use case
        • State that is shared by components collaborating in the use case
        • Controller classes that act on the state and other resources
        • View classes that interact with their controllers and reference state
        • Tear-down code
     
    • Infrastructure Services
      • The Composite UI Application Block includes a set of basic infrastructure services that you can use in your applications. You can also build your own services that provide infrastructure capabilities specific to your applications.
        The application block provides the following infrastructure services:
        • Catalog Reader service
        • Module Loader service
        • Authentication service
        • State Persistence service
     
    • Modules
      • Modules are composed of a set of services,WorkItems,SmartParts, controllers, business entities, and the Module Initialization class, which is used for initializing and running the module's WorkItems.
     
    • Module Services
      • Module services are objects that you can register once by using the Service attribute, and then reference from any other component from within any WorkItem.
     
    • State
      • A WorkItem contains state, which is a loosely typed collection of objects shared by the components collaborating in the use case. State can be persisted by calling the Load and Save methods on WorkItem.
      • State can be persisted to any storage medium by using an infrastructure service that implements IStatePersistenceService. There are two built in implementations of this service: FileStatePersistenceService, which will persist the WorkItem to a file in the same directory as the executable and IsolatedStorageStatePersistenceService, which will persist the WorkItem to a file in Isolated Storage.
     
    • Controllers
      • The controllers have the same role that they have in the MVC pattern. They are there to implement the business logic behind the view.
     
    • Service Agents
      • Service agents are components that are used to interact with any external back-end services.

     

    Service agents are necessary when you develop applications that use external services; therefore, they are listed here as important components. However, the CAB does not provide any formal structure or support for these components.
  2. The Composite UI Application Block consists of a number of subsystems that interoperate to provide the functionality that the application block provides. Each of these subsystems is defined in its own namespace and assembly, as shown in the following figure.
     
    The assemblies that you use in a typical CAB implementation are:
    • Microsoft.Practices.CompositeUI.WinForms.dll
    • Microsoft.Practices.CompositeUI.dll
    • Microsoft.Practices.ObjectBuilder.dll
     
    .

     

Exercise 2: Creating a Module

In this exercise, you will create a basic module that contains a service and a WorkItem.

First step

  1. Open the ModuleLoader.sln file, and build the solution.

Setting Up the Environment

  1. In Visual Studio, click Tools, click Options, click Projects and Solutions, and then click Build and Run. If For new solutions, use the currently selected project as the startup project. is selected, clear the check box. Click OK.
     
    Familiarize yourself with the new Solution Explorer in Visual Studio 2005 by expanding all of the projects and folders in the view.
     
    In particular, examine the Services, WorkItems, and Views folders and structures. When you develop applications with the CAB, this is one method for structuring and laying out your projects. You write each module in its own .NET class library project (and compile it into one assembly per module). All services, WorkItems, and Views are separated into their own folders. The ModuleInit class is under the root and holds a class for initializing the module.

     

    You will see a light yellow folder named CAB. This is a new feature called Solution Folders that Microsoft introduced within the IDE. These folders are used to group a set of projects or solution items so that solutions are easier to manage and view. The CAB folder includes the three subsystems of the Composite UI Application Block.
     
    Note that you have two projects: the ModuleLoaderShell and the GPSModule.
    The ModuleLoaderShell is the project that contains the entry point for the application, as well as the Workspaces.  The GPSModule is the structure that will form the module you are about to create. Make sure that the GPSModule builds to the same location as the ModuleLoaderShell assembly.
  2. Expand the References node under the GPSModule project.

    You will see references to the three CAB subsystems:CompositeUI,ObjectBuilder, and CompositeUI.WinForms. Each reference has its Copy Local property set to true so that when the solution is compiled (including the subsystems), the assemblies will be copied into the local module and shell debug directories.

Building Your First Module

  1. Expand the Services folder, and open the IGPSService.cs and GPSService.cs files.

     

    The modules can contain a number of different elements. In this exercise, you will create a module that has two services, one view, and one WorkItem to drive the services and view.
  2. Paste the following code into the IGPSService.cs file where indicated.

    public interface IGPSService
    {
        int GetLatitude();
        int GetLongitude();
    }
    The first step in creating a service is to create an interface that defines the operations or methods of the service. In this exercise, you are declaring a service that has two main operations:GetLatitude and GetLongitude. You use interfaces to define the services so that you can easily change the implementation of the operations without affecting any of your base code.
  3. Paste the following service implementation code into the GPSService.cs file where indicated.

    [Service(typeof(IGPSService))]
    public class GPSService : IGPSService
    {
        public int GetLatitude()
        {
            return 42;
        }
        public int GetLongitude()
        {
            return 125;
        }
    }
    This is a very important part of the service creation. It is the actual implementation of the service that you have defined through the IGPSService interface. You create a class named GPSService, which implements the IGPSService interface. You then need to write implementations for both of your operations:GetLatitude and GetLongitude. In this implementation, you are returning dummy values. However, at some point you may want to write a new implementation of this service that actually connects to a GPS receiver and retrieves real-time positioning information.
    The first difference that you will see between this and a normal interface implementation is the use of the Service attribute above the class. This attribute is used to register the class as service for the current module. That way, the CAB can easily retrieve the implementations of the services and use them where necessary. You are establishing the ServiceType as the type of your IGPSService interface, which allows you to easily change the implementation without affecting any other code that may need to refer to this service.
  4. Open the OnDemandService.cs file under the Services folder.
     
    Paste the following code under the first //TODO indicator.

     

    public interface IDistanceCalculatorService
    {
        int ComputeDistance(int latitude, int longitude);
    }
    Here, you are creating another service called IDistanceCalculatorService, which will have one operation that computes the distance by using the latitude and longitude values.
  5. Paste the following code under the second //TODO indicator.

    [Service(typeof(IDistanceCalculatorService), AddOnDemand = true)]
    public class DistanceCalculatorService : IDistanceCalculatorService
    {
        public DistanceCalculatorService()
        {
            MessageBox.Show("This is DistanceCalculatorService being constructed for the first time. Subsequent calls to the service will not require construction.", "Service constructed", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        public int ComputeDistance(int latitude, int longitude)
        {
            return 1234;
        }
    }
    This time, you are writing the implementation for your IDistanceCalculatorService. You have also written a constructor that notifies you when the service is being constructed and initialized.
     
    The second service introduces a feature called On Demand Services. You use On Demand Services to specify that a service will not be instantiated until it is actually requested for use. To use On Demand Services, add the AddOnDemand attribute, and set it to true.
     
    Instead of being created during the initialization of the module like all other services, an on-demand service will not be created until a piece of code retrieves the service. This is helpful if you have many services because they are not loaded into memory until someone wants to use them. For example, you could have 200 services that are not used until required, which would be a large memory and performance enhancement.
  6. Now that your services are in place, you need to set up your view so that you can take advantage of the services you have implemented.
    Expand the Views folder, right-click the GPSView item, and click View Code.

     

  7. You will see a class named GPSView, which inherits from UserControl. This is the same as any other custom control implementation. If you are unfamiliar with .NET Framework 2.0, you may wonder what the partial keyword is doing in the class declaration. You may also wonder why there is a call to the InitializeComponent method when the InitializeComponent method is not visible.
    A new feature in .NET Framework 2.0 allows you to mark a class as partial. By doing this, you indicate that you have implemented only one piece of the class in your current code location, and you have implemented the other pieces elsewhere. This allows you to split a class definition across multiple source files. This is helpful because you can eliminate clutter, and you can implement your UI plumbing in one file and your event handling in another. For example, this allows you to send one file to a visual designer, who creates the front end, and send another file to a business logic developer, who creates the business logic and event handling code.
    Add the following code to the top of the GPSView class, as indicated by the first //TODO.

     

    private IGPSService gpsService;
    [ServiceDependency]
    public IGPSService GPS
    {
        set { gpsService = value; }
    }
    This code tells the CAB that this class has a dependency on the IGPSService. You have not initialized a new service. You want to use the service that has already been created and stored.
    The CAB uses Inversion of Control (IOC) or Dependency Injection to accomplish this task. When you place the [ServiceDependency] attribute on the member property, it will automatically initialize it with the correct service at run time when the view is added to the WorkItem.
    You want to use the GPS service in your view, so you add this dependency.
  8. Replace the cmdGetDistance_Click method with the following code.

    private void cmdGetDistance_Click(object sender, EventArgs e)
    {
        // Note that you didn't added a ServiceDependencyAttribute for the
        // IDistanceCalculatorService, because that way the service would be created
        // when this view is added to the work item. This is just for the purposes of this example.
        //The service is not loaded into memory until this method is called.
        //The [ServiceDependency] attribute is not applied until the property is called the first time
        IDistanceCalculatorService calc = parentWorkItem.Services.Get<IDistanceCalculatorService>();
        txtDistance.Text = calc.ComputeDistance(gpsService.GetLatitude(), gpsService.GetLongitude()).ToString();

    }
    This code is the event handler for the GetDistance button, which is clicked to retrieve the current distance.
     
    To get the distance, you need to use a IDistanceCalculatorService and your GPSService. You made the IDistanceCalculatorService an On Demand service, which means that it has not been constructed until now. To construct and receive the service, make a call to the Get method of the Services collection of the parent work item. The Get method takes the type of service you wish to retrieve (IDistanceCalculatorService), and returns a reference to it.
     
    You then need to call the ComputeDistance method to retrieve the actual distance, and store it in your view's txtDistance text box. The ComputeDistance method accepts the latitude and longitude as parameters; therefore, you call the GPSService GetLatitude and GetLongitude methods to return the information.
     
    You did not need to call Get method of the Services collection to retrieve the GPSService. This is because you used the [ServiceDependency] attribute, which made a call to Services.Get for you when the view was added to the WorkItem.
  9. Replace the cmdGetLatitude_Click method with the following code.

    private void cmdGetLatitude_Click(object sender, EventArgs e)
    {
        txtLatitude.Text = gpsService.GetLatitude().ToString();
    }
    This is the event handler method for the GetLatitude button, which is used to retrieve the latitude from GPSService. As in the previous step, make a call to GPSService's GetLatitude method, and store the result in your text box. You do not need to call Get on the Services collection because you have already retrieved an instance of the service by using the ServiceDependency attribute.
  10. Expand the WorkItems folder, and open the GPSWorkItem.cs file.

     

  11. Replace the OnRunStarted method with the following code:

    protected override void OnRunStarted()
    {
        base.OnRunStarted();

        IWorkspace GPSWorkspace = Workspaces["MainWorkspace"];
        GPSWorkspace.Show(Items.AddNew<GPSView>());


    }
    The WorkItem's responsibility is to show the GPSView in the given workspace. This will be covered in detail in another lab.
     
    The WorkItem is a crucial part of the architecture, and it is shown here as the way to show a view in the workspace on the shell (WinForm).
    If you are new to the .NET framework 2.0, you may be confused by the syntax of:
     
    Items.AddNew<GPSView>().
     
    A new feature called Generics has been introduced in the platform.
    You can read more about Generics by visiting this introduction article on MSDN:
     
    The AddNew call simply accepts a type as a template parameter, which tells it what type it is to create and work with. It will then return this type instead of an object type so that no casting is needed.
  12. Open the GPSModuleInit.cs file.

    This file contains a class that inherits from ModuleInit. The class in a module assembly that inherits from ModuleInit is constructed when the CAB is initialized, and its Load method is executed accordingly. It is a mechanism used to initialize a module and all of its segments.
    If you want to programmatically add services to the module, override the AddServices method of the ModuleInit class.
    If you want to display the user interface or perform custom logic on startup, override the Load method of the ModuleInit class. These methods are not mandatory.
  13. Replace the Load method with the following code:

    public override void Load()
    {
        // This is just to show when the module initialization happens
        MessageBox.Show("We are in the SampleModule Start() method. Our module initialization is underway.", "Module Initialization", MessageBoxButtons.OK, MessageBoxIcon.Information);

        GPSWorkItem workitem = rootWorkItem.WorkItems.AddNew<GPSWorkItem>();
        workitem.Run();
        // Shows that the service is ready and working
        MessageBox.Show("Our GPS Service is now constructed and ready for use.\nGet Latitude returns: " + workitem.Services.Get<IGPSService>().GetLatitude().ToString(), "GPS Service Constructed and Ready", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }
    The Load method is written to initialize the module on startup.
     
    You first create the GPSWorkItem by calling the AddNew method on the WorkItems collection of rootWorkItem. You then execute the Run method on the WorkItem to place your GPSView in your MainWorkspace in the shell.
     
    After you have initialized your GPSWorkItem, you let the user know that the Load method is being executed, and that module initialization is underway. You also call Get for the first time on your IGPSService, and run its GetLatitude method to prove that all services that are not On Demand have been created and are ready to be used.

Creating the Shell

  1. Expand the ModuleLoaderShell project if it is not already expanded, and double-click the MainForm.cs item.

    You will be presented with the new Visual Studio 2005 forms development environment. You will see a DeckWorkspace control on the form, which is named MainWorkspace. You will now understand where the GPSView is placed in the previous step. If you take a look at the previous step, you will see that you have referenced this MainWorkspace control by using
     
    IWorkspace GPSWorkspace = Workspaces["MainWorkspace"];
     
    If you were to extend this shell, you would add more Workspaces and have them populated with views from within your special, independent modules.
  2. In Solution Explorer, double-click the ProfileCatalog.xml file to open it.

    The Composite UI Application Block supports the customization of an application by using solution profiles in a catalog. A solution profile is a list of modules that should be loaded into an application.
     
    Because you have created a module and you want to use it in your application, you must define it in the ProfileCatalog.xml. This allows the module loader to load the module.
  3. Replace the XML in ProfileCatalog.xml with the following:

     

    <?xml version="1.0" encoding="utf-8" ?>

    <SolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile" >
    <Modules>
        <ModuleInfo AssemblyFile="GPSModule.dll"
    />
    </Modules>
    </SolutionProfile>
    Study the format: it is quite simple. It allows you to define a set of different solution profiles for your application, but keep in mind that only one will be used per application. You will see that the name is Default for the solution profile in the previous XML example. This is the default profile, and if not specified in code, it will be used instead of any other profile in the file.
     
    Make sure that the Copy To Output Directory is set to CopyAlways, otherwise the solution will not know where to find the catalog file.
     
    You can have multiple modules per solution profile, but in this case you have only one module named GPSModule.

Creating the Program and Root WorkItem

  1. In Solution Explorer, double-click the Program.cs file to open it.

  2. Replace the Main method with the following code:

    [STAThread]
    static void Main()
    {
        new Program().Run();
    }
    This is the entry point for the application.
     
    Call the Run method on the class. The class implements FormShellApplication<WorkItem, MainForm> and you pass it the type of work item to use and the form to use as the shell.
     
    Calling Run will load the ProfileCatalog.xml file and determine which modules it needs to load. After it retrieves the list of modules, it will begin loading them and initializing them. If any of the modules contain a ModuleInit class, it will construct the class and call the appropriate Load and/or AddServices methods

Trying Out the Program

  1. To build the application, press Ctrl+Shift+B.

    This will build the solution. Fix any errors that may have occurred during the build, and repeat this step until no errors are shown.
  2. To start the application in debug mode, press F5.
    WARNING: If you experience difficulties building or debugging a solution, make sure that the ModuleLoaderShell project has been set as the Startup Project.

     

    You will receive a few messages to let you know that the module has been initialized, and that the service is working.
    If you receive any errors during this stage, make sure that your ProfileCatalog.xml is correct.
  3. When the UI loads, click the Get Distance button to initialize the IDistanceCalculatorService Service, and call the ComputeDistance method.

    You will receive confirmation that the service is being constructed for the first time.
  4. Click the Get Distance button again. You do not receive any messages.

    This means that the code has not tried to construct the service again. It is simply returning the service that is stored in the root WorkItem.
  5. Click the Get Latitude button to run the GetLatitude method from the IGPSService service. You do not receive any messages because the IGPSService Service was initialized when the application started because you did not use On Demand loading for it.

To check the finished solution click here.

posted on 2006-05-17 02:40  ValeX  阅读(3333)  评论(0编辑  收藏  举报