Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / WPF

XPence: A WPF Metro Style, Smart Client Expense Tracker

Rate me:
Please Sign up or sign in to vote.
4.92/5 (73 votes)
28 Nov 2022CPOL26 min read 209.4K   13K   94   77
WPF smart client demo of nitty gritty of MVVM for a real life LOB application
In this article, you will learn about a WPF smart client demonstrating the nitty gritty of MVVM for a real life LOB application.

Cover photo

Contents

Introduction

The internet is teeming with examples demonstrating MVVM. The beginner however, often has to run to different sources for the nitty gritties involved in implementing MVVM in "real life" LOB apps. XPence started as a POC to deepen my understanding of Metro (aka. Modern) UI design principles, but gradually became a labour of love. This app brings almost all the best practices required to implement MVVM in a Line of business application, at one place.

A friend of mine once argued that MVVM forces the use of many frameworks like IOC Containers or MVVM frameworks. I repeat here what I replied to him: MVVM sets guidelines for application architecture that lets you write robust and easy to change software. The frameworks are for making our lives easy by avoiding writing of boilerplate code. MVVM does not mandate using any framework. While writing XPence, use of any framework has been deliberately avoided so that the purity of MVVM is not shadowed by the framework. Having said that, I strongly recommend the use of frameworks. They help keeping the code cleaner and more organised. A lot of open source frameworks are available and they are designed to be MVVM friendly. To list a few:

  • Cinch by Sacha Barber: An awesome MVVM Framework
  • MVVM Light: A lightweight and effective MVVM framework
  • Caliburn Micro: A very popular framework that provides IOC container as well as MVVM base classes
  • MEF: My personal favourite IOC container that may be used along with PRISM

Who Is This Article For?

It is often a difficult task to mark the target audience. All I can say is, it would interest:

  • Beginners who are starting to implement MVVM in LOB apps and are still bugged by a lot of "how"s. However, the article expects an elementary understanding of MVVM (what it is, the magic of binding, etc.). If you are absolutely new to MVVM, you can find a lot of resources over the internet. My suggestions would be the following few:
  • Also the article assumes you are good with WPF concepts like DependencyProperties, Styles, Templates, Converters, etc.
  • The MVVM Pros who don't mind revisiting the concepts and are open to debate on some of them.

Before we proceed further, I would request you to read the bold text in the That's it! section.

Running the Application

To build the code, you would need Visual Studio 2022 (Express/ Professional/ Ultimate).
Few external DLLs are also used but they are packaged with the source code so you need not worry about getting them separately. To know more, visit the Acknowledgements and external components section.

For persisting data, MS SQL Server CE is used. Before you could run the application, you will need MS SQL Server CE 4.0. It involves a few points to consider before you actually install it. This page provides the details you would need for installing the MS SQL Server CE.

If you don't want to install SQL Server CE 4.0, there is still a way out.

  • Download the SQL Server CE DLLs (SQLDlls) from the top of this page and place the two folders (amd64 and x86) inside the Output directory (refer Directory structure).
  • Go to the app.config file of XPence project and uncomment the system.data tag.

Rebuild the project and it should work. I tested this on a 64 bit machine that did not have SQL Server CE 4.0 installed. However, the DLLs provided are 32 bit DLLs. If your machine is 64 bit, these DLLs would still run as 32 bit.

Acknowledgements and External Components

I am an advocate of using open source and hence XPence uses a lot of external components. Some are open source and the rest are shameless thefts from other authors. Credits and acknowledgements should go where they belong:

  • XPence uses Mahapps Metro for the metro/ modern UI. It is an awesome WPF library for building Metro looking UIs.
  • XPence uses the Apex grid for cleaner and shorter XAML files. Apex provides many other features too, though not utilized by XPence.
  • Syncfusion's free Metro Studio came in very handy for the XAML figures used in XPense.
  • A lot of code for creating an image cropper control has been taken from this article.
  • A sliding content control came in handy for metro experience. Mahapps metro provide their own metro content control but I preferred this for more vibrant sliding.
  • XPence encrypts its passwords for saving in the DB. This article provided all the code for that.
  • The compact data viewer came in very handy during development.
  • NHibernate is used as the (preferred) ORM (over EntityFramework!)
  • For charting, Modern UI Charts library is used.
  • For logging, XPense uses log4net.
  • This blog helped me overcome a WPF bug that bugged me for a while.

My sincere thanks to all the authors, whose code has been used in the application. You guys rock!

Introducing Xpense to a User

Let's have a look at what XPense is and how it is supposed to work. XPence is an expense tracker software for a small team. It is a "single admin and multiple users system" where the Admin enjoys certain enhanced authorization over the system. Let's have a walk through of the application as an admin and see what all it offers. Be sure to download the TestData (from the top of this page) to run it yourself!

On launch, the login screen appears.

Login Screenshot

Punch in the admin credentials and the home screen would be launched.

Home Screenshot

You may change your password or the theme and accent of the application from the top right settings icon. On clicking the icon, the Settings flyout opens:

Settings Screenshot

XPence offers you to save the settings for future.

Confirmation Screenshot

From the home screen, you may go to the all expense view where you can perform all CRUD operations for expenses entered by you or other users (normal users can do this only to expenses entered by them). Here is the screen shot for the all expenses screen:

AllExpenses Screenshot

You can filter the expenses using the filter flyout. Here is a screen shot for that:

FilterScreenshot Screenshot

Mind it, the normal users won't have the "filter by user" field enabled in their filter flyout.

And finally, the manage screen, to where you can navigate from the home screen, is something that only the admin has access to. You can change a user's name or picture and the same would be available on the emblem (top right corner of application window) when they login. Here is a screen shot of the manage screen:

ManageScreenshot Screenshot

That's all about "What XPence does?".

Initial Things

Before starting with the internals, let's have a look at the directory structure and understand how the code is organised.

  • Directory Structure

    As you extract the XPence.zip file, you will find three folders:

    • ObjMatter: For any C# project, Visual Studio produces a lot of files in the obj folder. This bloats up the size of each project folder, especially for a WPF windows app project. They may be deleted and the Visual Studio regenerates them after every build but if you have considerable number of projects in the solution, it becomes a pain to go inside individual project folders to clean the obj folder. All the projects in the solution, therefore, redirect their obj matter in this folder. This can be done by including the following path in the PropertyGroup of the Project tag (you will need to open all .csproject files in a text editor):
      <BaseIntermediateOutputPath>..\..\ObjMatter\</BaseIntermediateOutputPath>
    • Output: This contains all the output files of the projects (executable and DLLs) and three folders:
      • Data: This contains the Database file which is a SQL Server CE (.sdf) file.
      • Logs: This is where the log file for the application goes.
      • ThirdParty: This folder contains all the external DLLs that XPence uses. Whatever DLLs are referred to by the projects have their CopyLocal set to false and the App.Config of the XPence project provides a probing path to the runtime to check for dependant DLLs. This fastens up the build time, though not noticeable in a solution with just six projects!
    • XPence: Contains the source files for the project. This is the Solution folder.

    All in all, this structure makes distribution of executable and source code easy and also reduces the rebuild time!

  • Source Code

    Opening the solution in Visual Studio will present six projects. Remember the "separation of concerns" starts at this very level of creating projects. In my opinion, one should be a bit choosy when it comes to adding references to his project. I try to make the following things sure:

    1. The ViewModels should not be aware about the view and hence view specific DLLs like PresentationFramework.dll and System.Xaml.dll should not be referred to by the ViewModel project. Remember whatever your ViewModel project refers will also be referred by its test project and ViewModel developers should not at all be dependent on any View components for testing.
    2. The Model project should not be referred to by the View project directly (more on this later). It's a temptation for the beginners to put enumerations in Model project(s). But this may not be right for every case. If you have an ItemsControl (like ComboBox) in View that expects the enumeration for its DataSource, the View project would need to refer to the Model. Therefore, it's better to put the enumerations in a shared project that would be referred to by the View, ViewModel and Model projects independently.
    3. Constants, Resource strings, etc. that may be shared across View, ViewModel and Model should be placed in shared project. This project may contain the enumerations too.
    Here is a diagram that summarizes the above points:
    Application architecture diagram

    These points help keep the code maintainable and testable in spite of multiple change requirements (that inevitably keep coming!), especially for large projects.

