devxlogo

Build Composite WPF Applications

Build Composite WPF Applications

hen you build complex UI applications, it is all too easy to end up with a messy, tightly coupled, interwoven mess that is difficult to develop and maintain; and impossible to test.

To avoid that, you need to employ good design patterns in your UI layer that help you keep things loosely-coupled and testable. Composite Application Guidance for WPF (Composite WPF for short) is a set of libraries, documentation, and sample code from Microsoft patterns & practices that helps you to build clean, maintainable applications. This article shows you what the Composite WPF provides and how to use it to build your WPF applications.

The first question you may be asking yourself is “What’s a composite application?” In simple terms, a composite application is composed of multiple loosely-coupled parts. WPF itself is built on the concept of composition. You compose your UI out of granular UIElements, forming a whole that users see as a Window or Page on their screens. Composite WPF is more abstract, focusing specifically on how you compose the code artifacts that contain both the UI definitions and the logic code that supports the UI presentation. Composite WPF provides the Composite Application Library (CAL), a Reference Implementation sample application, a set of QuickStart applications, and documentation for everything provided in the guidance.

Although the simplest way to get started building a WPF application is to start adding Windows and user controls to a project and fleshing everything out with a jumble of XAML and code behind, this quickly leads to a difficult-to-develop-and-maintain application. You will almost certainly end up with complex object and assembly reference paths interwoven through your code, and tight coupling between the logic code and the specific UI elements in your application.

If you care about productivity and maintainability, then you need to employ good UI design patterns to prevent this, and you need a way to allow different parts of the application to be developed independently, but still come together easily into a single, coherent whole at design and runtime. You will also want to be able to unit test the logic in your UI application, which requires that logic to be decoupled from the associated UI layout and controls as much as possible. This takes a little more up-front work as you define each of the pieces your application is composed of, but will result in an overall reduction in your development time because it avoids the downstream complexities that really get you bogged down in integration, testing, and maintenance.

Composite WPF events follow the pub/sub pattern, keeping publishers and subscribers decoupled. They also support thread dispatching on the UI thread, publishers thread, or a thread pool thread. You can also specify a filter for when your subscriber is called.

Composite WPF helps you do accomplish these goals by providing the following features:

  • Modular loading: You can decompose your application into multiple loosely-coupled modules that get dynamically loaded at runtime.
  • UI composition: You can dynamically compose portions of your UI out of loosely-coupled views, and you can follow UI design patterns to keep the logic code decoupled from the UI definition.
  • Commands: You can use a commanding infrastructure that overcomes limitations in WPF Routed Commands and that allows command handlers to be decoupled from the UI itself.
  • Loosely-coupled Pub/Sub events: You can publish and subscribe to strongly-typed events from logic code scattered across your application without any direct references between publishers and subscribers. You also have control over what thread the event notifications are made on, and have the ability to filter notifications based on the contents of the event data payload.

A good way to make these concepts concrete is to work through a fairly simple Composite WPF application. The downloadable code for this article contains the completed sample application but I’ll spend most of the article stepping you through the core parts. In terms of complexity, this sample sits somewhere between the Stock Trader Reference Implementation sample and the QuickStarts that come with Composite WPF. The business scenario is an appointment management application.

Editor’s Note: This article was first published in the November/December 2008 issue of CoDe Magazine, and is reprinted here by permission.

Composite Application Structure
Your Composite WPF application should always have a standard structure. You will have a WPF Application project (EXE) called the “shell project.” The contents of the shell project should be mostly ignorant of the implementation details of the specific business problem the application addresses. It just provides the top level structure for the application, the main window structure, and the initialization code to set up the execution environment. The shell project contains the Shell?the main application window?and a bootstrapper class that gets everything rolling. It may also contain shared resources and styles for the entire application, as well as shared services. However, you can also factor those out into other shared assemblies.

Modules provide most of the functionality of the application. Modules are the top-level building blocks of composite apps. Technically you can have more than one module class in a single assembly, but the recommended approach is to have a separate class library for each module. A module assembly contains a module class that acts as the entry point or initializer for the module. It also contains all the view definitions and supporting code for whatever portion of the business functionality that module encapsulates.

Figure 1 shows how the shell and modules contribute to create the runtime application. As the shell executable starts, the bootstrapper takes care of initializing the environment and creates and displays the Shell main window. The bootstrapper sets up any shared services for the application and then loads the modules that the application is composed of. Each module then initializes its own views and services, and adds its views to known locations in the shell, called regions.

