Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Catel - Part 7 of n: What’s new in Catel 2.x

0.00/5 (No votes)
16 Aug 2011 1  
This article explains the new features of Catel 2.x

Article Browser

Table of Contents

  1. Introduction
  2. General
  3. IoC containers / ServiceLocator
  4. MVVM behaviors
  5. WP7 Mango support
  6. What’s next?

1. Introduction

Welcome to part 7 of the articles series about Catel. If you haven’t read the previous article(s) of Catel yet, it is recommended that you do. They are numbered so finding them shouldn’t be too hard.

This article is all about the new Catel 2.x releases. Catel 2.0 was released on August 4th, 2011 and contains tons of new features and improvements. This article will handle the most important changes and new features.

There are some features that are important enough to name in this article, but not important enough for a separate chapter. Therefore, some of the changes are described in the general part.

2. General

This chapter describes features that are important enough to be mentioned, but do not required a separate chapter because the explanation is fairly simple.

2.1. Behaviors

Catel now offers several new behaviors out of the box. The developers of Catel are normally spoiled with the luxury of developing applications using WPF, but sometimes they need to write software using Silverlight. One of the downsides of writing applications in Silverlight is that a lot of functionality you are used to in WPF is not available. Two of these simple things are the missing DoubleClick event and the missing option to update a binding on property changed instead of lost focus.

2.1.1. DoubleClickToCommand

To easily enable developers to use these “luxuries” in Silverlight, two new behaviors are developed. The DoubleClickToCommand allows a developer to enable the DoubleClick event (which is not available) on any FrameworkElement that supports the LeftMouseButtonDown event. So, if a DoubleClick event must be added, this can simply be done using the behavior. Say there is an Image control representing a logo, and the user should be able to double-click it to edit the logo. The following code is sufficient to create this behavior:

<Image Source="/Catel.Demo;component/resources/images/catel.png">
    <i:Interaction.Triggers>
        <catel:DoubleClickToCommand Command="{Binding EditLogo}" />
    </i:Interaction.Triggers>
</Image>

2.1.2. UpdateBindingOnTextChanged

One question we get asked on a very regular basis is how to update a binding when the user types something into a textbox. By default, the binding only updates when the textbox loses the focus. Sometimes, this behavior is not enough because the developer wants to give the user feedback immediately when typing in a textbox. Or, another usage is a textbox with a filter where the filter should apply immediately after typing into a textbox.

One option to solve this problem is to subscribe to the TextChanged event in the code-behind and update the binding explicitly. A better option is to use the UpdateBindingOnTextChanged behavior that ships with Catel.

<TextBox Text="{Binding SearchParameter, Mode=TwoWay}">
    <i:Interaction.Behaviors>
        <catel:UpdateBindingOnTextChanged />
    </i:Interaction.Behaviors>
</TextBox>

2.2. IPleaseWaitService implementation for Silverlight

One of the most requested features in the new version was the implementation of the IPleaseWaitService for Silverlight. The service was already implemented for WPF, but in Silverlight the end-developers had to implement the IPleaseWaitService themselves. Most developers used the BusyIndicator control for this implementation. Therefore, a default implementation of the IPleaseWaitService using the BusyIndicator is now included.

2.3. Single namespace for all controls

All controls, windows, behaviors and more that can be used inside xaml files are all located in different namespaces to follow the .NET Framework as much as possible. In previous version of Catel, all the namespaces used in a xaml file needed to be included manually. For example, this is how the usings of a xaml file could look like:

xmlns:Controls="clr-namespace:Catel.Windows.Controls;assembly=Catel.Silverlight"
xmlns:Interactivity="clr-namespace:Catel.Windows.Interactivity;assembly=Catel.Silverlight"

Now, all namespaces are mapped to http://catel.codeplex.com. This means that the code above keeps perfectly valid, but from now on it is easier to write Catel based xaml:

xmlns:catel="http://catel.codeplex.com"

The code above is enough to reach all controls, behaviors, and all other elements that can be used inside xaml exposed by Catel.

2.4. Auditing

If you are developing a real application, then you probably want to know what your users are doing with your application. Catel offers a solution for this by providing the capability to add auditing to an application. Auditing can be used to gather statistics, see what features are actually used. With auditing, you can create and register custom auditors that can handled changes and events of view models. This way, you can gather a lot of statistics or any information that you want to gather about the user experience. Below is a list of events that can be handled:

  • OnViewModelCreating
  • OnViewModelCreated
  • OnPropertyChanging
  • OnPropertyChanged
  • OnCommandExecuting
  • OnViewModelSaving
  • OnViewModelSaved
  • OnViewModelCanceling
  • OnViewModelCanceled
  • OnViewModelClosing
  • OnViewModelClosed