Overview

Here is a brief of the overview.

The Application Architecture

This is how the application architecture looks like:

Application architecture diagram

I believe, most of the WPF developers following the MVVM pattern will find this familiar. The diagram is self explanatory so I won't explain much. However, a few comments that I would add are:

  • The benefit of repository is that it is injected into the ViewModel against interface and therefore changing the implementation would have least impact. Whatever be your data source, ViewModel is agnostic about it.
  • A model, no matter how simple and small, should always be wrapped inside a ViewModel before being rendered to the View. A model's sole responsibility is to carry data and (may be) validate it. The model should not be compliant with any of the requirements of the View.

    My colleague did not like my idea of wrapping models that contained just one string property inside a ViewModel. The models would form items for a combobox. A few days later, a requirement came where the users wanted a button with every combobox item. The wrapper Viewmodel exposed an ICommand property and the requirement was over!

  • Since the data operations are abstracted by the Repository, the choice of using any ORM becomes wider. NHibernate does not go very well with WPF but not if you have a repository.

The UI Sections

Here is a brief of UI sectioning.

Login screen

If you have any doubt about the diagram above, be patient as we will have a detailed look when we see the components.

Components

The various components that are worth making a note of are listed below:

Messaging Service

Messaging service is responsible for a number of tasks, viz:

  • Showing a message box
  • Showing a pop up window (modal or otherwise) with custom content
  • Showing a busy message and blocking the application window whenever the app is doing some background work
  • Showing a flyout with custom content

The messaging service is an interface that is provided to any ViewModel through constructor injection. The ViewModel, thus is never aware of any View specific code (e.g., Message box, Pop up window, etc.) This is how the interface looks. It provides all members that are required for the above operations.

C#
/// <summary>
/// The messaging service interface.
/// </summary>
public interface IMessagingService
{
    /// <summary>
    /// A quick method to show a modal dialog to the user.
    /// The header text would be default.
    ///
    /// <param name="message" />The message to be shown in the dialog.
    void ShowMessage(string message);

    /// <summary>
    /// A quick method to show a message to the user along with the header text.
    /// </summary>
    /// <param name="message" />The message to be shown in the dialog.
    /// <param name="header" />The header of the dialog.
    void ShowMessage(string message, string header);

    /// <summary>
    /// A method to show to the user a dialog with a  message.
    /// The method returns a dialog result.
    /// The header text would be default.
    /// </summary>
    /// <param name="message" />The message to be shown in the dialog.
    /// <param name="dialogueType" />The header of the dialog.
    /// <returns>The dialog result as selected by the user.</returns>
    DialogResponse ShowMessage(string message, DialogType dialogueType);

    /// <summary>
    /// A method to show to the user a dialog with a  message and header.
    /// The method returns a dialog result.
    /// </summary>
    /// <param name="message" />The message to be shown in the dialog.
    /// <param name="header" />The header of the dialog.
    /// <param name="dialogueType" />The dialog type.
    /// <returns>The dialog result as selected by the user.</returns>
    DialogResponse ShowMessage
          (string message, string header, DialogType dialogueType);

    /// <summary>
    ///  A method to display wait dialog to the user.
    /// </summary>
    /// <param name="showBusy" />
    /// <param name="message" />
    /// <param name="header" />
    void ShowProgressMessage(string header, string message);

    /// <summary>
    /// Closes a progress message dialog that is visible.
    /// </summary>
    void CloseProgressMessage();

    /// <summary>
    /// Shows a view identified by the <see cref="viewKey">
    /// and binds the view to the <see cref="viewModel">.
    /// The view is shown as a blocking modal dialog.
    /// </see></see>
    /// <param name="viewKey" />shows the view identified by the key.
    /// <param name="viewModel" />The modal view is bound to this view model.
    void ShowCustomMessageDialog
         (string viewKey, ModalDialogViewModelBase viewModel);

    /// <summary>
    /// Shows a view identified by the <see cref="viewKey">
    /// and binds the view to the <see cref="viewModel">.
    /// The view is shown as a non blocking modal dialog.
    ///
    /// <param name="viewKey" />shows the view identified by the key.
    /// <param name="viewModel" />The modal view is bound to this view model.
    void ShowCustomMessage(string viewKey, ModalDialogViewModelBase viewModel);

    /// <summary>
    /// Registers an instance of <see cref="FlyoutViewModelBase">
    /// that can be used in the application.
    /// </see></summary>
    /// <param name="flyoutViewModelBase" />
    void RegisterFlyout(FlyoutViewModelBase flyoutViewModelBase);
}

Note that the DialogType and DialogResponse enumerations reside in the XPence.Infrastructure project. The internal implementation of this interface is maintained as a singleton instance by MessageServiceFactory class. This is where I missed an IOC container the most. Without constructor injection stubbing data for Unit Testing would become a nightmare and so the singleton instance is created at the application start up, but passed to every view model through constructor injection.

Custom Pop Up Screens

It's worth spending a moment looking at how the custom pop up messages are being shown in XPence. If you have noticed, the IMessagingService interface you might have noticed that the parameters of two members, viz: ShowCustomMessageDialog and ShowCustomMessage, expects a string viewKey and a parameter of type ModalDialogViewModelBase. As you may have guessed correctly, this is the ViewModel that serves any pop up view. The key is used by the messaging service to identify the type of UserControl that will be bound to the instance of ModalDialogViewModelBase passed. We will see in a while from where the messaging service gets the type of UserControl using the key. First let's have a look at how the ModalDialogViewModelBase looks. Here is the code:

C#
/// <summary>
/// A base class for all view Models that cater to modal dialogs.
/// </summary>
public abstract class ModalDialogViewModelBase : ViewModelBase
{
    #region Public Members

    /// <summary>
    /// Gets or sets if the user has cancelled the dialog.
    /// </summary>
    public bool IsCancelled
    {
        get { return _isCancelled; }
        set
        {
            if (value != _isCancelled)
            {
                _isCancelled = value;
                OnPropertyChanged(GetPropertyName(() => IsCancelled));
            }
        }
    }

    /// <summary>
    /// Gets or sets if the User has chosen Ok for the dialog.
    /// </summary>
    public bool IsOk
    {
        get { return _isOk; }
        set
        {
            if (value != _isOk)
            {
                _isOk = value;
                OnPropertyChanged(GetPropertyName(() => IsOk));
            }
        }
    }

    /// <summary>
    /// Gets or sets the title text for the window.
    /// </summary>
    public string TitleText
    {
        get { return _titleText; }
        set
        {
            if (value != _titleText)
            {
                _titleText = value;
                OnPropertyChanged(GetPropertyName(() => TitleText));
            }
        }
    }

    #endregion

    #region Commands