?
Figure 1. Composite Application Startup Process: The figure shows the relationship between the shell and the modules that create the runtime application.
?
Figure 2. AppointmentManager Application Solution: This Solution Explorer view shows the files in the AppointmentManager solution.

Figure 2 shows the project structure for the application I will cover in this article. The three class library projects under the CAL solution folder are the libraries that ship with the Composite Application Guidance for WPF (collectively called CAL). The AppointmentManager project is the shell project. The Appointment.Infrastructure is a shared library referenced by both the shell project and the module projects, and holds shared types such as constants, event types, entity definitions and interfaces. The application is composed of two modules, one for viewing appointments and one for editing appointments.

In any project that uses CAL, you will generally want to reference all three CAL libraries: Composite, Composite.Wpf, and Composite.UnityExtensions.

Bootstrapper
To create your bootstrapper, you can simply inherit the UnityBootstrapper base class from CAL:

   // C#   class AppointmentBootstrapper : UnityBootstrapper      { ... }      ' VB   Public Class AppointmentBootstrapper      Inherits UnityBootstrapper       ...   End Class

When you do that, CAL will take care of setting up Unity as the dependency injection container, and it will also set up the infrastructure for using several other CAL services covered later in this article, specifically the module loading service, the region manager service, and the event aggregator service.

You will need to override two methods in your bootstrapper: the CreateShell method and the GetModuleEnumerator method.

   // C#   protected override DependencyObject CreateShell()   {      Shell shell = new Shell();      shell.Show();      return shell;   }   protected override IModuleEnumerator       GetModuleEnumerator()   {      return new ConfigurationModuleEnumerator();   }         ' VB   Protected Overrides Function CreateShell() _      As DependencyObject       Dim shell As New Shell()      shell.Show()      Return shell   End Function      Protected Overrides Function GetModuleEnumerator() _       As IModuleEnumerator      Return New ConfigurationModuleEnumerator()   End Function

The CreateShell function constructs an instance of the main window type, shows it, and returns the reference so the base class can configure the region manager service for that window. The GetModuleEnumerator method simply returns an instance of your module enumeration service. A module enumerator is a class that tells the module loading service which modules to load.

I’ll cover module loading in more detail shortly, but CAL has three module enumerators you can use out of the box. For most scenarios, you can just use the enumerator classes provided in CAL. The sample application uses the ConfigurationModuleEnumerator, which uses information in the config file to determine what modules to load. CAL also includes a DirectoryLookupModuleEnumerator, which scans a directory to find all the modules in assemblies in that directory; and a StaticModuleEnumerator, which allows you to programmatically tell the module enumerator which modules to load. You can find examples of using those module enumerators in the QuickStarts that come with the Composite Application Guidance for WPF.

For the appointment manager sample application, you also need a data service that provides appointment data to all the loaded modules. I chose to implement that service in the Appointment.Infrastructure class library, and get it loaded at the first opportunity in the bootstrapping process, immediately after the dependency injection container gets initialized by the base class. The UnityBootstrapper class provides a hook for you to either provide your own container or do additional initialization steps through an override of the ConfigureContainer method, like this:

   //C#   protected override void ConfigureContainer()   {      base.ConfigureContainer();      Container.RegisterType(          new ContainerControlledLifetimeManager());   }      ' VB   Protected Overrides Sub ConfigureContainer()      MyBase.ConfigureContainer()      Container.RegisterType( _         Of IAppointmentDataService, _         AppointmentDataService) _         (New ContainerControlledLifetimeManager())   End Sub

The preceding code uses the RegisterType method on the IUnityContainer interface (exposed to me through the UnityBootstrapper.Container base class property) to register the type with the container so that classes in the loaded modules can obtain it from the container and use the same service instance. The ContainerControlledLifetimeManager ensures the instance references handed out by the container follow the singleton pattern.

After defining the bootstrapper class, you need to slightly modify to the way the application starts up compared to normal WPF applications. First you remove the StartupUri property from the Application element in App.xaml.

   

Then you add an override for the OnStartup method in the code behind of the Application class to create the bootstrapper, and then call Run on the base class.

   // C#   protected override void OnStartup(StartupEventArgs e)   {      base.OnStartup(e);      new AppointmentBootstrapper().Run();   }      ' VB   Protected Overrides Sub OnStartup(ByVal e _      As StartupEventArgs)      MyBase.OnStartup(e)      CType(New AppointmentBootstrapper(), _          AppointmentBootstrapper).Run()   End Sub

