Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / WPF

Catel - Part 5 of n: Building a WPF example application with Catel in 1 hour

Rate me:
Please Sign up or sign in to vote.
4.84/5 (21 votes)
28 Jan 2011CPOL27 min read 98.7K   3.5K   37   26
This article explains how to write a real-world application using Catel.

Catel is a brand new framework (or enterprise library, just use whatever you like) with data handling, diagnostics, logging, WPF controls, and an MVVM Framework. So, Catel is more than "just" another MVVM Framework or some nice Extension Methods that can be used. It's more like a library that you want to include in all the (WPF) applications you are going to develop in the near future.

This article explains how to write unit tests for MVVM using Catel.

Article browser

Table of contents

  1. Introduction
  2. Functional requirements
    1. Content providers
    2. User interface
  3. Creating the project
    1. Features handled
    2. Directory structure
    3. Creating projects
      1. SocialMediaStream
      2. SocialMediaStream.Library
      3. SocialMediaStream.Providers.Facebook
      4. SocialMediaStream.Providers.Twitter
      5. SocialMediaStream.Test
    4. Enable logging
    5. Setting up themes and styles
  4. Creating the models
    1. Features handled
    2. Interfaces
      1. ISocialMediaEntry
      2. ISocialMediaProvider
      3. ISocialMediaProviderFactory
    3. Base provider
    4. Twitter and Facebook providers
    5. SocialMediaProviderFactory
  5. Creating the View Models
    1. Features handled
    2. SocialMediaEntryViewModel
    3. MainWindowViewModel
  6. Creating the Views
    1. Features handled
    2. SocialMediaEntry
    3. MainWindow
    4. Setting up IoC containers
    5. TraceOutputWindow for additional debug info
    6. Dynamic theme switching
  7. Conclusion

Introduction

Welcome to part 5 of the article series about Catel. This article explains how to write a real-world application using 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.

The goal of this article is represented by the following picture:

Image 1

Idea to application

O boy, did the title promise too much? I don't think so! This article will show you how to write an application using Catel. This article includes using Catel in the following fields:

  • Data handling

    Models are written using DataObjectBase, and include validation to make sure no invalid families can be saved (how can a family be invalid, but that's another question for another time). The example also uses serialization out-of-the-box to load/save data from/to disk.

  • WPF controls

    The application is built by using controls of the Catel library. It includes popup windows, controls, and error handling.

  • MVVM

    The application uses the MVVM Framework that ships with Catel. In this application, the real power of the framework becomes visible. This example uses View Models, services, nested controls, validation, and more. Also, the strength of the combination of a View Model with an intelligent Model in the background will become visible in this article.

  • Unit testing

    Unit tests are very important to improve the quality of a software. One of the strongest points of the MVVM pattern is that it can be excellently combined with unit testing, and in this article, we will see how.

Each part of this article explains what to do step by step, but will also explain what parts of Catel are used during the specific steps. Each step can be skipped if required (for example, if you are only interested in how View Models are created). This way, you can fully focus on the points of the framework that you need.

This article will implement the application using the MVVM pattern. If you don't know what the MVVM pattern is, or if you are not yet familiar yet, read this very short introduction to MVVM. It is also recommended that you read the other articles of Catel first, but if you are smart enough, you should be able to get a grip of the framework by only viewing this example application.

In this article, we will first start by creating the project in Visual Studio. Then, we will build the application up from scratch by beginning with the Models (the data objects themselves). After the Models, the View Models (how are we going to show the Models to the user) will be created. And, finally, we will create the Views which will be the UI that the user will actually see. In real-life, a designer would do the last step based on the ViewModel, if you are lucky enough to be on a project with a GUI designer.

This example does not include writing unit tests, since it's unfair to write a whole application in one hour including unit tests. But, the MVVM framework gives you the ability to write unit tests whenever you please. Also, since I am not a designer, I don't want to put too much time into the UI itself. Therefore, the app might look a little bit sluggish, but hey, aren't all real-life applications a little bit sluggish?

When using Catel, it is very important to use the provided code snippets. In the first place, it saves you a lot of time, but it also saves you from mistakes. I have seen too much developers copy/paste dependency properties and forgetting to change the owner of the property. This will lead to very strange and unpredictable errors, since you will only get an error if you hit both the controls that register the dependency property on the same owner twice. An advantage of Catel is that it automatically determines the owner, so the dependency property mistake will not be made when using Catel. Still, it is very important to use the code snippets to correctly implement the code that uses Catel.

2. Functional requirements

Like every project, we need to start with the functional requirements. The functional requirements for this article are to make a hip, but still functional, and not too complex application. This made us thinking: what is hip, and still fairly easy to build and understand. The answer is social media. Maybe you are thinking: no, not another social media application. However, you don’t have to use or buy this application, it’s for demo purposes only, so if you don’t like the concept, just use it as a learning point.

I have an HTC Desire phone with Android, and it has a friend stream widget. This is a very cool widget that keeps me up-to-date with both Twitter and Facebook. I want to use this application as a good example of how social media should be implemented. I don’t want it to be too functional, that would divert from the actual purpose of this article.

2.1. Content providers

To support several social media sites, a content provider must be created. We will create an interface (or base class) that every content provider for every social network needs to provide.

2.2. User interface

What follows now are a few sketches on how the UI should look in the end, but don’t blame me if I can’t make it look nice.

Image 2

The messages should be sorted by date/time, newest on top.

3. Creating the project

We are really going to start from scratch. We are going to create the solution and the projects. In this part, the right project types are created, and the libraries are linked to the projects. The reason that these steps are included is to show the recommended way of including third party libraries into a solution, and keep the solution organized and clean.

3.1. Features handled

  1. log4net (logging);
  2. themes and styles.

3.2. Directory structure

It’s very important to be consistent in organizing your projects. During the years, I found a structure that I like very much, since it gives me (and all people using Open-Source software that I develop) a very clear overview of what’s included in the package. Below is a screenshot of the directory structure:

Image 3

  • Design: Contains the designs for the project, such as logo files, icons, etc. I like to keep this together with a software project to make sure all are saved in the source code repository.
  • Doc: Contains the documentation for the project. This can be as simple as a readme.txt and history.txt, but can also include functional and technical designs.
  • Lib: It is a good practice to keep the libraries used inside a project in the same directory. This way, you can simply replace libraries with a newer version and update all your projects at once instead of messing with files everywhere.
  • Output: The output folder isn’t created by you. If you set the output directory for your projects correctly (“..\..\output\Release\” for Release builds, “..\..\output\Debug\” for Debug builds), Visual Studio or the compiler will create the directories. But it’s nice not have to dig into the bin directory of each project, but that all files are written to the same output directory.
  • Src: This contains the source code, thus the solution, project files, and the actual source files.

3.3. Creating projects

It is very important to create a project that is maintainable and testable. Therefore, we want to make sure that the application uses “Separation of Concerns” (SoC). In its own way, MVVM is also an implementation of SoC, namely by dividing the UI, UI logic, and business logic from each other.

For this project to follow the rules of SoC, we need to create the following projects structure. It’s split up into separate folders because I like to keep things simple and clear. This may come in handy when projects become very large (think about 20 – 25 projects in one solution). First, let’s see how the projects structure looks like; after that, I can tell you more about the meaning of each project.

Image 4

3.3.1. SocialMediaStream

This is the main application. It’s a WPF application (.NET 3.5 SP1), and is set as startup project, so it is started every time we are going to debug the application. This project includes the UI, but also the ViewModels since the ViewModels define the behavior of the UI.

This project will also be responsible to “couple” all behaviors together via Inversion of Control.

3.3.2. SocialMediaStream.Library

This .NET Class Library is the SDK of the application. Developers who want to write providers for the application will need this library. It contains the interface definitions and some “convenience” classes such as a base implementation of the ISocialMediaProvider interface.

3.3.3. SocialMediaStream.Providers.Facebook

This .NET Class Library is responsible for providing the Facebook implementation.

3.3.4. SocialMediaStream.Providers.Twitter

This .NET Class Library is responsible for providing the Twitter implementation.

3.3.5. SocialMediaStream.Test

This unit test project contains all the unit tests written for the application. Writing unit tests is out of the scope for this article, but since I needed them to make sure the app is somewhat stable, I thought including them would not harm anyone.

3.4. Enable logging

We want to be able to see what is actually happening, even after we deploy the software to all our clients. Therefore, logging is very important. Catel uses log4net, and even provides additional extension methods to make it easier to log exceptions and messages with formatting.

The use of log4net is very simple, and can be done via configuration. Below is an example configuration:

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" 
      type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
  </configSections>

  <log4net>
    <!-- Rolling file (actual log file) -->
    <appender name="RollingFileAppender" 
         type="log4net.Appender.RollingFileAppender">
      <file 
         value="${ALLUSERSPROFILE}\Catel\SocialMediaStream\SocialMediaStream.log"/>
      <appendToFile value="true"/>
      <maxSizeRollBackups value="20"/>
      <maximumFileSize value="10MB"/>
      <rollingStyle value="Size"/>
      <staticLogFileName value="true"/>
      <layout type="log4net.Layout.PatternLayout">
        <header value="[Application start]
"/>
        <footer value="[Application end]
"/>
        <conversionPattern value="%date - %-5level - %logger - %message%newline"/>
      </layout>
    </appender>

    <!-- Trace -->
    <appender name="TraceAppender" 
         type="log4net.Appender.TraceAppenderEx, Catel.Core">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date - %message%newline"/>
      </layout>
    </appender>

    <!-- Setup the root category, add the appenders and set the default level -->
    <root>
      <!-- Level can be FATAL, ERROR, WARN, INFO, DEBUG, ALL -->
      <level value="ALL"/>
      <appender-ref ref="RollingFileAppender"/>
      <appender-ref ref="TraceAppender"/>
    </root>
  </log4net>

</configuration>

To make sure that log4net reads the configuration, add the XmlConfigurator attribute to your assembly info:

XML
[assembly: log4net.Config.XmlConfigurator(Watch = true)]

3.5. Setting up themes and styles

To set up themes, we has to do two things. First, let’s start by adding the theme into the application dictionary:

XML
<Application x:Class="SocialMediaStream.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="/UI/Windows/MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary Source="/Catel.Windows;component/themes/generic.xaml" />
    </Application.Resources>
</Application>

One of the nice things about Catel is that it is able to fix the margins of user controls. This way, you no longer have to worry about setting the margins for user controls to correct the spacing. The use of the StyleHelper class that is responsible for this behavior is very simple:

C#
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        StyleHelper.CreateStyleForwardersForDefaultStyles();

        base.OnStartup(e);
    }
}

