My .NET focused coding blog.

Customizing the creation and initialization of content in the Modern UI for WPF

This post is about how you could customize the creation and initialization of the content of a ModernWindow in the Modern UI for WPF library by creating a custom class that implements the FirstFloor.ModernUI.Windows.IContentLoader interface.

The Modern UI for WPF is an open source project on CodePlex that contains a set of controls and styles that WPF developers can use to quickly change the appearance of a WPF desktop application into a great looking “Modern UI” app. There are some screenshots available here that demonstrate what this “Modern UI” look is all about.

To use the Modern UI in your WPF application you can simply install the ModernUI.WPF NuGet package using the Package Manager Console in Visual Studio:

Package Manager Console
This will add the necessary reference to the FirstFloor.ModernUI.dll assembly where the custom classes are defined, along with references to any other dependant assemblies that are required.

The next step is to modify the App.xaml file of your application and add references to the ResourceDictionaries that contain the custom styles. The documentation on CodePlex provides a step-by-step tutorial on how to setup your Modern UI app if you want more information about this.

<Application x:Class="Mm.ModernUISample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.xaml" />
        <ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.Light.xaml"/>
      </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>

  </Application.Resources>
</Application>

ModernWindow

A Modern UI window is a special custom looking kind of window that contains a content area and optionally some additional links that lets the user navigate between different content.

To create a Modern UI window, you could just add an ordinary WPF Window to your solution in Visual Studio as usual and then change the base class of the new window from System.Windows.Window to FirstFloor.ModernUI.Windows.Controls.ModernWindow in both the XAML markup and the code-behind file:

<mui:ModernWindow  x:Class="Mm.ModernUISample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mui="http://firstfloorsoftware.com/ModernUI"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        
    </Grid>
</mui:ModernWindow>
public partial class MainWindow : FirstFloor.ModernUI.Windows.Controls.ModernWindow
{
  public MainWindow() {
    InitializeComponent();
  }
}

The ModernWindow class extends the built-in Window class and has a ContentSource property. The value of this property specifies a System.Uri to a Page or UserControl that defines the content to be loaded when the window is originally displayed on the screen.

Note that the Content property of the window is ignored and all content is rendered by the specified ContentSource. You can use a predefined set of page layouts to change the overall layout of the window if you want to. Refer to the documentation for more information about this.

Dependency Injection

If you are using dependency injection when you create the content view you need to somehow tell the ModernWindow which parameters to inject the Page or UserControl with. The view might for example depend on some service or a view model like the sample UserControl below does:

public interface IViewModel
{
  string Info { get; }
}


public class ViewModel : IViewModel
{
  public string Info {
    get {
      return "Hello demo!";
    }
  }
}

public partial class ContentView : UserControl
  {
    public ContentView(IViewModel viewModel) {
      InitializeComponent();
      this.DataContext = viewModel;
    }
  }
}
<UserControl x:Class="Mm.ModernUISample.ContentView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
  <Grid>
    <TextBlock Text="{Binding Info}"/>
  </Grid>
</UserControl>

The way to do this is to create a class that implements the IContentLoader interface. This interface has a single asynchronous method named LoadContentAsync that takes the content Uri and a System.Threading.CancellationToken and returns a Task<object>. Implementing it is straight forward:

public class MyContentLoader : FirstFloor.ModernUI.Windows.IContentLoader
{
  private readonly IViewModel _viewModel;
  public MyContentLoader(IViewModel viewModel) {
    _viewModel = viewModel;
  }

  public async Task<object> LoadContentAsync(Uri uri, System.Threading.CancellationToken cancellationToken) {
    ContentView view = await Task<ContentView>.Factory.StartNew(() =>
    {
      return new ContentView(_viewModel);
    }, System.Threading.CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
    return view;
  }
}

The method simply creates the view asynchronously and then returns it. Note that the constructor of the sample MyContentLoader class above takes the parameters to inject the view with and then uses them when creating the view in the LoadContentAsync method. Also note that the view is actually created synchronously on the UI thread, like any UI control should, but since the interface declares an asynchronous method you must return a Task.

Once you have implemented the class that implements the interface and creates the view, you then simply set the ContentLoader property of the ModernWindow to an instance of this class. Also note that you must set the ContentSource property of the window to a Uri for the content view to actually show up in the window:

public partial class MainWindow : FirstFloor.ModernUI.Windows.Controls.ModernWindow
{
  public MainWindow() {
    InitializeComponent();
    this.ContentLoader = new MyContentLoader(new ViewModel());
    //you must set the ContentSource to some Uri here...
    this.ContentSource = new Uri("ContentView.xaml", UriKind.Relative);
  }
}

Modern UI

You may also define the constructor of the window itself to accept the same parameter(s) as the content view and simply pass them on to the custom ContentLoader class:

public partial class MainWindow : FirstFloor.ModernUI.Windows.Controls.ModernWindow
  {
    public MainWindow(IViewModel viewModel) {
      InitializeComponent();
      this.ContentLoader = new MyContentLoader(viewModel);
      this.ContentSource = new Uri("ContentView.xaml", UriKind.Relative);
    }
  }
}

If you are injecting your application’s start-up window this way, you need to make sure that you initialize the values to be passed to the constructor of the window before an instance of it gets created. The easiest way do to this is to remove the StartupUri attribute from the App.xaml file and create an instance of the MainWindow yourself by overriding the OnStartup method in the App.xaml.cs code-behind file:

public partial class App : Application
{
  protected override void OnStartup(StartupEventArgs e) {
    base.OnStartup(e);

    MainWindow mainWindow = new MainWindow(new ViewModel());
    mainWindow.Show();
  }
}

You could of course also use an IoC of your choice, like for example Unity, NInject or StructureMap, to resolve the dependencies for you but how to setup such a container and register the mappings between types is out of scope of this post. The key to create a content view whose constructor accepts one or more arguments is to create a class that implements the ContentLoader interface and set the ContentLoader property of the ModernWindow to an instance of this type.



Leave a comment