Defining the Shell and Regions
The Shell is nothing more than a normal WPF window class, and will contain the layout controls that determine the overall layout structure of your top level window. It will typically also define named regions, which are injection points where modules can put their views when they load up. The process of injecting a view is done through the region manager service provided by CAL. You’ll see the code for doing that later. For now, just focus on how to define the regions.

The appointment manager needs a simple top and bottom pane layout, with appointment viewers presented in the top pane, and appointment editors presented in the bottom pane. The Shell.xaml code looks like this:

                                                              
?
Figure 3. AppointmentManager Application and Regions: The top and bottom regions are outlined in red.

A content control defines the top pane, into which modules can inject viewer views, while the bottom pane is a TabControl into which modules can inject edit views. The RegionManager class’s RegionName property identifies the regions. The string names of the regions are factored out to constants in the infrastructure library because they are a shared contract of sorts between the shell and the modules; the modules will also need to refer to those constants. That’s preferable to repeating the string in many places, because the compiler won’t catch any errors if you get one or more of them wrong.

Figure 3 shows the resulting UI, with the views that have been loaded into the regions by the modules already visible. The regions are outlined in the figure.

Loading the Modules
The module loader service in CAL handles loading the modules and calling an initialization method in the module classes. But before it can do that, the module enumerator gets called to determine what modules the application is composed of. Remember, that the appointment manager bootstrapper constructed the ConfigurationModuleEnumerator. Now, you need to specify to the enumerator which modules to load through the app.config file. First you need a config section that points to the CAL class, which knows how to deserialize the module information:

               
...

Next, you need to specify the module information. As mentioned previously, modules provide the core functionality for the application. This sample has two modules, one that provides the appointment viewing capability (Appointment.Modules.Viewers), and one that provides the appointment editing capability (Appointment.Modules.Editors). They are class library projects that include references to the CAL libraries, and they contain a single module class definition each. Here’s the configuration:

                           

Each module specifies its assembly information, the type of the module class within that assembly, and the name of the module. The name can be used to specify inter-module dependencies by putting a nested collection of dependencies (not shown) under each module element. There is also a property you can set to tell the module loader to defer loading the module until an on-demand call is made to the module loader to load the module later.

Modules
A module class is just a class that implements the IModule interface defined by CAL:

   // C#   public class AppointmentViewersModule : IModule   {      public void Initialize() {}   }         ' VB   Public Class AppointmentViewersModule      Implements IModule      Public Sub Initialize() Implements _          IModule.Initialize         ...      End Sub   End Class

The module performs all its startup code in the Initialize method, similar to the bootstrapper for the Shell. Typically, this first involves registering types with the container that the module contributes, and then setting up any views and showing them through the region manager.

For example, Listing 1 shows the full implementation of the AppointmentViewersModule class. The class has a parameterized constructor so the Unity container can inject its dependencies, which include the container itself and the region manager. The Initialize method first calls a helper method called RegisterViewsAndServices. You don’t have to include this, but almost any module needs to do something along those lines, so I usually factor it out into a method of this name as shown. In this case, the only type registered is the AppointmentListView type, based on its interface.

The Initialize method then resolves the view model (which I’ll talk more about shortly) and gets an IRegion reference from the region manager service. It then adds the view from the view model into that region and activates it. This process is what makes the view show up in the Shell region defined earlier.

Defining Views and View Models
The easiest way to define a view for a composite application is via a user control. For example, in the Appointment.Modules.Viewers library, the AppointmentListView user control contains the grid that is presented in the top portion of the window. You could just put all your view logic in the code behind of the user control, but that is not a great idea from a separation of concerns perspective, and will reduce the testability and maintainability of your views.

Instead, you should use one of several popular UI composition patterns, such as Model-View-Controller, Model-View-Presenter, or Model-View-ViewModel. The appointment manager sample application uses the latter, but combined with Model-View-Controller in the editing module. Model-View-ViewModel is also known as Presentation Model, but ViewModel is a popular name for this pattern in the context of WPF.

The idea behind view models is that you define a class called a ViewModel that is similar to a presenter in Model-View-Presenter. This class sits between the view and the model (domain model). The main purpose of the view model is to offer up data to the view in the way the view wants to see it. In other words, the view model should expose properties that make hooking up the UI elements of the view easy using simple data binding expressions. The ViewModel might also expose commands as properties that the view can bind to.