The developer has all the freedom to handle one or more methods in an auditor. Of course multiple auditors are possible as well. Let’s take a look how easy it is to add auditing to an MVVM application.

2.4.1. Creating an auditor

Creating a new auditor is very simple. Create a new class, derive from AuditorBase and override the methods you are interested in. The class example tracks the event to a fake analytics framework.

/// <summary>
/// Logs all commands to a custom analytics service.
/// </summary>
public class CommandAuditor : AuditorBase
{
    private Analytics _analytics = new Analytics();
 
    /// <summary>
    /// Called when a command of a view model has just been executed.
    /// </summary>
    /// <param name="viewModel">The view model.</param>
    /// <param name="commandName">Name of the command, which is the name of the command property.</param>
    /// <param name="command">The command that has been executed.</param>
    /// <param name="commandParameter">The command parameter.</param>
    public override void OnCommandExecuted(IViewModel viewModel, string commandName, ICatelCommand command, object commandParameter)
    {
        _analytics.TrackEvent(viewModel.GetType(), "commandName");
    }
}

2.4.2. Registering an auditor

Registering a new auditor is extremely easy as you can see in the code below:

AuditingManager.RegisterAuditor(new CommandAuditor());

3. IoC containers / ServiceLocator

Before Catel 2.0, the IoC container used internally was Unity. However, this forced all users to use and configure Unity as the IoC container in their apps and required the shipping of the libraries as well. Since Catel 2.0, a different technique is used which allows the end-developer to use the IoC container technique of their choice.

3.1. Introduction to the ServiceLocator

Catel uses its own ServiceLocator implementing the IServiceLocator to gather all services required by Catel. For example, default services are the IPleaseWaitService and the IUIVisualizerService. By default, when the first view model is instantiated, Catel registers all default out of the box services to the ServiceLocator. However, it only does this when the specific services are not already registered. This allows an end-developer to register his/her own implementations of the services before any view model is instantiated (for example, at application startup).

The ServiceLocator can be instantiated, but Catel instantiates one instance that can be used and shared amongst all objects inside the same AppDomain. The ServiceLocator can be retrieved by using ServiceLocator.Instance. The ViewModelBase implements GetService which internally calls ServiceLocator.ResolveType. This way, the end-developer does not have to think about instantiating or retrieving the right ServiceLocator, but can simply use GetService to retrieve the implementation of a specific registered interface.

3.2. Using the ServiceLocator

There are several actions available using the ServiceLocator. Below are all the actions available.

3.2.1. Registering a type

Registering a type in the ServiceLocator is very simple and works like any other IoC container:

ServiceLocator.Instance.RegisterType<IPleaseWaitService, PleaseWaitService>();

3.2.2. Registering an instance of a type

Catel uses Activator.CreateInstance to create the interface implementations when the object is first resolved. However, sometimes a service constructor requires parameters or takes a long time to construct. In such cases, it is recommended to create the type manually and register the instance of the type:

var pleaseWaitService = new PleaseWaitService();
ServiceLocator.Instance.RegisterInstance<IPleaseWaitService>(pleaseWaitService);

3.2.3. Registering a type via MissingType event

The ServiceLocator gives the end-developer a last-resort chance to register a type when it is not registered in the ServiceLocator or any of the external containers. This event is very useful for logging (then the developer in the log knows exactly what type is missing from the IoC container) or it can be used to determine at runtime in a very late stage what implementation of the service must be used. To register a type via the event, subscribe to the event and then use the following code:

private void OnMissingType(object sender, MissingTypeEventArgs e)
{
    if (e.InterfaceType == typeof(IPleaseWaitService))
    {
        // Register an instance
        e.ImplementingInstance = new PleaseWaitService();
 
        // Or a type
        e.ImplementingType = typeof(PleaseWaitService);
    }
}

If both the ImplementingInstance and ImplementingType are filled, the ImplementingIntance will be used.

3.2.4. Resolving a type

To retrieve the implementation of a service, use the following code:

var pleaseWaitService = ServiceLocator.Instance.ResolveType<IPleaseWaitService>();

