Composite UI Application Block Hands On Labs |
| |||||
Lab 1: Introduction to Developing with the Composite UI Application BlockAfter completing this lab, you will be able to:
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
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:
The application block is designed to support the development of smart client line-of-business applications, such as those used in the following scenarios:
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
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:
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.
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:
.
Exercise 2: Creating a Module
In this exercise, you will create a basic module that contains a service and a
WorkItem.
First stepOpen the ModuleLoader.sln file, and build the solution. Setting Up the Environment
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
Expand the
WorkItems folder, and open the
GPSWorkItem.cs file.
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.
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. 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 ShellExpand 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.
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.
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 WorkItemIn Solution Explorer, double-click the Program.cs file to open it. 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 ProgramTo 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.
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.
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.
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.
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. |
New feature in Visual Studio 2005,i just used this tool for developing Smart Client project in Microsoft Corp.
转载于:https://www.cnblogs.com/valexzhou/archive/2006/05/17/402072.html