    /// <summary>
    /// Gets the Ok dialog command.
    /// Bind this to the button that selects Ok option for the dialog.
    /// </summary>
    public ICommand OkSelectedCommand { get; private set; }

    /// <summary>
    /// Gets the cancelled dialog command.
    /// Bind this to the button that cancels the dialog.
    ///
    public ICommand CancelSelectedCommand { get; private set; }

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes instance of <see cref="ModalDialogViewModelBase">
    /// </see>
    protected ModalDialogViewModelBase()
    {
        OkSelectedCommand = new RelayCommand(OkSelected);
        CancelSelectedCommand = new RelayCommand(CancelSelected);
    }

    #endregion

    #region Protected Overidable methods

    /// <summary>
    /// Fired on OkCommand selected.
    /// </summary>
    protected virtual void OnOkSelected()
    {

    }

    /// <summary>
    /// Fired on Cancel selected.
    /// </summary>
    protected virtual void OnCancelSelected()
    {

    }

    #endregion

    #region Internal Events

    /// <summary>
    /// An event used internally by the
    /// <see cref="ModalCustomMessageDialog"> to listen to the Ok
    /// or cancelled option selected by user.
    /// </see></summary>
    internal event EventHandler DialogResultSelected
    {
        add { _dialogResultSelected += value; }
        remove { _dialogResultSelected -= value; }
    }

    #endregion

    #region Private Methods

    /// <summary>
    /// Caters the <see cref="OkSelectedCommand">.
    /// </see></summary>
    private void OkSelected()
    {
        IsOk = true;
        NotifyView(DialogResponse.Ok);
    }

    /// <summary>
    /// Caters the <see cref="CancelSelectedCommand">.
    ///
    private void CancelSelected()
    {
        IsCancelled = true;
        NotifyView(DialogResponse.Cancel);
    }

    /// <summary>
    /// Fires the <see cref="DialogResultSelected"> event.
    /// </see></summary>
    /// <param name="dialogResponse" />
    private void NotifyView(DialogResponse dialogResponse)
    {
        if (null != _dialogResultSelected)
            _dialogResultSelected(this, EventArgs.Empty);
    }

    #endregion

    #region Private Members

    private bool _isCancelled;
    private bool _isOk;
    private string _titleText;
    internal event EventHandler _dialogResultSelected;

    #endregion
}

The ModalDialogViewModelBase gives the basic properties of IsOk and IsCancelled that gives the user choice made on that dialog. The consumer code can then carry on with its operations depending on the user choice. It also offers the OkSelectedCommand and CancelSelectedCommand that may be bound to any ICommandSource (usually Button) in the pop up view. It offers an event DialogResultSelected to which the view will hook. Whenever the user makes a choice (Ok/Cancel), the ModalDialogViewModelBase records the response and fires this event. The pop up window, which is hooked to this event understands that it has to close now. If it is not very clear, please be patient, read on!

The Messaging Service uses a common Window called the ModalCustomMessageDialog. All the Messaging Service does is:

  1. Open the ModalCustomMessageDialog.
  2. Sets the UserControl, that it got using key, in the window's content.
  3. Binds the window to the instance of ModalDialogViewModelBase passed.

The Window is intelligent enough to listen to its DataContext for any notifications. Here is the code behind for ModalCustomMessageDialog.

C#
/// <summary>
/// Interaction logic for ModalCustomMessageDialog.xaml
/// </summary>
public partial class ModalCustomMessageDialog
{
    #region Constructors

    /// <summary>
    /// Static constructor
    /// </summary>
    static ModalCustomMessageDialog()
    {
    }

    /// <summary>
    /// Initializes and ionstance of <see cref="ModalCustomMessageDialog">.
    ///
    public ModalCustomMessageDialog()
    {
        InitializeComponent();
    }

    #endregion

    #region Static Event Handlers

    /// <summary>
    /// The handler to handle the content property changed.
    /// </summary>
    /// <param name="source" />
    /// <param name="e" />
    private static void OnActualContentPropertyChanged
            (DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        var modalWindow = source as ModalCustomMessageDialog;
        if (null != modalWindow)
        {
            if (null != modalWindow.ActualContentHolder)
                modalWindow.ActualContentHolder.Content = e.NewValue;
        }
    }

    #endregion

    /// <summary>
    /// A duplicate content property built to help a developer with his laziness.
    /// </summary>
    public object ActualContent
    {
        get { return GetValue(ActualContentProperty); }
        set { SetValue(ActualContentProperty, value); }
    }
    public static readonly DependencyProperty ActualContentProperty =
           DependencyProperty.Register("ActualContent", typeof(object),
           typeof(ModalCustomMessageDialog), new PropertyMetadata
           (null, OnActualContentPropertyChanged));

    #region Base class overrides

    /// <summary>
    /// On property changed.
    /// </summary>
    /// <param name="e" />
    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        if (e.Property == DataContextProperty)
        {
            var oldViewModel = e.OldValue as ModalDialogViewModelBase;
            if (null != oldViewModel)
                oldViewModel.DialogResultSelected -= OnViewNotified;
            var newViewModel = e.NewValue as ModalDialogViewModelBase;
            if (null != newViewModel)
                newViewModel.DialogResultSelected += OnViewNotified;
        }
        else
        {
            base.OnPropertyChanged(e);
        }
    }

    #endregion

    #region Private Helpers

    /// <summary>
    /// Closes the Window.
    ///
    /// <param name="sender" />
    /// <param name="e" />
    private void OnViewNotified(object sender, EventArgs e)
    {
        var viewModel = DataContext as ModalDialogViewModelBase;
        if (null != viewModel)
            viewModel.DialogResultSelected -= OnViewNotified;
        Close(); //Close the window
    }

    #endregion
}

Now let's arrive at the question from where does the Messaging Service procure the type of UserControl that it sets inside the modal dialog window. The answer is: from a singleton instance of a class called ModalViewRegistry. At application start up, all pop up user control types are fed in the ModalViewRegistry against their keys. It's as simple as that! Here is the code for ModalViewRegistry class:

C#
/// <summary>
/// Registers the modal views used in the application against a key.
/// This is a singleton class. Get an instance of the class
/// at the application startup and
/// Register all the modal views against suitable keys.
/// This key would be used later on by the <see cref="IMessagingService"/>
/// instance to extract the view and display in a modal dialog.
/// </summary>
public class ModalViewRegistry
{
    #region Public Members

    /// <summary>
    /// Gets the sole instance of the MainViewRegistry class.
    ///
    public static ModalViewRegistry Instance { get { return _instance.Value; } }

    #endregion

    #region Public Methods

    /// <summary>
    /// Registers a UserControl that needs to be shown as a modal dialog.
    /// </summary>
    /// <param name="key" />Key against which the user control
    /// is stored in the registry.
    /// <param name="userControlType" />The type of UserControl
    /// that would be shown as a modal dialog.
    /// <exception cref="InvalidOperationException">Throws exception
    /// if an existing key is used to add another UserControl.
    /// This is to eliminate the risk of overwriting an existing
    /// UserControl in the registry.
    public void RegisterView(string key, Type userControlType)
    {
        if (string.IsNullOrEmpty(key))
            throw new ArgumentException("key");
        if (null == userControlType)
            throw new ArgumentException("userControl");
        if (!userControlType.IsSubclassOf(typeof(UserControl)))
            throw new InvalidCastException
            ("Only a user control type can be assigned.");
        if (_modalViewRegistry.ContainsKey(key))
            throw new InvalidOperationException("Key already exists.");
        _modalViewRegistry[key] = userControlType;
    }

