Caliburn Micro Part 5: The Window Manager

Hello again and welcome to the next post in our Caliburn Micro tutorial series. A relatively simple tutorial this time where we will be looking at the Window Manager. Before we begin, here are the links to the previous tutorials in this series:

Part 1: Getting Started
Part 2: Data Binding and Events
Part 3: More About Events and Parameters
Part 4: The Event Aggregator

Many of you who have played around with Caliburn Micro will know there is not much mentioned about the Window Manager. Due to this, I won’t be covering everything about the Window Manager, I’ll simply explain what I know about it. To demonstrate how to use the Window Manager, we will be extending the application we made in the first blog post of this series.

Using a Window Manager

You may remember from the getting started blog post that one of the first things we did was delete MainWindow.xaml from the project. Caliburn Micro takes care of initializing the window, setting its data context and displaying the appropriate view for us. The Window Manager is one of the mechanisms responsible for getting this done. When you run an application built with Caliburn Micro, the Window Manager is automatically used to create the startup window. For small applications, this is all you really need to know about the Window Manager. When you are building larger applications that need to display other windows or dialogs, it’s time to learn how to use the Window Manager. To demonstrate this we will add a button to the application which will open a new window when clicked. Start by adding a button to AppView.xaml and hooking the click event to a method in AppViewModel.cs. You can do this using Caliburn Micro conventions as explained in the previous tutorials. I have called this method “OpenWindow”. In the OpenWindow method we are going to need access to an instance of a Window Manager. Although we could simply create a new instance of WindowManager and use that, it is best practice to get a hold of the global Window Manager instance that Caliburn Micro makes available to the application. We can do this by making a constructor on AppViewModel.cs that takes in an IWindowManager and store it in a field. You may remember from part 4 of this blog series of what needs to be done when you create a constructor on a view model that has at least 1 parameter. Here is a recap in 3 easy step:

1. Update the bootstrapper as follows. Remember to add System.ComponentModel.Composition.dll as a reference in your project.

using Caliburn.Micro;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
 
public class AppBootstrapper : Bootstrapper<AppViewModel>
{
  private CompositionContainer container;
 
  protected override void Configure()
  {
    container = new CompositionContainer(new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));
 
    CompositionBatch batch = new CompositionBatch();
 
    batch.AddExportedValue<IWindowManager>(new WindowManager());
    batch.AddExportedValue<IEventAggregator>(new EventAggregator());
    batch.AddExportedValue(container);
 
    container.Compose(batch);
  }
 
  protected override object GetInstance(Type serviceType, string key)
  {
    string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
    var exports = container.GetExportedValues<object>(contract);
 
    if (exports.Count() > 0)
    {
      return exports.First();
    }
 
    throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
  }
}

2. Use the Export attribute on the AppViewModel class:

[Export(typeof(AppViewModel))]
public class AppViewModel : PropertyChangedBase
{
  ...
}

3. Use the ImportingConstructor attribute on the AppViewModel constructor:

private readonly IWindowManager _windowManager;
 
[ImportingConstructor]
public AppViewModel(IWindowManager windowManager)
{
  _windowManager = windowManager;
}

Now we can use the Window Manager instance in our OpenWindow method to open a new window. To keep this tutorial simple, we will simply create a new window that also uses the AppViewModel as the data context. This is done using the following code:

public void OpenWindow()
{
  _windowManager.ShowWindow(new AppViewModel(_windowManager));
}

Run this up and press the button to see another window appear. All we needed to do was pass in an instance of a view model, then everything else from creating the window instance and displaying the appropriate view for our view model is done for us. Once again Caliburn Micro makes our life easier!

Using the Window Manager in Caliburn Micro

The Window Manager has all sorts of methods and method overloads for opening windows, dialogs and popups. All these methods are fairly straight forward and easy to use. You can see more usage of the Window Manager in the HelloWindowManager sample that comes with Caliburn Micro. One more thing I wanted to look at is the ‘settings’ parameter. Here you can pass in a dynamic object used to set properties on the new window. This gives you fine-grained control on the appearance of your application if you need it. Here is an example where I am setting the WindowStartupLocation to be manual rather than center-owner.