3.3. External containers

As explained earlier, the ServiceLocator supports external containers to make sure that the end-developer can use the IoC container of his/her choice and is not forced by Catel to use any specific IoC implementation. Registering a container is very simple and can be done at any time (but, must of course be done before service registered in the external container can be used by the ServiceLocator itself):

ServiceLocator.Instance.RegisterExternalContainer(myUnityContainer);

Registering an external makes it possible to resolve types from the ServiceLocator that are originally registered in the external container. For example, a database repository is registered in unity, but must be used inside a view model. This is what happens:

  1. End-developer registers ICustomerRepository in a Unity container
  2. End-developer registers the external container in the ServiceLocator of Catel
  3. In a view model, the developer uses GetService<ICustomerRepository>() to retrieve the repository
  4. Internally, the ServiceLocator checks which external container has the type and uses that specific container to return the type

3.4. Synchronization between containers

Until now, it all seems very nice. However, there are some aspects that are very hard and which must be taken into account. For example, think of the following issues:

  • Instance caching
  • Which container is the actual owner of a type?

3.4.1. Synchronization from external container to ServiceLocator

Internally, the ServiceLocator keeps a reference of all registered types in the internal container, all instantiated types and the original container per type. For example, think of the following situation:

  • A type is registered in Unity (but the ServiceLocator does not know whether it's a type or instance since Unity makes no difference between them)
  • A developer resolves the type in the container

The ServiceLocator checks whether there is already an instance created or requested earlier by the developer. If it already has an instance in the cache, it simply returns the instance from the cache. If not, it checks which container (it might be itself, but also one of the external containers) is the owner of the type. A container is considered the owner if the type was first found on that specific container. The ServiceLocator lets the external container (in this case Unity) instantiate the type. Then the ServiceLocator stores the instance in the instance cache so it can be easily resolved in the next call.

3.4.2. Synchronization from ServiceLocator to external containers

Great, now think of this situation:

  1. A type is registered in the ServiceLocator
  2. The type must be injected into a constructor using Unity

This is a bit harder because the type is not registered in Unity, but it is used by Unity. Luckily, the ServiceLocator is able to automatically register types in all external containers as soon as a type is registered or resolved. This way, when a type is registered in the ServiceLocator, it automatically calls ExternalContainer.RegisterType (or (ExternalContainer.RegisterInstance in case of an instance) for all external containers. This way, it is possible to register a type in the ServiceLocator, but still be able to use the powerful dependency injection of Unity.

3.4.3. Automatic or manual synchronization

By default, the ServiceLocator automatically keeps all the IoC containers in sync so the end-developer should not have any knowledge of the inner workings of the ServiceLocator. Sometimes, a developer wants to have more control and needs to disable this automatic behavior. This is possible using the following property:

ServiceLocator.Instance.AutomaticallyKeepContainersSynchronized = false;

Then it is still possible to manually synchronize the containers using the following methods:

  • ExportInstancesToExternalContainers => exports instances only
  • ExportToExternalContainers => exports instances and registered types

3.5. Supported external containers

The following external IoC containers are supported out of the box:

IoC container Version introduced Remarks
Unity 2.0 Implemented exactly the way unity is meant to be.
MEF 2.1 MEF only supports instances. Therefore, as soon as a MEF container is registered as external container, an instance will be created of all types and registered in the external MEF container. This way, it is possible to use MEF to import all services exposed by the ServiceLocator or any other external containers (such as Unity).

4. MVVM behaviors

One of the downsides of Catel was the need to derive all windows and controls from either DataWindow<TViewModel> or UserControl<TViewModel>. While these controls are really powerful and flexible, sometimes it’s just not possible to derive from one of the generic controls. For example, a company might already have a base class where all controls must derive from.

Catel includes demo applications for both WPF and Silverlight that show the differences between deriving from a generic view base or using the behaviors. Look for Catel.Examples.WPF.AdvancedDemo or Catel.Examples.SL4.AdvancedDemo.

The separation of the logic from the DataWindow<TViewModel> and UserControl<TViewModel> (from now on referred to as ViewBase<TViewModel> for the sake of simplicity) was one of the most important goals of the Catel 2.0 release. The idea was simple: create behaviors that allow the end-developers to apply the behavior to any control or window to get the same powerful behavior as the ViewBase<TViewModel>. However, there are some pitfalls since the internals of the generic base classes are quite complex.

The team started by separating the logic from the classes into new Logic classes. These are classes that contain the logic which are applied to an external control that must be injected into the Logic class. This resulted in the UserControlLogic and WindowLogic classes that contain the actual logic. The UserControl<TViewModel> and DataWindow<TViewModel> simply create an instance of the Logic classes and dispatch the actual implementation to the Logic classes.

When the first step was successful, it was time for the second step: creating behaviors that could be attached to any UserControl or Window without the need for a generic base. The UserControl implementation was fairly simple because there is no real interaction with the control except for the type of the view model. The Window implementation was much harder because the external window should be able to save, cancel and close the view model using UI controls. The next sections explain how to use the different behaviors.

4.1. WindowBehavior

The WindowBehavior allows a developer to provide the MVVM behavior of the DataWindow in every window. This is an extreme powerful addition to “regular” MVVM implementations. To enable the behavior on a window, all that is required is the following xaml code which should be located inside the window definition:

<i:Interaction.Behaviors>
    <catel:WindowBehavior x:Name="mvvmBehavior" ViewModelType="ViewModels:DemoWindowViewModel"
                                    SaveAndClose="saveAndCloseButton"
                                    CancelAndClose="cancelAndCloseButton" />
</i:Interaction.Behaviors>

Only one property on the WindowBehavior is required: ViewModelType. The ViewModelType property should be filled with the view model that should be created when the view is instantiated. In the example above, the DemoWindowViewModel will be instantiated as soon as the window is created. Then the view model is automatically set as the DataContext of the window.

There are some other (optional) properties available on the behavior as well:

Property Description
Save Calls SaveViewModel on the view model.
SaveAndClose Calls SaveViewModel on the view model, but also closes the window if SaveViewModel returns true.
Cancel Calls CancelViewModel on the view model.
CancelAndClose Calls CancelViewModel on the view model, but also closes the window.
Close Closes the window without calling SaveViewModel or CancelViewModel.

4.2. UserControlBehavior

The UserControlBehavior allows a developer to provide the MVVM behavior of the UserControl in every user control. The UserControlBehavior class takes care of all the MVVM integrations of a user control and a view model. So, where you previously had to derive a UserControl implementation from UserControl<TViewModel>, you can now create a new UserControl like any application and then add this:

<i:Interaction.Behaviors>
    <catel:UserControlBehavior x:Name="mvvmBehavior" ViewModelType="ViewModels:DemoViewModel" />
</i:Interaction.Behaviors>

This looks great, but why is there still a UserControl<TViewModel> with this terrific solution?

First of all, we have to think about all the people that are already using Catel. We don't want to break their code and provide backward compatibility. Also, the UserControl<TViewModel> implements the IViewModelContainer interface which allows chained view models in a hierarchy. If you don't need this, just go for the behavior. If you need hierarchy chains, either let the custom UserControl implement it or use the UserControl<TViewModel>.

To support nested user controls and their validation, it is important to chain views together using the IViewModelContainer interface. You can choose not to do this, but then it is important to disable SupportParentViewModelContainers for performance reasons (otherwise, the behavior will keep searching the visual tree for the parent view model).

5. WP7 Mango support

Catel 2.x fully supports Windows Phone 7 Mango. Most MVVM frameworks around are happy to announce the support for WP7 Mango, but Catel goes a bit (well, a lot) further than that.

5.1. All hardware capabilities can be emulated

Microsoft has added a new SDK for Windows Phone 7 Mango. The Windows Phone emulator had the option to simulate the location service (GPS). When Microsoft showed that they implemented support for simulating the accelerometer and the location services, I was more than happy. However, what about the compass and gyroscope? No word about those sensors (which you want to simulate as well, right?).

One of the things we decided to build was a test implementation for every sensor available on the Windows Phone 7 device. This way, you can simulate all sensors in the emulator on your development machine in an MVVM manner.

This means that the following services (including emulation services) are available in Catel:

  • IAccelerometerService
  • ICompassService
  • IGyroscopeService
  • ILocationService (GPS)
  • IVibrateService

6. What’s next?

Since the first release of Catel in December 2010, it is more feature complete than any other MVVM framework/toolkit We’re still full of ideas and working as hard as we can to realize these ideas. In the meantime, if you have any comments, ideas, improvements or what-so-ever, don’t hesitate to leave a comment or contact us. We’d love to hear your feedback.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here