    /// <summary>
    /// Determines if a  key is already registered in the registry.
    ///
    /// <param name="viewKey" />The key that needs for checked in the registry.
    /// <returns>
    public bool ContainsKey(string viewKey)
    {
        return _modalViewRegistry.ContainsKey(viewKey);
    }

    #endregion

    #region Internal Methods

    /// <summary>
    /// Gets the registered UserControl against the key.
    /// </summary>
    /// <param name="key" />
    /// <returns>
    internal UserControl GetViewByKey(string key)
    {
        Type userControlType = _modalViewRegistry[key];
        return Activator.CreateInstance(userControlType) as UserControl;
    }

    #endregion

    #region Member Variables

    private readonly IDictionary<string,> _modalViewRegistry;
    // static holder for instance, need to use lambda
    // to construct since constructor is private.
    private static readonly Lazy<modalviewregistry> _instance
      = new Lazy<modalviewregistry>(() => new ModalViewRegistry());
    #endregion

    #region Contructor

    /// <summary>
    /// Initializes and instance of <see cref="MessagingService">
    /// </see>
    private ModalViewRegistry()
    {
        _modalViewRegistry = new Dictionary<string,>();
    }

    #endregion
}

Flyouts

One of the major features and attractions of Metro UI is Flyouts. MahApps metro developers have done a wonderful job of offering the flyouts of a MetroWindow as an ItemsControl. This means the view model serving the MainWindow of the application may contain a list of objects that can serve as flyouts. A Flyout control of Mahapps metro has all the Dependency Properties for controlling the Visibility, Theme and Direction. Therefore, if we have a base ViewModel that carries all the base properties for controlling a flyout, an inherited class from it can serve as the ViewModel for a flyout that is shown in the MainWindow. All we need is a collection of these base view models in the MainWindowModel (aka. flyout container). This is how the FlyoutViewModelBase looks:

C#
public abstract class FlyoutViewModelBase : ViewModelBase
    {
        #region Public properties

        /// <summary>
        /// Gets or sets the header text of the flyout instance.
        /// </summary>
        public string Header
        {
            get { return _header; }
            set
            {
                if (_header == value)
                    return;
                _header = value;
                OnPropertyChanged(GetPropertyName(() => Header));
            }
        }

        /// <summary>
        /// Gets or sets the position of the flyout instance.
        /// </summary>
        public VisibilityPosition Position
        {
            get { return _position; }
            set
            {
                if (_position == value)
                    return;
                _position = value;
                OnPropertyChanged("Position");
            }
        }

        /// <summary>
        /// Gets or sets if the flyout instance is visible or collapsed.
        /// </summary>
        public bool IsOpen
        {
            get { return _isOpen; }
            set
            {
                if (_isOpen == value)
                    return;
                _isOpen = value;
                OnPropertyChanged("IsOpen");
            }
        }

        /// <summary>
        /// Gets or sets the theme of the flyout instance.
        /// 
        public FlyoutTheme Theme
        {
            get { return _theme; }
            set
            {
                if (_theme == value)
                    return;
                _theme = value;
                OnPropertyChanged("Theme");
            }
        }

        #endregion

        #region protected members.

        protected string _header;
        protected VisibilityPosition _position;
        protected bool _isOpen;
        protected FlyoutTheme _theme;

        #endregion

    }

The next requirement that arises is any ViewModel in the project may demand a flyout. How to let that ViewModel add an instance of a FlyoutViewModelBase to the flyout container? If we had an IOC container, we could have easily procured a singleton instance of the flyout container. But we aren't using one. MessagingService comes to our rescue by providing a method to add a FlyoutViewModel from anywhere in the application. Here is a simplified diagram that explains how this works:

Flyout architecture diagram

Navigation Service

Navigation is an important functionality of any metro based application. While frameworks like PRISM offer robust Navigation functionalities, it is not an elephant task to make a small one for yourself. Mahapps metro offers a navigation functionality, but I did not like it because it provided a forward button too and I did not want to make a browser like application. I preferred writing a small Navigation functionality for myself. Well, let's see how the INavigator interface works. Shall we? Being an Interface, it can be accessed from anywhere (read it as any ViewModel) by IOC/DI. Here is the interface.

C#
/// <summary>
/// A contract for an instance of navigator.
/// Provides the members required for navigation.
///
public interface INavigator:INotifyPropertyChanged
{
    /// <summary>
    /// Commands the navigator instance to navigate back.
    /// </summary>
    void NavigateBack();

    /// <summary>
    /// Commands the navigator instance to navigate back.
    /// </summary>
    void NavigateToHome();

    /// <summary>
    /// Gets or sets the current selected view.
    /// </summary>
    WorkspaceViewModelBase CurrentView { get; set; }

    /// <summary>
    /// Gets the Enumerable of all the views.
    /// </summary>
    /// <returns>
    IEnumerable<workspaceviewmodelbase> GetAllView();

    /// <summary>
    /// Adds a workspace view to the navigator instance.
    /// </summary>
    /// <param name="workspaceView" />
    void AddView(WorkspaceViewModelBase workspaceView);

    /// <summary>
    /// Adds the home view model.
    ///
    /// <param name="workspaceView" />
    void AddHomeView(WorkspaceViewModelBase workspaceView);

    /// <summary>
    /// Navigates to the view specified by the key.
    /// </summary>
    /// <param name="viewKey" />
    void NavigateToView(string viewKey);
}

The Navigator works in three very simple steps:

  • It contains the collection of all WorkspaceViewModels.
  • It holds a current view that is in turn, is listened to for any change, by the ApplicationViewModel.
  • It offers the method NavigateToView(string viewKey) that takes the registered name of the WorksaceViewModelBase and sets it as the current view.

It gives special attention to the HomeView and offers a special NavigateToHome() method. This is because from almost every place "navigate to home" may be demanded. I think, the implementation for the above mentioned functionality is pretty simple and straight forward and so I am not providing the implementation. Should you want to see that, you can see the internal class Navigator in the source code.

Lazy Loading of View Models

A ViewModel class is the major driving component of an MVVM application. In fact, to run a pure MVVM application, you don't need a View component at all. A mere test project or a console project will do. It's no wonder then, that since ViewModel classes do all the heavy loading, they may have time consuming and memory intensive code for initialization. If you see the ViewModelBase class in the project (I am not pasting the code here), you will find an Initialize method that in turn calls an overridable OnInitialize method. Therefore, any derived ViewModel class can place their initialization code in the overriden OnInitialize method. Fair enough? But when to call the Initialize method of a ViewModel? Is it fine to call it at the application start up? Well, exactly that's the place! But how about this: I initialize the ViewModel that caters to the Manage View and the user doesn't event care to load the Manage screen? I, for nothing, have got all the data that I need in my Manage screen and holding that in memory. Isn't it a better idea to initialize a ViewModel only when the associated view is loaded? But we can't have code behind to do this. Attached behaviour comes to our rescue. If you want to understand what attached behaviors are, I suggest this article by Josh Smith. The InitializeDataContextWhenLoadedProperty does the task of calling the ViewModel's Initialize method whenever an associated view is loaded. Here is the code:

C#
/// <summary>
/// Provides attached properties for loading of an element.
/// </summary>
public static class ElementLoadingBehavior
{
    #region InitializeDataContextWhenLoaded