public void OpenWindow()
{
  dynamic settings = new ExpandoObject();
  settings.WindowStartupLocation = WindowStartupLocation.Manual;
 
  _windowManager.ShowWindow(new AppViewModel(_windowManager), null, settings);
}

Custom Window Managers

There are scenarios where it is useful to implement a custom Window Manager. This is good if you need to set properties on all Window instances to be common values. For example, properties could include the icon, the window state, window size and applying custom chrome. The most useful property that I have found to set on windows is the SizeToContent property. By default, Caliburn Micro sets this to be SizeToContent.WidthAndHeight. This means that the window automatically sizes itself based on its content. Although this can be convenient at times, I have found this to cause some issues with certain application layouts and seems to be buggy when setting the window to be maximized by default. Creating a custom Window Manager is very simple. Start by adding a class that extends WindowManager. Next, you can override the EnsureWindow method and do something like the following:

protected override Window EnsureWindow(object model, object view, bool isDialog)
{
  Window window = base.EnsureWindow(model, view, isDialog);
 
  window.SizeToContent = SizeToContent.Manual;
 
  return window;
}

In this method, we start by calling base.EnsureWindow() to create the window instance. Next you can set any properties on the window that you want, and then simply return the window instance. The last step is to set an instance of your custom Window Manager to be globally used by the application. In the first code snippet in this blog post we make modifications to the bootstrapper. Here you can see we are adding a WindowManager instance to the CompositionBatch. You can replace this to use an instance of your custom window manager instead which would look like this:

batch.AddExportedValue<IWindowManager>(new AppWindowManager());

That’s everything I wanted to cover with the Window Manager, I hope you found it useful in building great WPF applications powered by Caliburn Micro! I appreciate your comments and feedback on the series!

Download the full Visual Studio 2010 project for this tutorial.

Next time I’ll give you an introduction to screens and conductors.

Happy coding :)

Tagged as WPF