By leveraging data binding in WPF and defining your UI logic in a ViewModel class, you can usually keep your code behind file empty of any code except the constructor and InitializeComponent call required for proper XAML construction. You might have to occasionally handle an event from a UI control in the code behind of the view (you can see an example in the AppointmentListView class), but if so, you should immediately dispatch that call into the ViewModel to keep all the real handling logic in the ViewModel or other non-view classes such as a controller. Doing this makes it easier to unit test the logic in your application. In fact, by separating your logic code from your view definition in this way, you can even define views in WPF with just a combination of a data template defined in a resource dictionary and the ViewModel that it binds to.

Listing 2 shows the view definition for the AppointmentListView user control. You can see it contains a ListView control that is bound to an Appointments collection property, with each of the columns specifying a DisplayMemberBinding to a property on the contained items in that collection. Thanks to the data binding mechanisms of WPF, you don’t have to specify in the view exactly where those properties are coming from or the type of the bound data object. This keeps the view nicely decoupled from the underlying data, which will be provided by the ViewModel. Also notice that there is a context menu hooked up to an edit command in the view. You’ll see more about Composite WPF commands in a little bit, but this shows the hookup for one of those commands.

Listing 3 shows the ViewModel for the appointment list view. Note that the pattern used here makes the ViewModel responsible for constructing its view through dependency injection in its constructor, and it exposes that view as a public property. This makes it possible for the module (see Listing 1) can construct the ViewModel, but can still get access to the view reference to add it to a region. The view class implements the IAppointmentListView interface, which defines an event that the ViewModel can listen to for user selections in the list. You can look at the download code for the full details on that aspect. The ViewModel also sets itself as the DataContext for the view. This allows the view to data bind to the properties exposed by the view. The Unity container constructs the AppointmentListViewModel class, and it also takes care of injecting any other dependencies that the class has (as specified by its parameterized constructor). These include the view itself, the appointment data service, and the event aggregator service (covered later).

Another important part of the design of a ViewModel is that for all properties exposed to the view for data binding, you need to make sure those properties raise change events to the view, so that the view can stay up-to-date with the underlying data. You can do this one of two ways. You can implement INotifyPropertyChanged on the ViewModel as shown in Listing 3 and raise the PropertyChanged event in the set block of each property, or you can derive the ViewModel from DependencyObject and implement the properties as DependencyProperties. The sample project uses the former, because the implementation is a little more straightforward. Implementing the properties as DependencyProperties might make sense if you were going to animate those properties in some way, because you can animate only DependencyProperties in WPF.

Adding Views to Regions
To add a view to a region, you just need a reference to the region manager service and an instance of the view. If you look again at the module in Listing 1, you’ll see these lines of code in the Initialize method:

   // C#   IRegion region = m_RegionManager.Regions[      AppointmentConstants.AppointmentViewsRegion];   region.Add(viewModel.View);   region.Activate(viewModel.View);      ' VB   IRegion region = m_RegionManager.Regions( _     AppointmentConstants.AppointmentViewsRegion)   region.Add(viewModel.View)   region.Activate(viewModel.View)

The region manager service reference was obtained through dependency injection in the constructor of the module class. The preceding code first indexes into the Regions collection with the name of the region, which returns an IRegion reference. Then it calls Add on that region, passing the view reference. In this case, the code obtains the view reference through the View property exposed by the ViewModel. Finally, to be sure the view is displayed immediately, call Activate on the region, passing the view again. It is up to the region implementation to decide whether to present a view immediately when adding it; in this case, when the region is based on a ContentControl, the view is not displayed unless you call Activate.

Composite Commands
Routed commands in WPF are very powerful and useful, but they have some shortcomings when applied to a composite application. The first is that they are entirely coupled to the visual tree?the invoker has to be part of the visual tree, and the command binding has to be tied in through the visual tree. This is because routed events are actually used under the covers to deliver command messages. The second shortcoming is that they are tightly tied in with the focus tree of the UI. You can overcome this with command targets, but that tends to be a fragile and tightly coupled solution as well. Finally, when invoking a routed command, only one command handler will actually be called, even if there is more than one in the focus tree.

As a result, if you want your command handlers to be in your presenters or view models, and you want to be able to address scenarios like a “Save All” command that will potentially have multiple handlers, you need something that goes above and beyond routed commands. Composite WPF commands do just that. CAL contains definitions for two types of commands that supplement the capabilities of routed commands: DelegateCommand and CompositeCommand. Both are based on the same ICommand interface as routed commands in WPF, but they provide the flexibility to hookup handlers in places other than the visual tree, are not tied into the focus in the UI at all, and allow multiple handlers for a single command.