    /// <summary>
    /// Gets the value of <see cref="InitializeDataContextWhenLoadedProperty">.
    ///
    /// <param name="element" />
    /// <returns><see langword="bool" />
    public static bool GetInitializeDataContextWhenLoaded(FrameworkElement element)
    {
        return (bool)element.GetValue(InitializeDataContextWhenLoadedProperty);
    }

    /// <summary>
    /// sets the value of <see cref="InitializeDataContextWhenLoadedProperty">.
    /// </see></summary>
    /// <param name="element" />
    public static void SetInitializeDataContextWhenLoaded(
      FrameworkElement element, bool value)
    {
        element.SetValue(InitializeDataContextWhenLoadedProperty, value);
    }

    /// <summary>
    /// The attached property that attaches the behaviour to a
    /// <see cref="FrameworkElement">.
    /// </see>
    public static readonly DependencyProperty
           InitializeDataContextWhenLoadedProperty =
        DependencyProperty.RegisterAttached(
        "InitializeDataContextWhenLoaded",
        typeof(bool),
        typeof(ElementLoadingBehavior),
        new UIPropertyMetadata(false, OnInitializeDataContextWhenLoadedChanged));

    /// <summary>
    /// Property changed event handler for
    /// <see cref="InitializeDataContextWhenLoadedProperty">.
    ///
    /// <param name="depObj" />
    /// <param name="e" />
    static void OnInitializeDataContextWhenLoadedChanged
           (DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        var item = depObj as FrameworkElement;
        if (item == null)
            return;
        if (e.NewValue is bool == false)
            return;
        if ((bool)e.NewValue)
            item.Loaded += OnElementLoaded;
        else
            item.Loaded -= OnElementLoaded;
    }

    /// <summary>
    /// The event handler to handle the loaded event of a framework element.
    /// </summary>
    /// <param name="sender" />
    /// <param name="e" />
    static void OnElementLoaded(object sender, RoutedEventArgs e)
    {
        if (!ReferenceEquals(sender, e.OriginalSource))
            return;
        var item = e.OriginalSource as FrameworkElement;
        if (item != null)
        {
            var dataContext = item.DataContext as ViewModelBase;
            if(null!=dataContext && !dataContext.IsInitialized)
            {
                dataContext.Initialize();
            }
        }
    }

    #endregion
}

Thus, with this behavior, our ViewModels are actually initialized on demand.

Native Styles

Although most of the styling in XPence is taken care of by MahApps metro, yet I preferred overriding a few styles. Also styles are provided for the custom controls that are native to XPence.

I always prefer XAML figures over PNG/JPEG images, because of their crisp and clean visual. It is worthwhile to have a look at how MetroButton style is designed in XPence. Here is how the style looks:

XML
<Style x:Key="MetroButtonStyle" TargetType="{x:Type Button}">
       <Setter Property="Foreground" Value="{DynamicResource AccentColorBrush}"/>
       <Setter Property="Template">
           <Setter.Value>
               <ControlTemplate TargetType="{x:Type Button}">
                   <grid x:name="LayoutRoot" background="Transparent"
                         rendertransformorigin="0.5,0.5">
                       <grid.rendertransform>
                           <transformgroup>
                               <scaletransform>
                               <skewtransform>
                               <rotatetransform>
                               <translatetransform>
                           </translatetransform></rotatetransform></skewtransform>

                       <viewbox>
                           <grid x:name="backgroundGrid" width="48"
                                 height="48" visibility="Visible">
                               <path x:name="arrow" data="{TemplateBinding Content}"
                                stretch="Uniform" fill="{TemplateBinding Foreground}"
                                stroke="{TemplateBinding Foreground}" width="26"
                                height="26" strokethickness="0.1">
                               <ellipse x:name="circle" fill="Transparent"
                                stroke="{TemplateBinding Foreground}"
                                width="40" height="40" strokethickness="2">

                   <controltemplate.triggers>
                       <trigger property="IsFocused" value="True">
                       <trigger property="IsDefaulted" value="True">
                       <trigger property="IsMouseOver" value="True">
                           <setter property="RenderTransform" targetname="LayoutRoot">
                               <setter.value>
                                   <transformgroup>
                                       <scaletransform scalex="1.1" scaley="1.1">
                                       <skewtransform>
                                       <rotatetransform>
                                       <translatetransform>

                       <trigger property="IsPressed" value="True">
                           <setter property="Opacity"
                            targetname="LayoutRoot" value="0.7">
                       </setter>
                       <trigger property="IsEnabled" value="False">
                           <setter property="Fill"
                            value="#8B8B8B" targetname="arrow">
                           <setter property="Stroke"
                            value="#8B8B8B" targetname="circle">

This style makes the button expect a Geometry for its content. And the Path in place of ContentPresenter draws the geometry inside the ellipse. The Geometries are stored as keyed resources in a separate ResourceFile. This style though precarious, is smart.

AppearanceManager

Mahapps metro provides a static ThemeManager class that provides methods for changing the theme and accent of your application. XPence uses a wrapper class over the ThemeManager class called AppearanceManager. A number of reasons are there for it:

  • The theme manager of Mahapps metro expects parameters that are native to Mahapps metro. In the future, there might be a need to replace Mahapps with some other library. The change will be limited to the wrapper class.
  • AppearanceManager class provides the themes and accents in the form of strings to the ViewModel layer which is good from "separation of concerns" perspective.
  • There are other components too that need theme and accent changes (like the charting component). AppearanceManager takes care of their needs too.

Here is the code of AppearanceManager:

C#
/// <summary>
    /// A static class that manages the appearance of the application.
    /// </summary>
    public class AppearanceManager
    {
        internal static readonly ResourceDictionary 
                 LightChartResource = new ResourceDictionary { Source = new Uri
 ("pack://application:,,,/XPence.Infrastructure;component/Resources/ChartLight.xaml") };
        internal static readonly ResourceDictionary DarkChartResource = 
                 new ResourceDictionary { Source = new Uri
 ("pack://application:,,,/XPence.Infrastructure;component/Resources/ChartDark.xaml") };

        private static readonly string LightThemeText;
        private static readonly string DarkThemeText;

        #region Constructors

        /// <summary>
        /// Static constructor to initialize static variables.
        /// 
        static AppearanceManager()
        {
            LightThemeText = "Light";
            DarkThemeText = "Dark";
        }

        #endregion

        #region Public Static Methods

        /// <summary>
        /// Gets the accent names.
        /// </summary>
        /// <returns />
        public static IEnumerable<string> GetAccentNames()
        {
            return ThemeManager.DefaultAccents.Select(a => a.Name).ToList();
        }

        /// <summary>
        /// Gets the theme names.
        /// </summary>
        /// <returns>
        public static IEnumerable<string> GetThemeNames()
        {
            var themes = new[] { LightThemeText, DarkThemeText };
            return themes;
        }

        /// <summary>
        /// Gets the accent name that the application is displaying presently.
        /// </summary>
        /// <returns>
        public static string GetApplicationAccent()
        {
            var theme = ThemeManager.DetectTheme(Application.Current);
            return theme.Item2.Name;
        }

        /// <summary>
        /// Gets the theme name that the app is displaying presently.
        /// </summary>
        /// <returns>
        public static string GetApplicationTheme()
        {
            var theme = ThemeManager.DetectTheme(Application.Current);
            if (theme.Item1 == Theme.Dark)
                return DarkThemeText;
            if (theme.Item1 == Theme.Light)
                return LightThemeText;
            throw new Exception("Undetected theme.");
        }

        /// <summary>
        /// Changes the accent of the application.
        /// 
        /// <param name="accentName" />The name of the accent color.
        public static void ChangeAccent(string accentName)
        {
            var theme = ThemeManager.DetectTheme(Application.Current);
            var accent = ThemeManager.DefaultAccents.First(x => x.Name == accentName);
            ThemeManager.ChangeTheme(Application.Current, accent, theme.Item1);
        }

        /// <summary>
        /// Changes the theme of the application.
        /// 
        /// <param name="themeName" />The name of the theme.
        public static void ChangeTheme(string themeName)
        {
            ChangeThemeForGraph(Application.Current.Resources, themeName);
            if (string.CompareOrdinal(LightThemeText, themeName) == 0)
            {
                var theme = ThemeManager.DetectTheme(Application.Current);
                ThemeManager.ChangeTheme(Application.Current, theme.Item2, Theme.Light);
            }
            else if (string.CompareOrdinal(DarkThemeText, themeName) == 0)
            {
                var theme = ThemeManager.DetectTheme(Application.Current);
                ThemeManager.ChangeTheme(Application.Current, theme.Item2, Theme.Dark);
            }
            else
            {
                throw new ValueUnavailableException("Theme name not known.");
            }
        }

        private static void ChangeThemeForGraph
                (ResourceDictionary resources, string themeName)
        {
            if (resources == null) 
                return;
            ResourceDictionary oldChartThemeResource;
            ResourceDictionary newChartThemeResource;
            if (string.CompareOrdinal(LightThemeText, themeName) == 0)
            {
                oldChartThemeResource = DarkChartResource;
                newChartThemeResource = LightChartResource;

            }
            else if (string.CompareOrdinal(DarkThemeText, themeName) == 0)
            {
                oldChartThemeResource = LightChartResource;
                newChartThemeResource = DarkChartResource;
            }
            else
            {
                throw new ValueUnavailableException
                      ("Theme resource not found for graph.");
            }
            if (oldChartThemeResource != null)
            {
                var md = resources.MergedDictionaries.FirstOrDefault
                         (d => d.Source == oldChartThemeResource.Source);
                if(null!=md)
                {
                    resources.MergedDictionaries.Add(newChartThemeResource);
                    var chartThemeChanged = resources.MergedDictionaries.Remove(md);
                    if(!chartThemeChanged)
                    {
                        throw new Exception("Theme for chart could not be changed");
                    }
                }
            }
        }

        #endregion
    }

Custom Controls 1: NavigationButtonControl
Login screen

I have been a Winforms programmer before. When I was introduced to WPF, I was amazed by the immense power it put in the hands of the UI developer. No matter how high your imagination runs as a UI developer, WPF lets you attain that. I believe, and I believe most of you would, that writing custom controls in WPF is far easier than in Winforms. The very new things that were introduced with WPF viz: DependencyProperties, ContentTemplate, DataTemplate let you do virtually anything you would want to do to show the data your way. There is no more need to delve into the complex GDI drawing code. To put in my two cents: a serious WPF developer, though not a pro in writing custom controls, should be able to write simple custom controls if he wants to have the independence to have wild imagination (no naughty meaning intended!).

When it comes to writing custom controls, it is always a rewarding decision to put it in an entirely new library (until and unless it is very specific to your application and contains its logic). Expense uses a number of custom control, but I put the simplest of them: NavigationButtonControl in the custom controls library. This is done to highlight the idea of WPF control libraries as reusable components.

How It Happened?

I needed a nice template for my buttons that would navigate the user to my Linkedin and google+ profile page. I took it as an overambitious opportunity to write a custom control. While some of you may suggest a better way of doing, I start by choosing the base class for my custom control. This is how I start:

  • Choose an existing control that is as close in functionality to your control as possible. In my case, it is button.
  • Using a disassembler (or even better, the object browser in Visual Studio), have a look at the base types it implements.
  • Figure out the base class that gives you the least common functionalities you want.

I was very clear about the functionalities I wanted in my custom control:

  • It would let me have a content.
  • It would let me override its template
  • It would be intelligent enough to understand when it is mouse pressed. (Why? Will come to that later.)
  • It would give me a Command dependency property to which I could bind an ICommand property of my view model.

I did not want:

  • Any exposed click event. (I wanted to force the user to use command binding. MVVM haters would hate me for this!)
  • Any properties like IsDefault, IsCancelled, etc.

Here is what the object browser shows me:

Login screen

It is definitely not difficult to guess that I had to write a replacement of ButtonBase class. The code for NavigationButtonControl is given below:

C#
/// <summary>
/// A replacement for <see cref="System.Windows.Controls.Primitives.ButtonBase"/>
/// class that lets the consumer have a content and overridable style.
/// when mouse is pressed over it.
/// </summary>
public class NavigationButtonControl : ContentControl, ICommandSource
{
    #region Dependency Properties.

    public static readonly DependencyProperty CommandProperty =
     DependencyProperty.Register("Command", typeof(ICommand),
     typeof(NavigationButtonControl), new PropertyMetadata(null, CommandChanged));
    public static readonly DependencyProperty CommandParameterProperty =
     DependencyProperty.Register("CommandParameter", typeof(object),
     typeof(NavigationButtonControl), new PropertyMetadata(null));
    public static readonly DependencyProperty IsPressedProperty =
     DependencyProperty.Register("IsPressed", typeof(bool),
     typeof(NavigationButtonControl), new PropertyMetadata(false));

    #endregion

    #region Public properties

    /// <summary>
    /// The is pressed property to indicate the state of the control
    /// when mouse is pressed over it.
    /// </summary>
    public bool IsPressed
    {
        get { return (bool)GetValue(IsPressedProperty); }
        set { SetValue(IsPressedProperty, value); }
    }

    #endregion

    #region ICommandSource Members

    /// <summary>
    /// Gets or sets <see cref="ICommand"> implementation that will be fired
    /// when the control is clicked.
    ///
    public ICommand Command
    {
        get
        {
            return (ICommand)GetValue(CommandProperty);
        }
        set
        {
            SetValue(CommandProperty, value);
        }
    }

    /// <summary>
    /// The parameter, the command would pass in the handling method.
    /// </summary>
    public object CommandParameter
    {
        get
        {
            return GetValue(CommandParameterProperty);
        }
        set
        {
            SetValue(CommandParameterProperty, value);
        }
    }

    /// <summary>
    ///
    ///
    public IInputElement CommandTarget
    {
        get { return this; }
    }

    #endregion

    #region Private Static Event handlers

    /// <summary>
    /// Handles when the Command dependency property is changed.
    /// </summary>
    /// <param name="d" />
    /// <param name="e" />
    private static void CommandChanged
            (DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as NavigationButtonControl;
        if (null != control)
        {
            control.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
        }
    }

    #endregion

    #region Private Methods

    /// <summary>
    /// Does the cleaning of old command and
    /// registers to the events of new commands.
    /// </summary>
    /// <param name="oldCommand" />
    /// <param name="newCommand" />
    private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
    {
        // If oldCommand is not null, then we need to remove the handlers.
        if (oldCommand != null)
        {
            RemoveCommand(oldCommand);
        }
        AddCommand(newCommand);
    }

    /// <summary>
    /// Removes the CanExecuted handler from the old command.
    ///
    /// <param name="oldCommand" />
    private void RemoveCommand(ICommand oldCommand)
    {
        EventHandler handler = CanExecuteChanged;
        oldCommand.CanExecuteChanged -= handler;
    }