19 Responses to “Caliburn Micro Part 5: The Window Manager”

  • […] Caliburn Micro Part 5: The Window Manager (Jason Fauchelle) […]

  • Really enjoying this series of posts – keep them coming!

  • This is great stuff. Please keep it up!!! Thanks

  • please continue this tutorial with screen conductor
    we’ll be happy for it ;)
    thanks

  • I just begin to use Caliburn Micro. There aren’t many tutorials on it. Lucky we have yours!
    Hope you’ll continue those tutorials! Very good job!
    Thanks

  • Very helpful and well written! Thanks for sharing.

  • You have a great way of explaining the material, and showing simple and consise examples. Keep up the good work. Really appreciate it.

  • Hi Jason – Thanks for a great set of articles. Been re-visting them in detail as I need to get up to speed with WPF/Caliburn.Micro now!

    Great that you have the DisplayName method shown in the source code for this part. I’d been wondering how that worked.

    A couple of items that may be helpful to others:

    In EnsureWindow I also used the following line:
    window.Title = “Give me a DisplayName in the ViewModel”;
    This will be the default Window title for all Windows using AppWindowManager ! Just reminds me to place the DisplayName if I forget!
    Also reminds us about the lack of consistency – Window.Title and then DisplayName for the same item.

    In case anyone gets bored with generating the same Window when pressing the button (an infinite sequence of the same window!):
    Add a second ViewModel and View (say SubViewModel.cs and SubView.xaml).
    Add a second button to AppView with Name = “OpenSubWindow” then add the following to the AppViewModel:

    public void OpenSubWindow()
    {
    _windowManager.ShowWindow(new SubViewModel(_windowManager));
    }

    Obviously you can use this to open as many windows as you like.

    Now – my question: How can I hide a Window if I have displayed a second (but get it to “unhide” when the second window is closed?

  • Hello James

    To hide a window you can call Window.Hide, and to unhide it again, you can call window.Show. There are several ways you could manage when to hide and show the windows. A good way to do this would be to keep track of the current window in the window manager, then hide that window when a new window is opened. You can set the Owner of the new window to be the previous window, and attach a Closed event handler that will get the Owner and show it again. Here is a simple update to the AppWindowManager that achieves this:

    public class AppWindowManager : WindowManager
    {
    private Window _currentWindow = null;

    protected override Window EnsureWindow(object model, object view, bool isDialog)
    {
    Window window = base.EnsureWindow(model, view, isDialog);

    window.SizeToContent = SizeToContent.Manual;
    window.Width = 300;
    window.Height = 300;

    if(_currentWindow != null)
    {
    window.Owner = _currentWindow;
    _currentWindow.Hide();
    }
    window.Closed += new EventHandler(Window_Closed);
    _currentWindow = window;

    return window;
    }

    private void Window_Closed(object sender, EventArgs e)
    {
    Window window = sender as Window;
    window.Closed -= new EventHandler(Window_Closed);
    if (window.Owner != null)
    {
    window.Owner.Show();
    }
    }
    }

    Hope this helps!

  • Brilliant series that really picked me off a painful path. Thank you.

    Any hopes of a part 6 with Conductors? I’d be eternally grateful!

  • When I have time I’ll look at continuing this series, starting with a Conductors tutorial.

  • I look forward to it, Jason. In the meantime, i’ll RTFM ;)

  • Nice series man, good job. It’s nice to have a reference like this sometimes, keep it up!

  • Hi Jason,

    Great tutorial series! Good man, many thanks to you.

    I’m still left wondering how to properly deal with simple dialog boxes, eg. to replace MessageBox.Show(…) or to extend further to more complex Inputs.

    I thought I could just use this WindowManager and launch a new ViewModel like you’ve described here, but then how does that spawned ViewModel close its own window? Would I have to Import the window manager and then close itself?

    It seems like this has all been built-in with the IResult and DialogHandler stuff, I just don’t understand it. I also can’t find any code examples that compile/run- even after looking here: http://caliburnmicro.codeplex.com/discussions/250303

    Is there some downloadable examples of all these dialogs in action? Am I missing some good example resources?

    Cheers
    Stu

  • Hello Stu

    Glad you like our Caliburn Micro tutorial series.

    I have not done anything with dialogs, and I don’t know of any resources so I don’t know the best answer. Importing the window manager is the approach I myself would first try. If I get around to playing with dialogs in Caliburn Micro I’ll be sure to write a blog post about what I do.

    -Jason

  • Hi Jason,

    I’ve checked that IWindowManager is necessary for the class if it intends to open another classes in a window, but if we only want to open eg. AnotherViewModel from the ‘AppViewModel’ and if we know, that AnotherViewModel doesn’t open any windows we don’t have to even declare the ‘private IWindowManager windowManager’ in AnotherViewModel.
    When doing this in ‘AppViewModel’ we have:
    public void OpenWindow()
    {
    _windowManager.ShowWindow(new AnotherViewModel( ));
    } //and it works

    . . . so I’ve got a question 4 U:
    Does the omitting of the windowManager in AnotherViewModel have an influence on releasing mamory/GBcollector’s behaviour when the AnotherViewModel has been colsed?

    or maybe I should always declare ‘private IWindowManager windowManager’ and ‘[ImportingConstructor]’ to ensure Caliburn.Micro is aware of AnotherViewModel instance?

    Best regards,

    Hubert

  • Hello Hubert

    I have not explored this myslef, but I highly doubt that you’d need to pass the window manager to AnotherViewModel to release memory. I’d expect that the window manager itself will handle releasing memory for any model in a window that it has created, regardless of which models hold the window manager. If AnotherViewModel is a Screen – and AnotherViewModel does not hold the window manager, then you will find that the OnDeactivate method is still called when it’s window closes (a responsibility of the window manager), I’d expect the same can be said about any behind the scenes memory operations. Might be best for you to look into it a bit further yourself though.

    Regards

    -Jason Fauchelle

  • Sorry 4 spamming, I’m very glad with your quick reply, Thx
    xD

  • Helped me a lot in starting with caliburn…. Thanks buddy!!!

  • Leave a Reply

Archives

Join our mailer

You should join our newsletter! Sent monthly:

Back to Top