Article Browser
Table of Contents
- Introduction
- General
- IoC containers / ServiceLocator
- MVVM behaviors
- WP7 Mango support
- 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.
public class CommandAuditor : AuditorBase
{
private Analytics _analytics = new Analytics();
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))
{
e.ImplementingInstance = new PleaseWaitService();
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:
- End-developer registers
ICustomerRepository
in a
Unity container
- End-developer registers the external container in the
ServiceLocator
of Catel
- In a view model, the developer uses
GetService<ICustomerRepository>()
to retrieve the repository
- 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:
- A type is registered in the
ServiceLocator
- 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.