    /// <summary>
    /// Add the event handlers for the new commands.
    ///
    /// <param name="newCommand" />
    private void AddCommand(ICommand newCommand)
    {
        var handler = new EventHandler(CanExecuteChanged);
        var canExecuteChangedHandler = handler;
        if (newCommand != null)
        {
            newCommand.CanExecuteChanged += canExecuteChangedHandler;
        }
    }

    /// <summary>
    /// If can executed changes for the ICommand,
    /// the control should change its IsEnabled property.
    /// </summary>
    /// <param name="sender" />
    /// <param name="e" />
    private void CanExecuteChanged(object sender, EventArgs e)
    {
        if (Command == null)
            return;
        var command = Command as RoutedCommand;
        // Different handling for routed commands and
        // for plain implementation of ICommand.
        IsEnabled = command != null ? command.CanExecute
        (CommandParameter, CommandTarget) : Command.CanExecute(CommandParameter);
    }

    /// <summary>
    /// This method fires the command.
    /// </summary>
    private void FireAtWill()
    {
        if (Command == null)
            return;
        var command = Command as RoutedCommand;
        if (command != null)
        {
            command.Execute(CommandParameter, CommandTarget);
        }
        else
        {
            Command.Execute(CommandParameter);
        }
    }

    #endregion

    #region Overriden Methods

    /// <summary>
    /// Handles the Mouse down event
    ///
    /// <param name="e" />
    protected override void OnMouseDown(MouseButtonEventArgs e)
    {
        base.OnMouseDown(e);
        IsPressed = true;
        FireAtWill();
    }

    /// <summary>
    /// Handles the mouse leave event.
    ///
    /// <param name="e" />
    protected override void OnMouseLeave(MouseEventArgs e)
    {
        base.OnMouseLeave(e);
        IsPressed = false;
    }

    #endregion

    #region Static Constructor

    /// <summary>
    /// Static constructor to take care of the static properties.
    ///
    static NavigationButtonControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(NavigationButtonControl),
                                                 new FrameworkPropertyMetadata
                                                (typeof(NavigationButtonControl)));
    }

    #endregion
}

The implementation of the interface ICommandSource and the property IsPressed is all it provides. Now while your control is ready, what about its look? If you leave it just like that, it will have the look of its base class which is the lookless ContentControl. I want to give the consumer of my control a default look for my control which he may override. This can be achieved in three simple steps:

  1. Tell the custom control library where is the resource file containing default style for the control is located in the project by placing this in the Assembly.cs of the custom control project.
    C#
    [assembly: ThemeInfo(
        ResourceDictionaryLocation.None, // where theme specific resource 
                                   // dictionaries are located
                                   // (used if a resource is not found in the page, 
                                   // or application resource dictionaries)
        ResourceDictionaryLocation.SourceAssembly //where the generic 
                                   // resource dictionary is located
                                   // (used if a resource is not found in the page, 
                                   // app, or any theme specific resource dictionaries)
    )]

    The comments present with the code explains it all. You are telling WPF where to look for the resources for the control if it is not found anywhere in the application that is consuming it, including the App.xaml. This path is fixed at your project\Themes\Generic.xaml.

  2. Tell the control that you want it to take up the default style. You override the DefaultStyleKey dependency property in the static constructor of the control.
    C#
    DefaultStyleKeyProperty.OverrideMetadata(typeof (NavigationButtonControl),
                   new FrameworkPropertyMetadata(typeof (NavigationButtonControl)));
  3. And now, supply the default style of the control in the \Themes\Generic.xaml file:
    XML
    <Style TargetType="{x:Type ControlsLib:NavigationButtonControl}">
            <Setter Property="MaxWidth" Value="50"/>
            <Setter Property="MaxHeight" Value="23"/>
            <setter property="Padding" value="2">
            <setter property="Margin" value="3">
            <setter property="SnapsToDevicePixels" value="True">
            <setter property="Template">
                <setter.value>
                    <controltemplate targettype=
                        "{x:Type ControlsLib:NavigationButtonControl}">
                        <grid x:name="backgroundGrid" 
                         rendertransformorigin="0.5,0.5" 
                         background="{DynamicResource 
                         {x:Static SystemColors.ControlBrush}}">
                            <contentpresenter snapstodevicepixels=
                            "{TemplateBinding SnapsToDevicePixels}">
                        </contentpresenter></grid>
                        <controltemplate.triggers>
                            <trigger property="IsPressed" value="True">
                                <setter targetname="backgroundGrid" 
                                 property="Background" 
                                 value="{DynamicResource 
                                 {x:Static SystemColors.ControlDarkBrush}}">
                            </setter></trigger>
                            <trigger property="IsMouseOver" value="True">
                                <setter targetname="backgroundGrid" 
                                 property="RenderTransform">
                                    <setter.value>
                                        <scaletransform scalex="1.1" scaley="1.1">
                                    
                                
                                <setter targetname="backgroundGrid" 
                                 property="Background" 
                                 value="{DynamicResource 
                                 {x:Static SystemColors.InactiveBorderBrush}}">
                            </setter>

Custom Controls 2: ImageCropperControl

ImageCropper Screenshot1

Since most of the code for this control is taken from this article by darrellp, I am not going into the working of the control. However, packaging of the work in a custom control has been done in XPence. Image cropper control provides a readonly DependencyProperty to give the cropped image that the user produces by adjusting the cropper rectangle. The control also supports drag drop of JPEG images on it (additionally, picking an image using an open file dialog is also provided).

It was during the writing of this control that I came across the WPF bug that prevents binding a readonly Dependency Property to a ViewModel property using the OnWayToSource binding mode. PushBinding was used to overcome the problem.

The image cropper is considerate enough to give a constant image size of 75 by 75 irrespective what image you drop into it. This is because the output image is going into the db and there has to be a limitation on the size.

The RenderTargetBitmap that is the output of the Image cropper control is converted to byte[] using ImageToBinaryConverter. And this byte array is actually saved in the db. Here is the code for ImageToBinaryConverter:

C#
/// <summary>
/// Am implementation of <see cref="IValueConverter"/> to convert Image
/// to byte array and vice versa.
/// </summary>
public class ImageToBinaryConverter : IValueConverter
{
    #region Implementation of IValueConverter

    /// <summary>
    /// Converts byte[] to Image source.
    ///
    /// <returns>
    /// A converted value.
    /// If the method returns null, the valid null value is used.
    /// </returns>
    /// <param name="value" />The value produced by the binding source.
    /// <param name="targetType" />The type of the binding target property.
    /// <param name="parameter" />The converter parameter to use.
    /// <param name="culture" />The culture to use in the converter.
    public object Convert(object value, Type targetType,
                          object parameter, CultureInfo culture)
    {
        var bytes = value as byte[];
        if (bytes != null && bytes.Length > 0)
        {
            var stream = new MemoryStream(bytes);
            var image = new BitmapImage();
            image.BeginInit();
            image.StreamSource = stream;
            image.EndInit();
            return image;
        }
        return null;
    }

    /// <summary>
    /// Converts ImageSource to byte[]
    /// </summary>
    /// <returns>
    /// A converted value.
    /// If the method returns null, the valid null value is used.
    /// </returns>
    /// <param name="value" />The value that is produced by the binding target.
    /// <param name="targetType" />The type to convert to.
    /// <param name="parameter" />The converter parameter to use.
    /// <param name="culture" />The culture to use in the converter.
    public object ConvertBack(object value, Type targetType,
                              object parameter, CultureInfo culture)
    {
        var renderTargetBitmap = value as RenderTargetBitmap;
        if (null != renderTargetBitmap)
        {
            var bitmapImage = new BitmapImage();
            var bitmapEncoder = new BmpBitmapEncoder();
            bitmapEncoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));