We will implement dynamic theme switching later in this article, since it is one of the last things to do.

4. Creating the Models

Creating the Models is important. In our case, we are going to create Models that are savable. In most business applications, an ORM mapper (like Entity Framework, NHibernate, LLBLGen Pro, etc.) is used, but including a database in our example is not required and will only distract from the core functionality of the software. A nice side-effect is that the DataObjectBase class (the most important class of all) is used for the Models, and therefore this example gives a good idea of what the class is capable of. However, I wanted to make sure to mention that Catel is fully compatible with any Model implementation as long as the Model implements the INotifyPropertyChanged interface, and if it doesn't, it shouldn't even be called a Model.

4.1. Features handled

  • DataObjectBase (Catel.Core);
  • Contracts via interfaces.

4.2. Interfaces

For the sake of Separation of Concerns (SoC), it is very important to create interfaces to define the contract between the different layers of the application.

4.2.1. ISocialMediaEntry

For the main application to be able to communicate with providers, there needs to be some sort of contract. This is normally done in the form of an interface so the objects can easily be mocked in unit tests. Below is the interface for a social media entry:

Image 5

4.2.2. ISocialMediaProvider

Now that we have defined what information a social media entry can contain, it’s time to decide how to communicate with the providers. So let’s create an interface defining these properties and a method to get the updates from that specific provider:

Image 6

We now have a contract between the main application and the providers that can be added as plug-ins. The only thing providers have to do is implement the ISocialMediaProvider interface, and it will be available for the application.

Since the providers of social media (at least the ones included in this article) use OAuth, there is no need to provide any username and/or password.

4.2.3. ISocialMediaProviderFactory

As I told you earlier in this article, it’s very important to separate your concerns. Therefore, we want to be able to create different factories that will find the social media providers. For example, in this application, we simply want to list all the assemblies in the current working directory and see if they contain any types that implement the SocialMediaProviderBase class. But imagine that you want to search for Web Services, a database, or something else. Therefore, we first need to define what the factory should do and create an interface for it:

Image 7

Now that we have created an interface for the factory, we can create several implementations, and decide via configuration what factory to use. This allows us to change the factory from LocalSocialMediaProviderFactory to a WebServicesSocialMediaProvider or a DatabaseSocialMediaProvider.

4.3. Base provider

To simplify development for the providers, we will create a base class that will already implement the DataObjectBase class. This way, provider developers only have to implement the GetLast10Updates method and care about the actual connection with the social media they are responsible for. Thanks to the dataobject and dataproperty code snippets, the class below is created in just a few minutes' time:

C#
/// <summary>
/// SocialMediaProviderBase Data object class which fully
/// supports serialization, property changed notifications,
/// backwards compatibility and error checking.
/// </summary>
#if !SILVERLIGHT
[Serializable]
#endif
public abstract class SocialMediaProviderBase : 
       DataObjectBase<SocialMediaProviderBase>, ISocialMediaProvider
{
    #region Variables
    #endregion

    #region Constructor & destructor
    /// <summary>
    /// Initializes a new object from scratch.
    /// </summary>
    /// <param name="providerName">Name of the provider.</param>
    protected SocialMediaProviderBase(string providerName)
    {
        Name = providerName;
    }

#if !SILVERLIGHT
    /// <summary>
    /// Initializes a new object based on <see cref="SerializationInfo"/>.
    /// </summary>
    /// <param name="info"><see cref="SerializationInfo"/>
    /// that contains the information.</param>
    /// <param name="context"><see cref="StreamingContext"/>.</param>
    protected SocialMediaProviderBase(SerializationInfo info, 
                                      StreamingContext context)
        : base(info, context) { }
#endif
    #endregion

    #region Properties
    /// <summary>
    /// Gets the name of the provider.
    /// </summary>
    /// <value>The name of the provider.</value>
    public string Name
    {
        get { return GetValue<string>(NameProperty); }
        private set { SetValue(NameProperty, value); }
    }

    /// <summary>
    /// Register the Name property so it is known in the class.
    /// </summary>
    public static readonly PropertyData NameProperty = 
           RegisterProperty("Name", typeof(string), string.Empty);
    #endregion

    #region Methods
    /// <summary>
    /// Gets the last 10 updates from the provider source.
    /// </summary>
    /// <returns>
    ///    <see cref="IEnumerable{ISocialMediaEntry}"/> containing all the updates.
    /// </returns>
    public abstract IEnumerable<ISocialMediaEntry> GetLast10Updates();
    #endregion
}

4.4. Twitter and Facebook providers

I don’t want to dive too deep into the support for Twitter and Facebook, because this is not where the article is about. I did a quick search on Open-Source libraries for both providers and found these two:

  • Tweetsharp: TweetSharp is the most complete and most effective client library for communicating with Twitter. TweetSharp works how you want to: simple service, fluent interface, or LINQ provider. It's designed so that you write less, and get more, from Twitter's API.
  • Facebook: The Facebook C# SDK helps .NET developers build web, desktop, Silverlight, and Windows Phone 7 applications that integrate with Facebook.

If you are interested in how the libraries work, either check out the source code in this article or view the official websites for more information.

I must admit that I smuggled some with the time. Writing the Twitter and Facebook providers was a real pain in the ***. It was my first experience with OAuth, and both libraries I picked contained bugs that I walked into. You might call it destiny, I call it bad luck. Anyway, writing a whole app including the providers is not doable in one hour, but if there was a good SDK that came with good examples, it should be doable in theory. Still, writing this app without providers in an hour is extremely fast (I hope you still believe that now :)).

4.5. SocialMediaProviderFactory

How do we determine what providers to use? We don’t want to create references to the Facebook and Twitter providers. We also want to make our application “future proof” so we don’t have to re-compile the application when a new provider is created. A great solution for this is the Factory pattern. This way, we create a single class responsible for finding and instantiating the providers.

For the sake of simplicity, this example application will only provide a factory that lists the assemblies inside the current working directory. The implementation is very simple, and should speak for itself:

C#
/// <summary>
/// Factory responsible to create all
/// <see cref="ISocialMediaProvider"/> instances.
/// </summary>
public class SocialMediaProviderFactory : ISocialMediaProviderFactory
{
    #region Variables
    private static readonly List<ISocialMediaProvider> _providers = 
                            new List<ISocialMediaProvider>();
    #endregion

    /// <summary>
    /// Gets the <see cref="ISocialMediaProvider"/>
    /// instances in the current working directory.
    /// </summary>
    /// <returns><see cref="IEnumerable{ISocialMediaProvider}"/>
    ///   containing all providers in the current working directory.</returns>
    public IEnumerable<ISocialMediaProvider> GetProviders()
    {
        lock (_providers)
        {
            if (_providers.Count > 0)
            {
                return _providers;
            }

            string[] files = 
              Directory.GetFiles(Environment.CurrentDirectory, "*.dll");
            foreach (string file in files)
            {
                try
                {
                    Assembly assembly = Assembly.LoadFile(file);
                    foreach (Type type in assembly.GetTypes())
                    {
                        if (type.IsSubclassOf(typeof(SocialMediaProviderBase)))
                        {
                            _providers.Add((ISocialMediaProvider)
                                    Activator.CreateInstance(type));
                        }
                    }
                }
                catch (Exception)
                {
                    // Continue
                }
            }

            return _providers;
        }
    }
}

5. Creating the View Models

Now that the Models are all set-up, it's time to think about the application and how the user should interact with the data (the Models). Since you need to be able to unit-test UI functionality, you need the logic to be testable without user interaction. The View Models are a great place to locate the logic of the View and determine what parts of the Models should be published to the UI (the View, and eventually the user), and how they should be published (should the user pick from a list, or is it text, or should specific fields be read-only?). The View Models are very easy to create, especially when the Model is correctly implemented. 

5.1. Features handled

  • ViewModelBase (Catel.Windows).

5.2. SocialMediaEntryViewModel

There are several ways to divide code and UI. Some prefer using ItemTemplates for ItemsControls, I prefer creating a separate user control for these items. I have the following reasons to separate the UI (and its logic) into a separate control and ViewModel:

  • A separate user control for an item inside an ItemsControl control gives you the option to create separate View Models.
  • Soon or later, clients will request commands that should be executed on items inside an ItemsControl. This is very hard to accomplish, since the only way to know which item to execute the command for is by creating an additional Selectedxxx property or binding the command parameter via special mediator classes.
  • Sometimes, it’s just not possible to do everything inside the View Model containing the collection. For example, in our application, ISocialMediaEntry has a Photo property which is of the type System.Drawing.Bitmap. We can’t show this image directly in WPF, thus we need to convert it to a BitmapSource class. This isn’t and shouldn’t be the responsibility of the View Model containing the items, but the responsibility of the item itself.

Enough talk about the reasons why I decided to go for a separate item user control. Let’s go and see how it is implemented. The View Model looks very large, but thanks to the vm, vmprop, vmpropmodel, vmpropviewmodeltomodel, and vmpropcommandwithcanexecute code snippets, this class is created in just a few minutes:

C#
/// <summary>
/// SocialMediaEntry view model.
/// </summary>
public class SocialMediaEntryViewModel : ViewModelBase
{
    #region Constructor & destructor
    /// <summary>
    /// Initializes a new instance
    /// of the <see cref="SocialMediaEntryViewModel"/> class.
    /// </summary>
    /// <param name="socialMediaEntry">The social media entry.</param>
    /// <exception cref="ArgumentNullException">The
    /// <param name="socialMediaEntry"/> is <c>null</c>.</exception>
    public SocialMediaEntryViewModel(ISocialMediaEntry socialMediaEntry)
        : base()
    {
        if (socialMediaEntry == null)
        {
            throw new ArgumentNullException("socialMediaEntry");
        }

        SocialMediaEntry = socialMediaEntry;

        if (socialMediaEntry.Photo != null)
        {
            Photo = socialMediaEntry.Photo.ConvertBitmapToBitmapSource();
        }

        OpenLink = new Command<object, object>(OnOpenLinkExecute, 
                                               OnOpenLinkCanExecute);
    }
    #endregion

    #region Properties
    /// <summary>
    /// Gets the title of the view model.
    /// </summary>
    /// <value>The title.</value>
    public override string Title { get { return "Social Media Entry"; } }

    #region Models
    /// <summary>
    /// Gets or sets the social media entry.
    /// </summary>
    [Model]
    private ISocialMediaEntry SocialMediaEntry
    {
        get { return GetValue<ISocialMediaEntry>(SocialMediaEntryProperty); }
        set { SetValue(SocialMediaEntryProperty, value); }
    }

    /// <summary>
    /// Register the SocialMediaEntry property so it is known in the class.
    /// </summary>
    public static readonly PropertyData SocialMediaEntryProperty = 
           RegisterProperty("SocialMediaEntry", typeof(ISocialMediaEntry));
    #endregion

    #region View model
    /// <summary>
    /// Gets or sets the photo.
    /// </summary>
    /// <remarks>
    /// This is not an automatic ViewModelToModel property mapping
    /// because we need to convert a <see cref="System.Drawing.Bitmap"/>
    /// into a <see cref="BitmapSource"/>.
    /// </remarks>
    public BitmapSource Photo
    {
        get { return GetValue<BitmapSource>(PhotoProperty); }
        set { SetValue(PhotoProperty, value); }
    }

    /// <summary>
    /// Register the Photo property so it is known in the class.
    /// </summary>
    public static readonly PropertyData PhotoProperty = 
                  RegisterProperty("Photo", typeof(BitmapSource));