ICommand defines three members:

  • CanExecute: Called by command invokers to determine if they should enable or disable their associated UI (i.e., menu item or toolbar button).
  • Executed: Called when the command is invoked.
  • CanExecuteChanged: Event fired when the state represented by CanExecute changes. This causes command invokers to re-query CanExecute to get the current enabled state of the command.

DelegateCommand is designed to target a single handler in a ViewModel, presenter, or controller (or wherever you want to put it, usually outside the visual tree). DelegateCommand lets you point (through a delegate) to the methods on some object, such as a ViewModel, that should be called for the CanExecute and Executed methods of the ICommand interface.

CompositeCommand acts as a container for a collection of command references to other commands. CompositeCommand calls CanExecute for each of the child commands, and only declares itself to be enabled when all the child commands are enabled. For example when you invoke CompositeCommand(Executed), it calls Executed on each of the child commands.

The EditAppointmentCommand hooked up to the context menu in Listing 2 is a CompositeCommand declared in the Appointment.Infrastructure shared library as a static object. This mimics the way WPF routed commands are usually defined, allowing them to be hooked up easily in any XAML declaration of a UI. The EditingController class in the Appointment.Modules.Editors module library declares a DelegateCommand named m_EditCommand hooked up in the constructor to a handling method within that controller. After hooking up the DelegateCommand to its target-handling method, the constructor adds the command to the EditAppointmentCommand CompositeCommand through the RegisterCommand method. Note that the command handling hookup can be strongly typed based on the expected command parameter type, even though invokers in WPF are all based on objects with no type safety:

   // C#   public class EditingController : IEditingController   {     DelegateCommand m_EditCommand;     public EditingController(...) { ...        m_EditCommand =            new DelegateCommand           (OnEditAppt);        AppointmentCommands.EditAppointmentCommand           .RegisterCommand(m_EditCommand);     }        void OnEditAppt(AppointmentItem item) { ... }      ' VB   Public Class EditingController      Implements IEditingController      Private m_EditCommand As _         DelegateCommand(Of AppointmentItem)      Public Sub New(...)         ...         m_EditCommand = New DelegateCommand( _            Of AppointmentItem)(AddressOf OnEditAppt)         AppointmentCommands.EditAppointmentCommand. _            RegisterCommand(m_EditCommand)      End Sub         Sub OnEditAppt(item As AppointmentItem)          ...      End Sub

Just like with WPF routed commands, if you specify only a handling method for the Executed method, then CanExecute is implicitly true always for that command.

Even though in this example there is only one child DelegateCommand added to the CompositeCommand, in general it supports adding as many child commands as your scenario dictates. You might also sometimes just declare a DelegateCommand and expose it directly to a UI for hookup. You can see an example in the AppointmentEditViewModel class, which contains the following commands exposed as properties:

   // C#   public class AppointmentEditViewModel :       INotifyPropertyChanged   {   public AppointmentEditViewModel( ... )      {         ...         SaveCommand = new            DelegateCommand(OnSave,OnCanSave);         CancelCommand = new            DelegateCommand(OnCancel);      }      public DelegateCommand SaveCommand       { get; set; }      public DelegateCommand CancelCommand       { get; set; }         ' VB   Public Class AppointmentEditViewModel      Implements INotifyPropertyChanged   Public Sub New(...)      SaveCommand = New DelegateCommand( _         Of Object)(AddressOf OnSave, _         AddressOf OnCanSave)      CancelCommand = New DelegateCommand( _         Of Object)(AddressOf OnCancel)   End Sub      Public Property SaveCommand() As _         DelegateCommand(Of Object) ...   End Property      Public Property CancelCommand() As _        DelegateCommand(Of Object)...   End Property

Now the UI elements in the corresponding view can hook up to those commands using data binding:

   

Composite Events
One key feature of CAL is the composite events mechanism. WPF routed events work great for allowing UI elements to flow events to other UI elements. However, in a composite application, the publisher of an event is often a ViewModel, controller, or other behind-the-scenes code. That’s true with subscribers as well. You don’t want to have to have direct object references between the publisher and subscriber, because those may be classes coming from two different modules that know nothing about each other. You don’t want any lifetime coupling between the publisher and subscriber either. As a result, neither normal .NET events nor WPF routed events are sufficient to address the requirements for loosely-coupled communications between modules in composite applications.