            using (var stream = new MemoryStream())
            {
                bitmapEncoder.Save(stream);
                stream.Seek(0, SeekOrigin.Begin);
                bitmapImage.BeginInit();
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.StreamSource = stream;
                bitmapImage.EndInit();
            }
            var encoder = new JpegBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(bitmapImage));
            byte[] data = null;
            using (var ms = new MemoryStream())
            {
                encoder.Save(ms);
                data = ms.ToArray();
            }
            return data;
        }
        return null;
    }

    #endregion
}

I am sure there is a better way of doing this. I was too hard pressed for time to delve in the imaging details of WPF. If someone suggests a better code for ConvertBack, I will be more than happy to update the code with an acknowledgement.

Here is a screenshot of the control in action:

ImageCropper Screenshot

To Be or Not to Be!

Many times, I have come across the question: Does MVVM mean no code behind? The straight forward answer is: No. The View is a component in a MVVM application and I don't think there is any harm if the code in code behind is strictly keeping its scope within view, e.g., there are two instances within XPence where you will find code behind:

  • Windows within messaging services
  • UserEmblemView

In the former, the messaging service is getting injected inside any ViewModel against an interface and hence the ViewModel in no way, is aware of any view specific code or logic going inside the messaging service. The consumer ViewModels only deal with string or enums. Which in no way hampers their test ability.

In the latter, the user control is used at two instances: first as an emblem in the top right corner of the application view and second as a DataTemplate in the ListBox's ItemTemplate showing users. There is a slight change in the control's appearance in the two instances (visibility of a path). The logic to hide a user component for one case and show for another is entirely scoped within the View component. Therefore, it is perfectly fine to write code behind in this case without breaking the MVVM guidelines. The code behind can be easily avoided by repeating the .xaml of the control in the ItemTemplate of the ListBox. But isn't that spoiling the re usability? I will call that over engineering!

Procrastination: A Lazy Man's Tool?

Procrastination is a lazy man's way of keeping up with yesterday! And therefore, I have made sure that XPence never becomes yesterday for me! There are several points that may stop XPence from becoming a "near-perfect" application. Some of them are attributed to my laziness and the remainder to my "hard pressed for time" schedule. While I am initiating the list with a few points, I am sure you will add to the list:

  1. Bad data models

    The data models in XPence is pretty straight forward: A model to carry transaction data and another one to carry the user date. While a transaction model should have a reference of the associated user model, it just has a string property called ModifiedUser.

  2. Redundant dependency property in ModalCustomMessageDialog

    I wanted to override the control template of the Mahapps metro's MetroWindow that is serving as ModalCustomMessageDialog. The right way would be to override the content template of the window. But I found it easier to create another dependency property called ActualContent, thus bypassing the task of delving into the template of MetroWindow.

  3. NavigationButtonControl is inaccurate

    I created this control in a separate plain WPF application that was not using Mahapps metro and it worked perfect. But when I used it in XPence, it stopped working. I realized any control that is placed in Mahapps MetroWindow has some mouse events getting compromised. This is because Mahapps does serious Mouse event manipulation to make the MetroWindow the MetroWindow. I took the shortcut of firing a command on MouseDown, unlike a real button that fires command on mouse up. The accurate code still lies dead at the bottom of the code file.

I am sorry but I believe honest confession is a better policy than pleading "not guilty" when actually I am "guilty"!

That's it!

Well, that's all I have to share. I am most eager to hear your comments, suggestions, remarks. Even with all my laziness, it has taken considerable effort to bring this up! I am not a seasoned writer like most cool guys out here, yet I have tried to give my best to explain the key ideas that XPence communicates. If you are voting below 5, please at least mention the reason. I would also like to ask, if you liked the article, please vote for it, Also, a comment would be nice as it would let me know if the article offers what people want to know. That would be my take-away from the article.

If you feel certain sections need more explanation, feel free to leave a comment and I will be happy to update the article with an acknowledgement. However, please consider the fact that since there are a lot of concepts that have been touched, every point can't have a very detailed explanation without the length of article becoming insanely large.

History

  • Version 1.0 (23 April 2014): Initial draft
  • Version 1.1 (1 May 2014): Namely the following updates:
    • Fixes for bugs reported by LOKImotive. v.i.z.:
      • The bug was that the amount entered for a transaction would not be reflected in the grid even after saving.
      • A better message if the amount field is left empty
    • Another bug fix that prevented the header text for the selected transaction from changing when a transaction was saved
    • Reduced the image sizes in the article as suggested by DaveAuld
    • Added accent color support to the pie chart
    • Changed content

License

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


Written By
Software Developer (Senior)
India India
Sid loves programming and has technical experience developing desktop based solutions using C#.Net, Winforms, WPF.
He has experience of Software services, I-Banks and Product development environments.
He also has a deep understanding of Product Development Lifecycle and Agile methodology of developing softwares.

Besides programming he is also fond of music, photography and cooking.
He lives in Bangalore.

Comments and Discussions

 
QuestionXPence is just amazing Pin
Member 1077322024-Apr-14 11:17
Member 1077322024-Apr-14 11:17 
GeneralMy vote of 5 Pin
Member 350550124-Apr-14 5:45
Member 350550124-Apr-14 5:45 
GeneralMy vote of 5 Pin
Wayne Gaylard24-Apr-14 5:21
professionalWayne Gaylard24-Apr-14 5:21 
GeneralRe: My vote of 5 Pin
Siddhartha S.24-Apr-14 5:53
Siddhartha S.24-Apr-14 5:53 
GeneralMy vote of 5 Pin
johannesnestler24-Apr-14 4:32
johannesnestler24-Apr-14 4:32 
QuestionAmazing! Pin
Varietes24-Apr-14 3:45
Varietes24-Apr-14 3:45 
Generalawesome article, loved it Pin
cool_manu0723-Apr-14 20:10
cool_manu0723-Apr-14 20:10 
GeneralRe: awesome article, loved it Pin
Siddhartha S.23-Apr-14 20:13
Siddhartha S.23-Apr-14 20:13 
Thanks! Please vote if you liked it!
Regards,
Sid

GeneralMy vote of 5 Pin
ashuvone23-Apr-14 3:53
ashuvone23-Apr-14 3:53 
GeneralRe: My vote of 5 Pin
Siddhartha S.23-Apr-14 16:33
Siddhartha S.23-Apr-14 16:33 
QuestionI was looking for this. Pin
shrknt3522-Apr-14 23:58
shrknt3522-Apr-14 23:58 
AnswerRe: I was looking for this. Pin
Siddhartha S.23-Apr-14 0:10
Siddhartha S.23-Apr-14 0:10 
GeneralRe: I was looking for this. Pin
shrknt3523-Apr-14 4:03
shrknt3523-Apr-14 4:03 
Questionvery nice Pin
Sacha Barber22-Apr-14 19:15
Sacha Barber22-Apr-14 19:15 
AnswerRe: very nice Pin
Siddhartha S.22-Apr-14 19:39
Siddhartha S.22-Apr-14 19:39 
GeneralRe: very nice Pin
Sacha Barber29-Apr-14 5:04
Sacha Barber29-Apr-14 5:04 
GeneralNeat and nice Pin
J. Wijaya22-Apr-14 16:23
J. Wijaya22-Apr-14 16:23 
GeneralRe: Neat and nice Pin
Siddhartha S.22-Apr-14 18:14
Siddhartha S.22-Apr-14 18:14 

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.