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
- Introduction
- Functional requirements
- Content providers
- User interface
- Creating the project
- Features handled
- Directory structure
- Creating projects
- SocialMediaStream
- SocialMediaStream.Library
- SocialMediaStream.Providers.Facebook
- SocialMediaStream.Providers.Twitter
- SocialMediaStream.Test
- Enable logging
- Setting up themes and styles
- Creating the models
- Features handled
- Interfaces
- ISocialMediaEntry
- ISocialMediaProvider
- ISocialMediaProviderFactory
- Base provider
- Twitter and Facebook providers
- SocialMediaProviderFactory
- Creating the View Models
- Features handled
- SocialMediaEntryViewModel
- MainWindowViewModel
- Creating the Views
- Features handled
- SocialMediaEntry
- MainWindow
- Setting up IoC containers
- TraceOutputWindow for additional debug info
- Dynamic theme switching
- 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:
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.
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
- log4net (logging);
- 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:
- 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.
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:
="1.0" ="utf-8"
<configuration>
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
-->
<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>
-->
<appender name="TraceAppender"
type="log4net.Appender.TraceAppenderEx, Catel.Core">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date - %message%newline"/>
</layout>
</appender>
-->
<root>
-->
<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:
[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:
<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:
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:
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:
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:
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:
#if !SILVERLIGHT
[Serializable]
#endif
public abstract class SocialMediaProviderBase :
DataObjectBase<SocialMediaProviderBase>, ISocialMediaProvider
{
#region Variables
#endregion
#region Constructor & destructor
protected SocialMediaProviderBase(string providerName)
{
Name = providerName;
}
#if !SILVERLIGHT
protected SocialMediaProviderBase(SerializationInfo info,
StreamingContext context)
: base(info, context) { }
#endif
#endregion
#region Properties
public string Name
{
get { return GetValue<string>(NameProperty); }
private set { SetValue(NameProperty, value); }
}
public static readonly PropertyData NameProperty =
RegisterProperty("Name", typeof(string), string.Empty);
#endregion
#region Methods
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:
public class SocialMediaProviderFactory : ISocialMediaProviderFactory
{
#region Variables
private static readonly List<ISocialMediaProvider> _providers =
new List<ISocialMediaProvider>();
#endregion
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)
{
}
}
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 ItemTemplate
s for ItemsControl
s, 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:
public class SocialMediaEntryViewModel : ViewModelBase
{
#region Constructor & destructor
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
public override string Title { get { return "Social Media Entry"; } }
#region Models
[Model]
private ISocialMediaEntry SocialMediaEntry
{
get { return GetValue<ISocialMediaEntry>(SocialMediaEntryProperty); }
set { SetValue(SocialMediaEntryProperty, value); }
}
public static readonly PropertyData SocialMediaEntryProperty =
RegisterProperty("SocialMediaEntry", typeof(ISocialMediaEntry));
#endregion
#region View model
public BitmapSource Photo
{
get { return GetValue<BitmapSource>(PhotoProperty); }
set { SetValue(PhotoProperty, value); }
}
public static readonly PropertyData PhotoProperty =
RegisterProperty("Photo", typeof(BitmapSource));
[ViewModelToModel("SocialMediaEntry")]
public string Message
{
get { return GetValue<string>(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly PropertyData MessageProperty =
RegisterProperty("Message", typeof(string));
[ViewModelToModel("SocialMediaEntry")]
public string MessagePreview
{
get { return GetValue<string>(MessagePreviewProperty); }
set { SetValue(MessagePreviewProperty, value); }
}
public static readonly PropertyData MessagePreviewProperty =
RegisterProperty("MessagePreview", typeof(string));
[ViewModelToModel("SocialMediaEntry")]
public string Author
{
get { return GetValue<string>(AuthorProperty); }
set { SetValue(AuthorProperty, value); }
}
public static readonly PropertyData AuthorProperty =
RegisterProperty("Author", typeof(string));
[ViewModelToModel("SocialMediaEntry")]
public DateTime Timestamp
{
get { return GetValue<DateTime>(TimestampProperty); }
set { SetValue(TimestampProperty, value); }
}
public static readonly PropertyData TimestampProperty =
RegisterProperty("Timestamp", typeof(DateTime));
[ViewModelToModel("SocialMediaEntry")]
public string Url
{
get { return GetValue<string>(UrlProperty); }
set { SetValue(UrlProperty, value); }
}
public static readonly PropertyData UrlProperty =
RegisterProperty("Url", typeof(string));
#endregion
#region Commands
public Command<object, object> OpenLink { get; private set; }
private bool OnOpenLinkCanExecute(object parameter)
{
return !string.IsNullOrEmpty(Url);
}
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.
public class MainWindowViewModel : ViewModelBase
{
#region Variables
private readonly ISocialMediaProviderFactory _socialMediaProviderFactor;
private readonly List<ISocialMediaProvider>
_enabledSocialMediaProviders = new List<ISocialMediaProvider>();
#endregion
#region Constructor & destructor
public MainWindowViewModel(): this(Catel.IoC.UnityContainer.
Instance.Container.Resolve<ISocialMediaProviderFactory>()) { }
public MainWindowViewModel(ISocialMediaProviderFactory
socialMediaProviderFactory)
{
_socialMediaProviderFactor = socialMediaProviderFactory;
VisitCatel = new Command<object>(OnVisitCatelExecute);
Refresh = new Command<object>(OnRefreshExecute);
}
#endregion
#region Properties
public override string Title { get { return "Social Media Stream"; } }
#region Models
#endregion
#region View model
public ObservableCollection<ISocialMediaEntry> SocialMediaEntries
{
get { return GetValue<ObservableCollection<ISocialMediaEntry>>(
SocialMediaEntriesProperty); }
set { SetValue(SocialMediaEntriesProperty, value); }
}
public static readonly PropertyData SocialMediaEntriesProperty =
RegisterProperty("SocialMediaEntries",
typeof(ObservableCollection<ISocialMediaEntry>));
#endregion
#endregion
#region Commands
public Command<object> VisitCatel { get; private set; }
private void OnVisitCatelExecute(object parameter)
{
var processService = GetService<IProcessService>();
processService.StartProcess("http://catel.codeplex.com");
}
public Command<object> Refresh { get; private set; }
private void OnRefreshExecute(object parameter)
{
RefreshSocialMedia();
}
#endregion
#region Methods
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();
}
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
):
<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:
public partial class SocialMediaEntry : UserControl<SocialMediaEntryViewModel>
{
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
):
<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>
-->
<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 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.
public partial class MainWindow : DataWindow<MainWindowViewModel>
: base(DataWindowMode.Custom)
{
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:
<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:
<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):
protected override void OnStartup(StartupEventArgs e)
{
StyleHelper.CreateStyleForwardersForDefaultStyles();
ConfigureIoCContainer();
base.OnStartup(e);
}
private static void ConfigureIoCContainer()
{
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):
public MainWindowViewModel()
: this(Catel.IoC.UnityContainer.Instance.
Container.Resolve<ISocialMediaProviderFactory>()) { }
public MainWindowViewModel(ISocialMediaProviderFactory socialMediaProviderFactory)
{
_socialMediaProviderFactor = socialMediaProviderFactory;
}
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:
#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
:
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:
public ObservableCollection<ThemeInfo> AvailableThemes
{
get { return (ObservableCollection<ThemeInfo>)
GetValue(AvailableThemesProperty); }
set { SetValue(AvailableThemesProperty, value); }
}
public static readonly DependencyProperty AvailableThemesProperty =
DependencyProperty.Register("AvailableThemes",
typeof(ObservableCollection<ThemeInfo>),
typeof(MainWindow), new UIPropertyMetadata(null));
public ThemeInfo SelectedTheme
{
get { return (ThemeInfo)GetValue(SelectedThemeProperty); }
set { SetValue(SelectedThemeProperty, value); }
}
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:
private void OnSelectedThemeChanged()
{
var currentApp = Application.Current;
if (currentApp == null)
{
return;
}
UpdateApplicationResources(currentApp);
UpdateApplicationResources(currentApp);
}
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:
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:
<!---->
<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.