The core element of CAB is the WorkItem
16. It represents the container
which manages all the objects instantiated by Dependency Injection. Figure
14 presents the WorkItem
hierarchy of the Test Suite.
Figure 14: The WorkItem hierarchy of the TestSuite.
Typically, every CAB module provides its own WorkItem
. The services
registered by a module are available at the module WorkItem
or any sub
WorkItems
. Only the services registered at the root WorkItem
can be accessed
in the whole application. Due to this fact, the modules FunctionGen.Driver
,
FunctionGen.ControlView
and SignalVisualizer
register their module
WorkItems
in the TestDevice.Manager
WorkItem
. Hence, these
WorkItems
can access the
services provided by the TestDevice.Manager
.
Sub WorkItems
are often used to handle a part of the use case. In Figure
14 the Documents are such sub WorkItems
. A Document WorkItem
manages the
lifecycle of a document and it controls the UI window which shows the
content to the user. The other sub WorkItems seen in Figure 14 are the Device Instances
. Each of them represents a test device.
This chapter describes how the requirements are implemented by using the Composite UI Application Block. For most of the requirements the prototype only shows one of the possible ways to solve them.
The configuration of the module loader is done in an XML file. The prototype is using the built-in dependency module loader which is shipped with the Smart Client Software Factory. It allows the definition of dependencies inside the XML file.
Additionally, the configuration via command line arguments of the
application is required. The first idea was to pass all information of the
XML configuration file as command line arguments. This idea is not practical
because all the information in a single line is not readable any more.
Therefore, a command line argument parser is implemented to let the user
choose which XML configuration file should be used. The different
configurations can be defined in XML files. If the user does not set the
command line argument, it reads the default file ProfileCatalog.xml
.
The prototype includes a unit test project for the TestDevice.Manager
module. It is using the Visual Studio 2005 unit test framework. The project
tests all non-UI classes of the module. These are the ModuleController
, the
TestDeviceManager
and the TestDevicesViewPresenter
class. Additionally, the
UI-class TestDevicesView
is partially tested. A complete test of an UI-class
requires special tools or frameworks because the input devices like a mouse
have to be simulated.
The notable aspect is that all of these classes are completely isolated
for the tests. This means that all the needed objects of these classes are
replaced by mock objects. The needed objects are mostly services from the
framework but they can also be collaborators of the same module. Before a
unit test runs, the framework is initialized with these mock objects. The
mock objects can be used to test the correctness of the interaction between
them and the object under test. The abstract class FixtureBase
initializes
the framework with the test configuration. All the test classes derive from
FixtureBase
and reuse the framework setup. The unit test writing can be done
with minor effort by reusing the framework setup.
The Composite UI Application Block is able to load services on demand.
The prototype is using this feature for the DocumentManager
. The
DocumentManager
uses the OpenFileDialog
and the
SaveFileDialog
class which
uses native resources. With performance in mind these classes are typically
lazy instantiated to save resources if they are not used. The framework
already implements service loading on demand. Therefore, a software
developer does not need to care about lazy loading inside the services.
The projects that are created with the Smart Client Software Factory in Visual Studio build all their files in the same directory. This may become problematic because the resource filenames could be identical with the ones of other modules. It is also possible that the modules use different versions of the same assembly. If all the files are deployed in one directory, the installation of a new module could overwrite files from other modules. The Test Suite uses customized build paths for deploying every module in a separate directory to prevent the mentioned issues (Figure 15). Furthermore, the module loader configuration files have to be adapted to the new path names.
Figure 15: File structure of the Test Suite.
Beside the Modules
directory tree, the following directories are created:
Infrastructure
contains
all files for the infrastructure. The infrastructure files are the
application assemblies that are required by all other modules.Libraries
include all
the necessary library files that are shared by the modules. For example, the
CAB assembly files are in this directory.Resources
contain all
the resource files that are shared by the modules.By using these sub directories, the common language runtime (CLR) has to be told where it is going to find the assemblies. A way to accomplish this, is to add the probing element in the application configuration file as it is shown in Listing 13.
1 <runtime>
2 <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
3 <probing privatePath="Libraries;Infrastructure" />
4 </assemblyBinding>
5 </runtime>
Listing 13: Extract of the application configuration file of the Test Suite.
CAB supports GUI extensions in two ways. The first one is via the Workspaces
. They are responsible to host the views of the modules.
The Test Suite provides a DockPanelWorkspace
to host views
inside the shell form. Furthermore, it contains the FormWorkspace
to host views in an own modal dialog. Both Workspaces
are
able to host Windows Forms controls and WPF controls. Due to this feature, a
step-by-step migration of the older UI technology to the newer one is
supported. All the modules of the Test Suite prototype use Windows Forms
controls for their views except of the SignalVisualizer
.
This module implements all its views as pure WPF controls.
The second way to support GUI extensions is done through UIExtensionSites
. The
Infrastructure.Layout
and
the TestDevice.Manager
module register UI elements as
extension sites. The other modules are able to extend these UI elements. For
example, the SignalVisualizer
adds a new menu item into the
drop down list of the View menu item which is defined by the Infrastructure.Layout
. The help topics of CAB and the reference
application of SCSF show how to use UIElementSites
. In
their examples, the modules create the concrete UI elements and add them to
a UIExtensionSite
. The problem is that the modules have to
know which concrete UI element is behind the UIExtensionSite
.
For example, the Infrastructure.Layout
registers a MenuStrip
instance. All modules, which have to extend the
MenuStrip
, create ToolStripMenuItem
elements and
add them to the UIExtensionSite
. If the MenuStrip
in the Infrastructure.Layout
module is replaced with
another similar control, all the modules have to be modified.
The Test Suite prototype solves this problem with the IUIElementCreationService
. The modules use this service to create
the necessary UI elements. Thus, the modules do not have any dependency to
the UI technology which is used in the Infrastructure.Layout
module. If the UI technology is replaced in the layout module, just the
IUIElementCreationService
class has to be updated.
The Test Suite uses the built-in command system of CAB. It can handle all
the needed requirements for most use cases. One of the exceptions is the
handling of the edit functions cut, copy and paste. Here, the command should
be routed to the active UI element only. In Windows-based applications the
active UI element is the one which has the focus. Besides command routing,
the command states are depending on the active UI element. If the active UI
element does not support these commands or no elements are selected, the
according commands have to be deactivated. In the Test Suite, the EditManager
takes care for the special treatment of these commands.
The Test Suite uses loosely coupled events for various reasons. The
modules can use the CAB event broker to update the text shown in the
application status bar. Moreover, the communication between the Presenter
/
WorkItemController
and the Presenter
/ Presenter
classes is done with the
loosely coupled events. A special case is the transmission of the signal
samples over the event broker. The SignalVisualizer
registers for this event topic and displays the samples. The event handling
has to be synchronized since the SignalVisualizer
runs in
the UI thread and the signal samples are created by other threads. CAB
provides a simple solution for thread synchronization in the event handler
as it is shown in Listing 14.
1 [EventSubscription(EventTopicNames.Sample,
2 Thread = ThreadOption.UserInterface)]
3 public void GotSample(object sender, SampleEventArgs sample)
4 {
5 ...
Listing 14: Thread synchronization in the event handler.
The synchronization with the WPF control of the SignalVisualizer
does not work properly. During the application shutdown a NullReferenceException
is thrown in the
System.Windows.Forms.Control.WaitForWaitHandle
method. The
exception is only thrown if a virtual function generator is not turned off
before the application is closed. Even then, the exception does not occur
every time. This is a typical behavior for threading issues. Therefore, the
prototype does not use the synchronization functionality of the CAB event
broker. Instead, the view synchronizes the method call manually.
1 public void AddSample(object device, double amplitude,
2 double relativeTime)
3 {
4 if (Dispatcher.CheckAccess())
5 {
6 InnerAddSample(device, amplitude,
relativeTime);
7 }
8 else
9 {
10 Dispatcher.Invoke(DispatcherPriority.Normal,
11 new AddSampleDelegate(InnerAddSample),
device, amplitude,
12 elativeTime);
13 }
14 }
15
16 private void InnerAddSample(object device, double amplitude,
17 double relativeTime)
18 {
19 signalView.AddSample(device, relativeTime, amplitude);
20 }
Listing 15: Shows how thread synchronization can be done in WPF controls.
Listing 15 shows the manual thread synchronization. The code extract is
part of the VisualizerView
class. The Dispatcher.CheckAccess
method verifies if the call needs to be
synchronized. Dispatcher.Invoke
calls the InnerAddSample
method synchronized with the UI thread.
The communication between the function generator and the visualizer via loosely coupled events is just exemplarily. Typically, signal samples have to be processed in real time and not in the way it is done in the prototype. The event broker is to slow for processing signals with high frequencies. However, it is a good example why synchronization can be necessary in association with loosely coupled events.
The Test Suite implementation presented in this chapter handles all the
requirements which are defined in chapter 2. Nevertheless, many requirements
are already handled by the .NET Framework or the Composite UI Application
Block. The CAB framework successfully reduced the effort to create the
prototype application. However, the learning time for understanding the
framework cannot be disregarded. CAB is very powerful through its abstract
and flexible design but it is also highly complex. Microsoft has seen that
the complexity of the framework is a problem for many users. Therefore, they
introduced the Smart Client Software Factory. SCFS assists the software
developer in common tasks and it is delivered with extended documentation.
Nevertheless, it does not help much in learning the concepts of the key
parts, the Object Builder and the WorkItem
of CAB.
16 See also WorkItem (p. 20).