CAL includes a composite events mechanism based on the pub/sub pattern that allows you to achieve all the goals mentioned in the previous paragraph. It works like this. CAL has an event aggregator service that acts as a repository for event objects. You can see in Listing 3 that the AppointmentListViewModel obtains a reference to the IEventAggregator service through its constructor dependency injection. It uses that reference when the SelectionChanged event fires from the view. The OnSelectionChanged event handler uses the event aggregator to get a reference to a type named AppointmentSelectedEvent.

The event objects themselves are a singleton instance of an event class that defined for each discrete event that you want to publish through composite events. The event aggregator service takes care of creating those event instances and hands them out as needed. To make this happen, you define a class that derives from CompositeWpfEvent:

   // C#   public class AppointmentSelectedEvent :       CompositeWpfEvent { }         ' VB   Public Class AppointmentSelectedEvent      Inherits CompositeWpfEvent(Of AppointmentItem)   End Class

The event class sets up a relationship between a named event type and the data payload type that the event will carry when it is fired. For the AppointmentSelectedEvent, the payload type is an AppointmentItem. This type is defined in the Appointment.Infrastructure shared class library because it will be used by all modules that want to subscribe or publish this event type. The CompositeWpfEvent base class provides all the functionality for publishing and subscribing to that event type.

In addition to simply publishing and subscribing to the event, the composite events in CAL also support the following choices when you subscribe to an event:

  • Thread Dispatching: You can be notified on the publisher’s thread, the UI thread, or an asynchronous notification on a thread from the .NET thread pool.
  • Event Filtering: You can provide a filter criteria, in the form of a lambda expression of a delegate, that allows you to be notified only when your filter criteria is met, based on the contents of the data payload for the event being fired (in other words, notify me only when the appointment subject is Golf and the time is between 2:00 and 6:00 pm).
  • Weak references: By default, the event object will maintain only a weak reference to your subscriber, so that if you do not unsubscribe, the event object will not keep your object alive just for the event subscription. If you want to override this, you can simply set a flag when subscribing.

You can see in Listing 3 that when the view fires the SelectionChanged event, the ViewModel calls GetEvent on the event aggregator, and then calls Publish on the resulting event object, passing the selected AppointmentItem (it picks the first item in the list of selected items forwarded by the event raised by the view).

The subscriber in this case is the EditController class in the Appointment.Modules.Editors module library. It handles the event by activating the associated region when an item is selected. Note that the Editors and Viewers modules have no references to each other, so the publisher and subscriber are completely decoupled.

   //C#   public EditingController(...,       IEventAggregator eventAggregator)   {      ...      eventAggregator.GetEvent()         .Subscribe(OnAppointmentSelected);   }      void OnAppointmentSelected(AppointmentItem item)   {      AppointmentEditViewModel viewModel =          GetViewModelForItem(item);      if (viewModel != null)      {         m_EditPanelRegion.Activate(viewModel.View);      }   }      ' VB   Public Sub New(..., ByVal eventAggregator _      As IEventAggregator)      eventAggregator.GetEvent( _      Of AppointmentSelectedEvent)() _     .Subscribe(AddressOf OnAppointmentSelected)   End Sub      Private Sub OnAppointmentSelected(ByVal item _      As AppointmentItem)      Dim viewModel As AppointmentEditViewModel = _          GetViewModelForItem(item)      If viewModel IsNot Nothing Then         m_EditPanelRegion.Activate(viewModel.View)      End If   End Sub

Wrap Up
By this point, your head is probably spinning from all the new concepts that Composite WPF introduces. It may seem like this stuff just makes your application more complex. And when taken at the granular level of just defining a user control for some portion of your screen, it is a bit more complex in terms of the constructs you need to put in place. But it all follows a nice set of patterns that have been widely accepted as a better way of building UI applications, and the end result will be a much more loosely-coupled application with good factoring. After you get over the initial learning curve, these things become second nature to you. The advantage of having things broken out into more pieces and abstractions is that as you are building in new functionality or trying to locate something you write before, you know right where to look because there’s a clean separation of concerns. I’d encourage you to dig into the sample application in detail, and then give it a try by building up a small sample application of your own using CAL and the patterns discussed here.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist

©2024 Copyright DevX - All Rights Reserved. Registration or use of this site constitutes acceptance of our Terms of Service and Privacy Policy.