    /// <summary>
    /// Gets or sets the message.
    /// </summary>
    [ViewModelToModel("SocialMediaEntry")]
    public string Message
    {
        get { return GetValue<string>(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    /// <summary>
    /// Register the Message property so it is known in the class.
    /// </summary>
    public static readonly PropertyData MessageProperty = 
                  RegisterProperty("Message", typeof(string));

    /// <summary>
    /// Gets or sets the message preview.
    /// </summary>
    [ViewModelToModel("SocialMediaEntry")]
    public string MessagePreview
    {
        get { return GetValue<string>(MessagePreviewProperty); }
        set { SetValue(MessagePreviewProperty, value); }
    }

    /// <summary>
    /// Register the MessagePreview property so it is known in the class.
    /// </summary>
    public static readonly PropertyData MessagePreviewProperty = 
                  RegisterProperty("MessagePreview", typeof(string));

    /// <summary>
    /// Gets or sets the author.
    /// </summary>
    [ViewModelToModel("SocialMediaEntry")]
    public string Author
    {
        get { return GetValue<string>(AuthorProperty); }
        set { SetValue(AuthorProperty, value); }
    }

    /// <summary>
    /// Register the Author property so it is known in the class.
    /// </summary>
    public static readonly PropertyData AuthorProperty = 
                  RegisterProperty("Author", typeof(string));

    /// <summary>
    /// Gets or sets the timestamp.
    /// </summary>
    [ViewModelToModel("SocialMediaEntry")]
    public DateTime Timestamp
    {
        get { return GetValue<DateTime>(TimestampProperty); }
        set { SetValue(TimestampProperty, value); }
    }

    /// <summary>
    /// Register the Timestamp property so it is known in the class.
    /// </summary>
    public static readonly PropertyData TimestampProperty = 
                  RegisterProperty("Timestamp", typeof(DateTime));

    /// <summary>
    /// Gets or sets the url.
    /// </summary>
    [ViewModelToModel("SocialMediaEntry")]
    public string Url
    {
        get { return GetValue<string>(UrlProperty); }
        set { SetValue(UrlProperty, value); }
    }

    /// <summary>
    /// Register the Url property so it is known in the class.
    /// </summary>
    public static readonly PropertyData UrlProperty = 
                  RegisterProperty("Url", typeof(string));
    #endregion

    #region Commands
    /// <summary>
    /// Gets the OpenLink command.
    /// </summary>
    public Command<object, object> OpenLink { get; private set; }

    /// <summary>
    /// Method to check whether the OpenLink command can be executed.
    /// </summary>
    /// <param name="parameter">The parameter of the command.</param>
    private bool OnOpenLinkCanExecute(object parameter)
    {
        return !string.IsNullOrEmpty(Url);
    }

    /// <summary>
    /// Method to invoke when the OpenLink command is executed.
    /// </summary>
    /// <param name="parameter">The parameter of the command.</param>
    private void OnOpenLinkExecute(object parameter)
    {
        var processService = GetService<IProcessService>();
        processService.StartProcess(Url);
    }
    #endregion
    #endregion

    #region Methods
    #endregion
}

This View Model consists of several parts. I will explain them one by one. Let’s start with the constructor. As you can see, there is only one constructor accepting an instance of ISocialMediaEntry. There is no way to fool the View Model by passing null, it will immediately throw an ArgumentNullException. The reason why there is only one constructor is the combination of UserControl<TViewModel> and the View Model. If the View Model only contains one constructor, UserControl<TViewModel> will automatically try to construct the View Model based on the current data context of the user controls. Sounds quite complex, but in practice, it isn’t. This control is going to be used inside an ItemsControl. The data context of each item is set to the item it represents (in our case, an instance of ISocialMediaEntry). As soon as the data context is set on the user control, it checks the attached View Model to see if it contains a constructor accepting the data context. If it does (and it does in our case), it will create the View Model and set it as the new data context.

Next, you see that we have a property declared with the Model attribute. It is the instance of the IsocialMediaEntry that is injected into the View Model via the constructor. Thanks to the Model attribute, it is possible to use the ViewModelToModel attribute.

All other properties, exception for Title and Photo, are decorated with the ViewModelToModel attribute. This is a convenience attribute so you don’t have to map the properties from/to the Model. In other words, when the property Url is decorated with the ViewModelToModel attribute, it tells the View Model to set the value of Url to the SocialMediaEntry.Url value as soon as the Model or the property on the Model changes. If you are creating an editable View Model, this attribute is also responsible for mapping changes from the UI back to the Model.

We also defined a command. It contains the CanExecute method (it’s only possible to open a link if there is a link) and the Execute method, which uses the IProcessService to start a new process for the URL. This results in the default browser opening the URL.

Last but not least, let’s take a closer look at the constructor again. As told earlier, as soon as the SocialMediaEntry property is set, all properties decorated with ViewModelToModel are automatically set. Since the Photo property of ISocialMediaEntry is of type System.Drawing.Bitmap, we need to convert it to a BitmapSource. Therefore, we don’t want to use automatic mappings, but we convert the Photo object ourselves via an Extension Method available in Catel.

5.3. MainWindowViewModel

We have discussed the most complex View Model of the application in the previous paragraph, if you can even call it complex. Let’s take a look at the View Model for the main window of the application. Again, we start with showing the code first, and then I will explain the code part by part.

Again, it might sound impossible, but this View Model is created within 10 minutes. The most complex was to implement the Initialize and Refresh methods, which required some actual coding. The rest of the view model is created using the vm, vmprop, and vmcommand code snippets.

C#
/// <summary>
/// MainWindow view model.
/// </summary>
public class MainWindowViewModel : ViewModelBase
{
    #region Variables
    private readonly ISocialMediaProviderFactory _socialMediaProviderFactor;
    private readonly List<ISocialMediaProvider> 
      _enabledSocialMediaProviders = new List<ISocialMediaProvider>();
    #endregion

    #region Constructor & destructor

    /// <summary>
    /// Initializes a new instance
    /// of the <see cref="MainWindowViewModel"/> class.
    /// </summary>
    /// <remarks>
    /// This constructor is created to use an IoC container.
    /// </remarks>
    public MainWindowViewModel(): this(Catel.IoC.UnityContainer.
      Instance.Container.Resolve<ISocialMediaProviderFactory>()) { }

    /// <summary>
    /// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
    /// </summary>
    /// <param name="socialMediaProviderFactory">
    ///   The social media provider factory.</param>
    /// <remarks>
    /// This constructor is created to allow custom dependency injection.
    /// </remarks>
    public MainWindowViewModel(ISocialMediaProviderFactory 
                               socialMediaProviderFactory)
    {
        _socialMediaProviderFactor = socialMediaProviderFactory;

        VisitCatel = new Command<object>(OnVisitCatelExecute);
        Refresh = new Command<object>(OnRefreshExecute);
    }
    #endregion

    #region Properties
    /// <summary>
    /// Gets the title of the view model.
    /// </summary>
    /// <value>The title.</value>
    public override string Title { get { return "Social Media Stream"; } }

    #region Models
    #endregion

    #region View model
    /// <summary>
    /// Gets or sets items.
    /// </summary>
    public ObservableCollection<ISocialMediaEntry> SocialMediaEntries
    {
        get { return GetValue<ObservableCollection<ISocialMediaEntry>>(
                     SocialMediaEntriesProperty); }
        set { SetValue(SocialMediaEntriesProperty, value); }
    }

    /// <summary>
    /// Register the SocialMediaEntries property so it is known in the class.
    /// </summary>
    public static readonly PropertyData SocialMediaEntriesProperty = 
      RegisterProperty("SocialMediaEntries", 
      typeof(ObservableCollection<ISocialMediaEntry>));
    #endregion
    #endregion

    #region Commands
    /// <summary>
    /// Gets the VisitCatel command.
    /// </summary>
    public Command<object> VisitCatel { get; private set; }

    /// <summary>
    /// Method to invoke when the VisitCatel command is executed.
    /// </summary>
    /// <param name="parameter">The parameter of the command.</param>
    private void OnVisitCatelExecute(object parameter)
    {
        var processService = GetService<IProcessService>();
        processService.StartProcess("http://catel.codeplex.com");
    }

    /// <summary>
    /// Gets the Refresh command.
    /// </summary>
    public Command<object> Refresh { get; private set; }

    /// <summary>
    /// Method to invoke when the Refresh command is executed.
    /// </summary>
    /// <param name="parameter">The parameter of the command.</param>
    private void OnRefreshExecute(object parameter)
    {
        RefreshSocialMedia();
    }
    #endregion

    #region Methods
    /// <summary>
    /// Initializes the object by setting default values.
    /// </summary>     
    protected override void Initialize()
    {
        IEnumerable<ISocialMediaProvider> socialMediaProviders = 
                  _socialMediaProviderFactor.GetProviders();
        foreach (ISocialMediaProvider socialMediaProvider in socialMediaProviders)
        {
            var messageService = GetService<IMessageService>();
            if (messageService.Show(string.Format(
                "Do you want to support '{0}'?", socialMediaProvider.Name),
                string.Format("{0} support", socialMediaProvider.Name), 
                              MessageButton.YesNo) == MessageResult.Yes)
            {
                _enabledSocialMediaProviders.Add(socialMediaProvider);
            }
        }

        RefreshSocialMedia();
    }

    /// <summary>
    /// Refreshes all the social media entries.
    /// </summary>
    private void RefreshSocialMedia()
    {
        var pleaseWaitService = GetService<IPleaseWaitService>();
        pleaseWaitService.Show("Retrieving updates from all providers");

        List<ISocialMediaEntry> entries = new List<ISocialMediaEntry>();

        foreach (ISocialMediaProvider socialMediaProvider 
                 in _enabledSocialMediaProviders)
        {
            pleaseWaitService.UpdateStatus("Retrieving updates from " + 
                                           socialMediaProvider.Name);
            entries.AddRange(socialMediaProvider.GetLast10Updates());
        }

        SocialMediaEntries = new ObservableCollection<ISocialMediaEntry>(
                entries.OrderByDescending(entry => entry.Timestamp));

        pleaseWaitService.Hide();
    }
    #endregion
}

Like in the previous chapter, let’s start with the constructor. Since this is the View Model, it does not require any special constructors. DataWindow<TViewModel> tries to construct the View Model using the empty constructor and sets it as the data context. However, in our example, we have two constructors, one with and one without arguments. The reason for this is explained a bit further in this article in the part Setting up IoC containers. So, until then, just assume there is a default constructor without arguments so the View Model can be constructed automatically.

Next, we have a collection of ISocialMediaEntry objects, which is obvious because we need to display a list of objects in our View. There are also two commands, one to visit the website of Catel, and one to refresh the social media manually.

This brings us to the most interesting part of the View Model for now: the use of the Factory and retrieving the updates of the social media. As explained earlier in this article, we really want our View Models to be testable and loosely coupled as possible. Therefore, we only use interfaces which allow us to do so. The View Model is injected with an instance of ISocialMediaProviderFactory so the View Model is able to retrieve a list of available ISocialMediaProvider instances. In the Initialize method, the View Model asks the user for each social media provider whether the user wants the application to support the specific provider. If so, it is stored in a special list containing all enabled social media providers.

Finally, we have a RefreshSocialMedia method that shows the PleaseWaitWindow via the IPleaseWaitService of the View Model. Then it retrieves all the updates for all enabled social media providers. Keep in mind that all of this is happening in the UI thread, so when an update takes a long time, that’s the reason. It’s better to use an asynchronous approach, but that would only increase the difficulty of our example application. I’ll leave implementing asynchronous updates up to the reader as a good exercise.

6. Creating the Views

The Views are the way the user experiences the application and is able to view and modify data. A View is, in an ideal situation, not created by a developer, but by a designer. However, there aren't much clients that actually want to spend money on designers (same for testers, even though they are very important!). I don't want to lose too much time on creating a very nice user interface, so the UI will be a bit basic. Even though the UI is basic, it's fully functional, and uses some controls and windows of Catel.

6.1. Features handled

  • DataWindow (Catel.Windows);
  • IoC containers;
  • UserControl (Catel.Windows);
  • TraceOutputWindow (Catel.Windows).

6.2. SocialMediaEntry

The user control consists of some simple XAML (notice that it derives from Catel.Windows.UserControl<TViewModel> instead of UserControl):

XML
<Controls:UserControl x:Class="SocialMediaStream.UI.Controls.SocialMediaEntry"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:Controls="clr-namespace:Catel.Windows.Controls;assembly=Catel.Windows"
      xmlns:ViewModels="clr-namespace:SocialMediaStream.UI.ViewModels"
      xmlns:i="clr-namespace:System.Windows.Interactivity;
               assembly=System.Windows.Interactivity"
      xmlns:Commands="clr-namespace:Catel.MVVM.Commands;assembly=Catel.Windows"
      x:TypeArguments="ViewModels:SocialMediaEntryViewModel">

    <ContentControl>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="MouseDoubleClick">
                <Commands:EventToCommand 
                   Command="{Binding OpenLink}" 
                   DisableAssociatedObjectOnCannotExecute="False" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
        
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="32" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <Image Grid.Row="0" Grid.Column="0" 
               Source="{Binding Photo}" Width="32" Height="32" />

            <Label Grid.Row="0" Grid.Column="1">
                <TextBlock TextWrapping="Wrap" 
                  Text="{Binding MessagePreview}" ToolTip="{Binding Message}" />
            </Label>

            <Label Grid.Row="1" Grid.ColumnSpan="2" 
               Content="{Binding Author}" HorizontalAlignment="Left" />
            <Label Grid.Row="1" Grid.ColumnSpan="2" 
               Content="{Binding Timestamp}" HorizontalAlignment="Right" />
        </Grid>
    </ContentControl>

</Controls:UserControl>

The interesting point is that this class also implements some kind of Interaction.Triggers. This is for the EventToCommand trigger to enable events to be converted to commands of a View Model. In this case, a MouseDoubleClick event will be converted to the OpenLink command of the View Model.

The code-behind of the user control is very clean, thanks to MVVM:

C#
/// <summary>
/// Interaction logic for SocialMediaEntry.xaml
/// </summary>
public partial class SocialMediaEntry : UserControl<SocialMediaEntryViewModel>
{
    /// <summary>
    /// Initializes a new instance of the <see cref="SocialMediaEntry"/> class.
    /// </summary>
    public SocialMediaEntry()
    {
        InitializeComponent();
    }
}

6.3. MainWindow

Like the user control discussed before, the main window consists of two parts. The first is the XAML (notice that it derives from Catel.Windows.DataWindow<TViewModel> instead of Window):

XML
<Windows:DataWindow x:Class="SocialMediaStream.UI.Windows.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Windows="clr-namespace:Catel.Windows;assembly=Catel.Windows"
        xmlns:ViewModels="clr-namespace:SocialMediaStream.UI.ViewModels"
        xmlns:LocalControls="clr-namespace:SocialMediaStream.UI.Controls"
        x:TypeArguments="ViewModels:MainWindowViewModel"
        SizeToContent="Manual" Width="400" Height="600" ShowInTaskbar="True"
        Icon="/SocialMediaStream;component/Resources/Images/Catel.png">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        
        <!-- Settings -->
        <WrapPanel Orientation="Horizontal" 
               Style="{StaticResource RightAlignedButtonsWrapPanelStyle}">
            <Button Command="{Binding VisitCatel}" 
                   Style="{StaticResource MediumRightAlignedImageButtonStyle}" 
                   ToolTip="Visit Catel homepage">
                <Image Source="/SocialMediaStream;component/Resources/Images/Catel.png" />
            </Button>
            
            <Button Command="{Binding Refresh}" 
                    Style="{StaticResource MediumRightAlignedImageButtonStyle}" 
                    ToolTip="Refresh">
                <Image Source="/SocialMediaStream;component/Resources/Images/Refresh.png" />
            </Button>
        </WrapPanel>

        <!-- Listbox with contents -->
        <ListBox Grid.Row="1" ItemsSource="{Binding SocialMediaEntries}"
                 ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                 x:Name="socialMediaEntriesListBox">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <LocalControls:SocialMediaEntry DataContext="{Binding}" />
                        <Separator Width="{Binding 
                              ElementName=socialMediaEntriesListBox, 
                              Path=ActualWidth}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Windows:DataWindow>

The XAML code is pretty straightforward. The only thing I want to mention is the use of the SocialMediaEntry control. As you can see, the ItemTemplate of the ListBox control defines an instance of the user controls and binds the DataContext property of the user control. This is very important so UserControl<TViewModel> will use that data context to instantiate the View Model discussed earlier.

The code-behind of the window is very clean, thanks to MVVM. It passes DataWindowMode.Custom to the base constructor to tell DataWindow<TViewModel> not to generate the default OK and Cancel buttons.

C#
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : DataWindow<MainWindowViewModel>
    : base(DataWindowMode.Custom)
{
    /// <summary>
    /// Initializes a new instance of the <see cref="MainWindow"/> class.
    /// </summary>
    public MainWindow()
    {
        InitializeComponent();
    }
}

6.4. Setting up IoC containers

Now that we have prepared a very loosely coupled system, it’s time to couple the loose parts. Catel uses Unity to implement IoC containers. For now, there is just one thing we want to configure, and that is the ISocialMediaProviderFactory that should be used to collect the several providers.

First, we need to go to the app.config file and specify the section for the Unity configuration:

XML
<configSections>
  <section name="unity" 
      type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, 
            Microsoft.Practices.Unity.Configuration" />
</configSections>

After that, we can create our own configuration section that defines what type to instantiate for the Factory:

XML
<unity>
  <containers>
    <container>
      <types>
        <type type="SocialMediaStream.ISocialMediaProviderFactory, SocialMediaStream" 
           mapTo="SocialMediaStream.SocialMediaProviderFactory, SocialMediaStream"/>
      </types>
    </container>
  </containers>
</unity>

The Unity configuration section above tells Unity to create an instance of SocialMediaProviderFactory we created in the previous paragraph when ISocialMediaProviderFactory is requested from the container.

To make sure that Unity loads the configuration, we need to add this piece of code to the code-behind of the application (App.xaml.cs):

C#
/// <summary>
/// Raises the <see cref="E:System.Windows.Application.Startup"/> event.
/// </summary>
/// <param name="e">A <see
/// cref="T:System.Windows.StartupEventArgs"/>
/// that contains the event data.</param>
protected override void OnStartup(StartupEventArgs e)
{
    StyleHelper.CreateStyleForwardersForDefaultStyles();

    ConfigureIoCContainer();

    base.OnStartup(e);
}

/// <summary>
/// Configures the IoC container.
/// </summary>
private static void ConfigureIoCContainer()
{
    // Load from config, overrides defaults
    UnityConfigurationSection section = 
      (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
    if ((section != null) && (section.Containers.Count > 0))
    {
        section.Configure(Catel.IoC.UnityContainer.Instance.Container);
    }
}

The code above configures the IoC configuration when the application starts up. The code that is executed should speak for itself.

We are almost there, hold on. We have set up the Factory interface, an example implementation, and configured the IoC container to instantiate the right class. The only thing left is to actually instantiate the Factory. We do this in the constructor of the main window View Model. We have created two constructors to be able to inject the factory to the main window View Model during unit tests (but of course, this can also be accomplished via configuring the IoC containers via the app.config of the unit test project):

C#
/// <summary>
/// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
/// </summary>
/// <remarks>
/// This constructor is created to use an IoC container.
/// </remarks>
public MainWindowViewModel()
    : this(Catel.IoC.UnityContainer.Instance.
           Container.Resolve<ISocialMediaProviderFactory>()) { }

/// <summary>
/// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
/// </summary>
/// <param name="socialMediaProviderFactory">The social media provider factory.</param>
/// <remarks>
/// This constructor is created to allow custom dependency injection.
/// </remarks>
public MainWindowViewModel(ISocialMediaProviderFactory socialMediaProviderFactory)
{
    _socialMediaProviderFactor = socialMediaProviderFactory;

    // Other code left out for sake of simplicity
}

The first constructor without any arguments uses the IoC container of Catel to resolve the configured implementation of ISocialMediaProviderFactory, which in our case will be SocialMediaProviderFactory.

6.5. TraceOutputWindow for additional debug info

The output window of Visual Studio is not visible at all times (we all want to see our own things), nor does it provide colored messages. Therefore, Catel comes with the TraceOutputWindow. The use of this window is very easy, and we are going to use it to show the log messages during debug sessions.

We want the TraceOutputWindow to show when the application is started, and it should be closed as soon as the user exits the application. What better place to use than the main window? Below is the additional code that we use to show the TraceOutputWindow during debug builds. First, we need to declare the window as a field:

C#
#if DEBUG
    private readonly TraceOutputWindow _traceOutputWindow;
#endif

Finally, we need to add code to the constructor of the main window to show and close the TraceOutputWindow:

C#
/// <summary>
/// Initializes a new instance of the <see cref="MainWindow"/> class.
/// </summary>
public MainWindow()
    : base(DataWindowMode.Custom)
{
    InitializeComponent();

#if DEBUG
    _traceOutputWindow = new TraceOutputWindow();
    _traceOutputWindow.Show();

    Closed += (sender, e) =>
                    {
                        if (_traceOutputWindow != null)
                        {
                            _traceOutputWindow.Close();
                        }
                    };
#endif
}

Thanks to the #if and #endif directives, there will be no traces of this window during release builds.

6.6. Dynamic theme switching

I don’t want to keep the dynamic theme switching capabilities hidden from you, so let’s discuss this. It’s not too hard to understand, so it’s a nice finalizer for this article. Dynamic theme switching is implemented in the code-behind. The reason for this is that it has nothing to do with manipulating data, and we cannot unit test it properly without creating an Application object, nor can we see if it actually works. Therefore, it is perfectly legal to implement this behavior in the code-behind (because this is what the code-behind is for). If you are still not convinced why this is implemented in the code-behind, think of using the same View Model in Silverlight. Silverlight uses a different approach for theme handling, but the actual functionality works the same for Silverlight. This allows us to use exactly the same View Model for the main window in Silverlight, and the developers of the Silverlight version can decide for themselves whether they want to implement dynamic theme switching support.

There are several things we need to do to implement dynamic theme switching. Let’s start with the code-behind. We need a dependency property that defines both the available themes and the selected themes:

C#
/// <summary>
/// Gets or sets the available themes.
/// </summary>
/// <value>The available themes.</value>
public ObservableCollection<ThemeInfo> AvailableThemes
{
    get { return (ObservableCollection<ThemeInfo>)
                  GetValue(AvailableThemesProperty); }
    set { SetValue(AvailableThemesProperty, value); }
}

// Using a DependencyProperty as the backing store for AvailableThemes.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty AvailableThemesProperty =
    DependencyProperty.Register("AvailableThemes", 
       typeof(ObservableCollection<ThemeInfo>), 
       typeof(MainWindow), new UIPropertyMetadata(null));

/// <summary>
/// Gets or sets the selected theme.
/// </summary>
/// <value>The selected theme.</value>
public ThemeInfo SelectedTheme
{
    get { return (ThemeInfo)GetValue(SelectedThemeProperty); }
    set { SetValue(SelectedThemeProperty, value); }
}

// Using a DependencyProperty as the backing store for SelectedTheme.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedThemeProperty =
    DependencyProperty.Register("SelectedTheme", 
    typeof(ThemeInfo), typeof(MainWindow), 
    new UIPropertyMetadata(null, 
        (sender, e) => ((MainWindow)sender).OnSelectedThemeChanged()));

As you can see, as soon as the SelectedTheme property changes, the OnSelectedThemeChanged method is invoked. Let’s see how that one looks like:

C#
/// <summary>
/// Called when the <see cref="SelectedTheme"/> property has changed.
/// </summary>
private void OnSelectedThemeChanged()
{
    var currentApp = Application.Current;
    if (currentApp == null)
    {
        return;
    }

    // Need to call this twice because the first update
    // fixes the dictionaries, and the second one actually updates the UI
    UpdateApplicationResources(currentApp);
    UpdateApplicationResources(currentApp);
}

/// <summary>
/// Updates the application resources.
/// </summary>
/// <param name="currentApp">The current application.</param>
private void UpdateApplicationResources(Application currentApp)
{
    currentApp.Resources.Clear();
    currentApp.Resources.MergedDictionaries.Clear();

    ResourceDictionary resourceDictionary = new ResourceDictionary();
    resourceDictionary.Source = 
      new Uri(SelectedTheme.Source, UriKind.RelativeOrAbsolute);

    currentApp.Resources.MergedDictionaries.Add(resourceDictionary);

    StyleHelper.CreateStyleForwardersForDefaultStyles();
}

Now that we have implemented the logic that will take care of re-establishing the resource dictionaries of the application, let’s see how the collection of available themes is initialized. At the end of the constructor, we add this code:

C#
AvailableThemes = new ObservableCollection<ThemeInfo>();
AvailableThemes.Add(new ThemeInfo("Aero - normal", 
  "/Catel.Windows;component/themes/aero/catel.aero.normal.xaml"));
AvailableThemes.Add(new ThemeInfo("Aero - large", 
  "/Catel.Windows;component/themes/aero/catel.aero.large.xaml"));
AvailableThemes.Add(new ThemeInfo("Expression Dark - normal", 
  "/Catel.Windows;component/themes/expressiondark/catel.expressiondark.normal.xaml"));
AvailableThemes.Add(new ThemeInfo("Expression Dark - large", 
  "/Catel.Windows;component/themes/expressiondark/catel.expressiondark.large.xaml"));
AvailableThemes.Add(new ThemeInfo("Sunny Orange", 
  "/Catel.Windows;component/themes/sunnyorange/generic.xaml"));
SelectedTheme = AvailableThemes[2];

By default, Expression Dark - normal is set as the theme.

Last but not least, we need to add controls on the main window to enable dynamic theme switching. After the row definitions of the first grid, we add this code:

XML
<!-- Theme switching, not in view model because it has nothing to do with that -->
<WrapPanel Orientation="Horizontal" VerticalAlignment="Center"
       DataContext="{Binding RelativeSource=
                    {RelativeSource AncestorType={x:Type Windows:DataWindow}}}">
    <Label Content="Theme" VerticalAlignment="Center" />
    <ComboBox SelectedItem="{Binding SelectedTheme}" ItemsSource="{Binding AvailableThemes}"
                DisplayMemberPath="Name" VerticalAlignment="Center" />
</WrapPanel>

As you can see, the DataContext of the WrapPanel is set to the DataWindow, not the View Model. Therefore, we can bind to the AvailableThemes and SelectedTheme dependency properties of the window.

7. Conclusion

I hope this article has shown you how powerful the Catel framework can be if used correctly. By combining data handling, validation, and WPF user controls, you are able to create applications extremely fast, and still be in full control of what happens.

This application did not include unit testing. This doesn’t mean that unit testing isn’t important at all. The reason unit testing is not included is because there already is a separate article all about unit testing. This article would become too big if all that stuff was handled in this article again.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionTraceAppenderEx Pin
talbot25-Oct-17 1:53
talbot25-Oct-17 1:53 
GeneralMy vote of 5 Pin
Pratik Bhuva2-Nov-13 2:30
professionalPratik Bhuva2-Nov-13 2:30 
GeneralLooks like Catel MVVM Framework is the best! Pin
UmitDagitan18-Feb-11 9:14
UmitDagitan18-Feb-11 9:14 
GeneralRe: Looks like Catel MVVM Framework is the best! Pin
Geert van Horrik19-Feb-11 3:26
Geert van Horrik19-Feb-11 3:26 
GeneralRe: Looks like Catel MVVM Framework is the best! Pin
UmitDagitan19-Feb-11 21:16
UmitDagitan19-Feb-11 21:16 
GeneralRe: Looks like Catel MVVM Framework is the best! Pin
Geert van Horrik19-Feb-11 21:47
Geert van Horrik19-Feb-11 21:47 
GeneralRe: Looks like Catel MVVM Framework is the best! Pin
UmitDagitan20-Feb-11 8:22
UmitDagitan20-Feb-11 8:22 
GeneralMy vote of 5 Pin
RaviRanjanKr2-Feb-11 1:00
professionalRaviRanjanKr2-Feb-11 1:00 
GeneralRe: My vote of 5 Pin
Geert van Horrik6-Feb-11 1:36
Geert van Horrik6-Feb-11 1:36 
GeneralMy vote of 5 Pin
imgen28-Jan-11 15:58
imgen28-Jan-11 15:58 
GeneralRe: My vote of 5 Pin
Geert van Horrik28-Jan-11 23:26
Geert van Horrik28-Jan-11 23:26 
GeneralRe: My vote of 5 Pin
Geert van Horrik29-Jan-11 0:58
Geert van Horrik29-Jan-11 0:58 
GeneralRe: My vote of 5 Pin
imgen29-Jan-11 3:59
imgen29-Jan-11 3:59 
GeneralRe: My vote of 5 Pin
Geert van Horrik1-Feb-11 1:20
Geert van Horrik1-Feb-11 1:20 
GeneralRe: My vote of 5 Pin
imgen1-Feb-11 16:15
imgen1-Feb-11 16:15 
GeneralRe: My vote of 5 Pin
Geert van Horrik1-Feb-11 21:45
Geert van Horrik1-Feb-11 21:45 
GeneralRe: My vote of 5 Pin
imgen4-Feb-11 2:56
imgen4-Feb-11 2:56 
GeneralRe: My vote of 5 Pin
Geert van Horrik4-Feb-11 2:58
Geert van Horrik4-Feb-11 2:58 
GeneralRe: My vote of 5 Pin
imgen4-Feb-11 4:59
imgen4-Feb-11 4:59 
GeneralRe: My vote of 5 Pin
Geert van Horrik4-Feb-11 6:13
Geert van Horrik4-Feb-11 6:13 
GeneralRe: My vote of 5 Pin
imgen4-Feb-11 16:29
imgen4-Feb-11 16:29 
GeneralRe: My vote of 5 Pin
Geert van Horrik6-Feb-11 1:35
Geert van Horrik6-Feb-11 1:35 
GeneralRe: My vote of 5 Pin
imgen4-Feb-11 2:58
imgen4-Feb-11 2:58 
GeneralRe: My vote of 5 Pin
imgen4-Feb-11 4:36
imgen4-Feb-11 4:36 
GeneralRe: My vote of 5 Pin
Geert van Horrik2-Feb-11 4:13
Geert van Horrik2-Feb-11 4:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.