Actions, Coroutines and Conventions tend to draw the most attention to Caliburn.Micro, but the Screens and Conductors piece is probably most important to understand if you want your UI to be engineered well. It’s particularly important if you want to leverage composition. The terms Screen, Screen Conductor and Screen Collection have more recently been codified by Jeremy Miller during his work on the book “Presentation Patterns” for Addison Wesley. While these patterns are primarily used in CM by inheriting ViewModels from particular base classes, it’s important to think of them as roles rather than as View-Models. In fact, depending on your architecture, a Screen could be a UserControl, Presenter or ViewModel. That’s getting a little ahead of ourselves though. First, let’s talk about what these things are in general.
This is the simplest construct to understand. You might think of it as a stateful unit of work existing within the presentation tier of an application. It’s independent from the application shell. The shell may display many different screens, some even at the same time. The shell may display lots of widgets as well, but these are not part of any screen. Some screen examples might be a modal dialog for application settings, a code editor window in Visual Studio or a page in a browser. You probably have a pretty good intuitive sense about this.
Often times a screen has a lifecycle associated with it which allows the screen to perform custom activation and deactivation logic. This is what Jeremy calls the ScreenActivator. For example, take the Visual Studio code editor window. If you are editing a C# code file in one tab, then you switch to a tab containing an XML document, you will notice that the toolbar icons change. Each one of those screens has custom activation/deactivation logic that enables it to setup/teardown the application toolbars such that they provide the appropriate icons based on the active screen. In simple scenarios, the ScreenActivator is often the same class as the Screen. However, you should remember that these are two separate roles. If a particular screen has complex activation logic, it may be necessary to factor the ScreenActivator into its own class in order to reduce the complexity of the Screen. This is particularly important if you have an application with many different screens, but all with the same activation/deactivation logic.
Once you introduce the notion of a Screen Activation Lifecycle into your application, you need some way to enforce it. This is the role of the ScreenConductor. When you show a screen, the conductor makes sure it is properly activated. If you are transitioning away from a screen, it makes sure it gets deactivated. There’s another scenario that’s important as well. Suppose that you have a screen which contains unsaved data and someone tries to close that screen or even the application. The ScreenConductor, which is already enforcing deactivation, can help out by implementing Graceful Shutdown. In the same way that your Screen might implement an interface for activation/deactivation, it may also implement some interface which allows the conductor to ask it “Can you close?” This brings up an important point: in some scenarios deactivating a screen is the same as closing a screen and in others, it is different. For example, in Visual Studio, it doesn’t close documents when you switch from tab to tab. It just activates/deactivates them. You have to explicitly close the tab. That is what triggers the graceful shutdown logic. However, in a navigation based application, navigating away from a page would definitely cause deactivation, but it might also cause that page to close. It all depends on your specific application’s architecture and it’s something you should think carefully about.
In an application like Visual Studio, you would not only have a ScreenConductor managing activation, deactivation, etc., but would also have a ScreenCollection maintaining the list of currently opened screens or documents. By adding this piece of the puzzle, we can also solve the issue of deactivation vs. close. Anything that is in the ScreenCollection remains open, but only one of those items is active at a time. In an MDI-style application like VS, the conductor would manage switching the active screen between members of the ScreenCollection. Opening a new document would add it to the ScreenCollection and switch it to the active screen. Closing a document would not only deactivate it, but would remove it from the ScreenCollection. All that would be dependent on whether or not it answers the question “Can you close?” positively. Of course, after the document is closed, the conductor needs to decide which of the other items in the ScreenCollection should become the next active document.
There are lots of different ways to implement these ideas. You could inherit from a TabControl and implement an IScreenConductor interface and build all the logic directly in the control. Add that to your IoC container and you’re off and running. You could implement an IScreen interface on a custom UserControl or you could implement it as a POCO used as a base for Supervising Controllers. ScreenCollection could be a custom collection with special logic for maintaining the active screen, or it could just be a simple IList
These concepts are implemented in CM through various interfaces and base classes which can be used mostly to build ViewModels. Let’s take a look at them:
In Caliburn.Micro we have broken down the notion of screen activation into several interfaces:
In addition to these core lifecycle interfaces, we have a few others to help in creating consistency between presentation layer classes:
Because certain combinations are so common, we have some convenience interfaces and base classes:
What this all means is that you will probably inherit most of your view models from either PropertyChangedBase or Screen. Generally speaking, you would use Screen if you need any of the activation features and PropertyChangedBase for everything else. CM’s default Screen implementation has a few additional features as well and makes it easy to hook into the appropriate parts of the lifecycle:
So, just to re-iterate: if you need a lifecycle, inherit from Screen; otherwise inherit from PropertyChangedBase.
As I mentioned above, once you introduce lifecycle, you need something to enforce it. In Caliburn.Micro, this role is represented by the IConductor interface which has the following members:
We also have an interface called IConductActiveItem which composes IConductor and IHaveActiveItem to add the following member:
You may have noticed that CM’s IConductor interface uses the term “item” rather than “screen” and that I was putting the term “screen collection” in quotes. The reason for this is that CM’s conductor implementations do not require the item being conducted to implement IScreen or any particular interface. Conducted items can be POCOs. Rather than enforcing the use of IScreen, each of the conductor implementations is generic, with no constraints on the type. As the conductor is asked to activate/deactivate/close/etc each of the items it is conducting, it checks them individually for the following fine-grained interfaces: IActivate, IDeactivate, IGuardClose and IChild. In practice, I usually inherit conducted items from Screen, but this gives you the flexibility to use your own base class, or to only implement the interfaces for the lifecycle events you care about on a per-class basis. You can even have a conductor tracking heterogeneous items, some of which inherit from Screen and others that implement specific interfaces or none at all.
Out of the box CM has three implementations of IConductor, two that work with a “screen collection” and one that does not. We’ll look at the conductor without the collection first.
This simple conductor implements the majority of IConductor’s members through explicit interface mechanisms and adds strongly typed versions of the same methods which are available publicly. This allows working with conductors generically through the interface as well as in a strongly typed fashion based on the items they are conducting. The Conductor
This implementation has all the features of Conductor
Similarly, this implementation also has the features of Conductor
There are two very important details about CMs IConductor implementations that I have not mentioned yet. First, they both inherit from Screen. This is a key feature of these implementations because it creates a composite pattern between screens and conductors. So, let’s say you are building a basic navigation-style application. Your shell would be an instance of Conductor
Not everything in CM that can be a screen is rooted inside of a Conductor. For example, what about the your root view model? If it’s a conductor, who is activating it? Well, that’s one of the jobs that the Bootstrapper performs. The Bootstrapper itself is not a conductor, but it understands the fine-grained lifecycle interfaces discussed above and ensures that your root view model is treated with the respect it deserves. The WindowManager works in a similar way by acting a little like a conductor for the purpose of enforcing the lifecycle of your modal (and modeless - WPF only) windows. So, lifecycle isn’t magical. All your screens/conductors must be either rooted in a conductor or managed by the Bootstrapper or WindowManager to work properly; otherwise you are going to need to manage the lifecycle yourself.
If you are working with WP7 or using the Silverlight Navigation Framework, you may be wondering if/how you can leverage screens and conductors. So far, I’ve been assuming a primarily ViewModel-First approach to shell engineering. But the WP7 platform enforces a View-First approach by controlling page navigation. The same is true of the SL Nav framework. In these cases, the Phone/Nav Framework acts like a conductor. In order to make this play well with ViewModels, the WP7 version of CM has a FrameAdapter which hooks into the NavigationService. This adapter, which is set up by the PhoneBootstrapper, understands the same fine-grained lifecycle interfaces that conductors do and ensures that they are called on your ViewModels at the appropriate points during navigation. You can even cancel the phone’s page navigation by implementing IGuardClose on your ViewModel. While the FrameAdapter is only part of the WP7 version of CM, it should be easily portable to Silverlight should you wish to use it in conjunction with the Silverlight Navigation Framework.
Previously, we discussed the theory and basic APIs for Screens and Conductors in Caliburn.Micro. Now I would like to walk through the first of several samples. This particular sample demonstrates how to set up a simple navigation-style shell using Conductor
public class ShellViewModel : Conductor<object> {
public ShellViewModel() {
ShowPageOne();
}
public void ShowPageOne() {
ActivateItem(new PageOneViewModel());
}
public void ShowPageTwo() {
ActivateItem(new PageTwoViewModel());
}
}
Here is the corresponding ShellView:
<UserControl x:Class="Caliburn.Micro.SimpleNavigation.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tc="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit">
<tc:DockPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
tc:DockPanel.Dock="Top">
<Button x:Name="ShowPageOne"
Content="Show Page One" />
<Button x:Name="ShowPageTwo"
Content="Show Page Two" />
</StackPanel>
<ContentControl x:Name="ActiveItem" />
</tc:DockPanel>
</UserControl>
Notice that the ShellViewModel has two methods, each of which passes a view model instance to the ActivateItem method. Recall from our earlier discussion that ActivateItem is a method on Conductor
Once the basic Conductor structure is in place, it’s quite easy to get it rendering. The ShellView demonstrates this. All we have to do is place a ContentControl in the view. By naming it “ActiveItem” our data binding conventions kick in. The convention for ContentControl is a bit interesting. If the item we are binding to is not a value type and not a string, then we assume that the Content is a ViewModel. So, instead of binding to the Content property as we would in the other cases, we actually set up a binding with CM’s custom attached property: View.Model. This property causes CM’s ViewLocator to look up the appropriate view for the view model and CM’s ViewModelBinder to bind the two together. Once that is complete, we pop the view into the ContentControl’s Content property. This single convention is what enables the powerful, yet simple ViewModel-First composition in the framework.
For completeness, let’s take a look at the PageOneViewModel and PageTwoViewModel:
public class PageOneViewModel {}
public class PageTwoViewModel : Screen {
protected override void OnActivate() {
MessageBox.Show("Page Two Activated"); //Don't do this in a real VM.
base.OnActivate();
}
}
Along with their views:
<UserControl x:Class="Caliburn.Micro.SimpleNavigation.PageOneView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock FontSize="32">Page One</TextBlock>
</UserControl>
<UserControl x:Class="Caliburn.Micro.SimpleNavigation.PageTwoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock FontSize="32">Page Two</TextBlock>
</UserControl>
I’d like to point out a few final things. Notice that PageOneViewModel is just a POCO, but PageTwoViewModel inherits from Screen. Remember that the conductors in CM don’t place any constraints on what can be conducted. Rather, they check each instance for support of the various fine-grained lifecycle instances at the necessary times. So, when ActivateItem is called for PageTwoViewModel, it will first check PageOneViewModel to see if it implements IGuardClose. Since it does not, it will attempt to close it. It will then check to see if it implements IDeactivate. Since it does not, it will just proceed to activate the new item. First it checks if the new item implements IChild. Since Screen does, it hooks up the hierarchical relationship. Next, it will check PageTwoViewModel to see if it implements IActivate. Since Screen does, the code in my OnActivate method will then run. Finally, it will set the ActiveItem property on the conductor and raise the appropriate events. Here’s an important consequence of this that should be remembered: The activation is a ViewModel-specific lifecycle process and doesn’t guarantee anything about the state of the View. Many times, even though your ViewModel has been activated, its view may not yet be visible. You will see this when you run the sample. The MessageBox will show when the activation occurs, but the view for page two will not yet be visible. Remember, if you have any activation logic that is dependent on the view being already loaded, you should override Screen.OnViewLoaded instead of/in combination with OnActivate.
Let’s look at another example: this time a simple MDI shell that uses “Screen Collections.” As you can see, once again, I have kept things pretty small and simple:
Here’s a screenshot of the application when it’s running:
Here we have a simple WPF application with a series of tabs. Clicking the “Open Tab” button does the obvious. Clicking the “X” inside the tab will close that particular tab (also, probably obvious). Let’s dig into the code by looking at our ShellViewModel:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive {
int count = 1;
public void OpenTab() {
ActivateItem(new TabViewModel {
DisplayName = "Tab " + count++
});
}
}
Since we want to maintain a list of open items, but only keep one item active at a time, we use Conductor
Those are the main scenarios. Hopefully you can see some of the differences from a Conductor without a collection and understand why those differences are there. Let’s see how the ShellView renders:
<Window x:Class="Caliburn.Micro.SimpleMDI.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org"
Width="640"
Height="480">
<DockPanel>
<Button x:Name="OpenTab"
Content="Open Tab"
DockPanel.Dock="Top" />
<TabControl x:Name="Items">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayName}" />
<Button Content="X"
cal:Message.Attach="DeactivateItem($dataContext, 'true')" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</DockPanel>
</Window>
As you can see we are using a WPF TabControl. CM’s conventions will bind its ItemsSource to the Items collection and its SelectedItem to the ActiveItem. It will also add a default ContentTemplate which will be used to compose in the ViewModel/View pair for the ActiveItem. Conventions can also supply an ItemTemplate since our tabs all implement IHaveDisplayName (through Screen), but I’ve opted to override that by supplying my own to enable closing the tabs. We’ll talk more in depth about conventions in a later article. For completeness, here are the trivial implementations of TabViewModel along with its view:
namespace Caliburn.Micro.SimpleMDI {
public class TabViewModel : Screen {}
}
<UserControl x:Class="Caliburn.Micro.SimpleMDI.TabView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal">
<TextBlock Text="This is the view for "/>
<TextBlock x:Name="DisplayName" />
<TextBlock Text="." />
</StackPanel>
</UserControl>
I’ve tried to keep it simple so far, but that’s not the case for our next sample. In preparation, you might want to at least think through or try to do these things:
This sample is based loosely on the ideas demonstrated by Billy Hollis in this well-known DNR TV episode. Rather than take the time to explain what the UI does, have a look at this short video for a brief visual explanation.
Ok, now that you’ve seen what it does, let’s look at how it’s put together. As you can see from the screenshot, I’ve chosen to organize the project by features: Customers, Orders, Settings, etc. In most projects I prefer to do something like this rather than organizing by “technical” groupings, such as Views and ViewModels. If I have a complex feature, then I might break that down into those areas.
I’m not going to go line-by-line through this sample. It’s better if you take the time to look through it and figure out how things work yourself. But, I do want to point out a few interesting implementation details.
One of the most important features of Screens and Conductors in Caliburn.Micro is that they are an implementation of the Composite Pattern, making them easy to compose together in different configurations. Generally speaking, composition is one of the most important aspects of object oriented programming and learning how to use it in your presentation tier can yield great benefits. To see how composition plays a role in this particular sample, let’s look at two screenshots. The first shows the application with the CustomersWorkspace in view, editing a specific Customer’s Address. The second screen is the same, but with its View/ViewModel pairs rotated three-dimensionally, so you can see how the UI is composed.
Editing a Customer’s Address
Editing a Customer’s Address (3D Breakout)
In this application, the ShellViewModel is a Conductor
You may not be aware of this, but Caliburn.Micro can display multiple Views over the same ViewModel. This is supported by setting the View.Context attached property on the View/ViewModel’s injection site. Here’s an example from the default CustomerWorkspaceView:
<clt:TransitioningContentControl cal:View.Context="{Binding State, Mode=TwoWay}"
cal:View.Model="{Binding}"
Style="{StaticResource specialTransition}"/>
There is a lot of other Xaml surrounding this to form the chrome of the CustomerWorkspaceView, but the content region is the most noteworthy part of the view. Notice that we are binding the View.Context attached property to the State property on the CustomerWorkspaceViewModel. This allows us to dynamically change out views based on the value of that property. Because this is all hosted in the TransitioningContentControl, we get a nice transition whenever the view changes. This technique is used to switch the CustomerWorkspaceViewModel from a “Master” view, where it displays all open CustomerViewModels, a search UI and a New button, to a “Detail” view, where it displays the currently activated CustomerViewModel along with its specific view (composed in). In order for CM to find these contextual views, you need a namespace based on the ViewModel name, minus the words “View” and “Model”, with some Views named corresponding to the Context. For example, when the framework looks for the Detail view of Caliburn.Micro.HelloScreens.Customers.CustomersWorkspaceViewModel, it’s going to look for Caliburn.Micro.HelloScreens.Customers.CustomersWorkspace.Detail That’s the out-of-the-box naming convention. If that doesn’t work for you, you can simply customize the ViewLocator.LocateForModelType func.
Although Caliburn.Micro provides the developer with default implementations of IScreen and IConductor. It’s easy to implement your own. In the case of this sample, I needed a dialog manager that could be modal to a specific part of the application without affecting other parts. Normally, the default Conductor
[Export(typeof(IDialogManager)), PartCreationPolicy(CreationPolicy.NonShared)]
public class DialogConductorViewModel : PropertyChangedBase, IDialogManager, IConductActiveItem {
readonly Func<IMessageBox> createMessageBox;
[ImportingConstructor]
public DialogConductorViewModel(Func<IMessageBox> messageBoxFactory) {
createMessageBox = messageBoxFactory;
}
public IScreen ActiveItem { get; private set; }
public IEnumerable GetChildren() {
return ActiveItem != null ? new[] { ActiveItem } : new object[0];
}
public void ActivateItem(object item) {
ActiveItem = item as IScreen;
var child = ActiveItem as IChild;
if(child != null)
child.Parent = this;
if(ActiveItem != null)
ActiveItem.Activate();
NotifyOfPropertyChange(() => ActiveItem);
ActivationProcessed(this, new ActivationProcessedEventArgs { Item = ActiveItem, Success = true });
}
public void DeactivateItem(object item, bool close) {
var guard = item as IGuardClose;
if(guard != null) {
guard.CanClose(result => {
if(result)
CloseActiveItemCore();
});
}
else CloseActiveItemCore();
}
object IHaveActiveItem.ActiveItem
{
get { return ActiveItem; }
set { ActivateItem(value); }
}
public event EventHandler<ActivationProcessedEventArgs> ActivationProcessed = delegate { };
public void ShowDialog(IScreen dialogModel) {
ActivateItem(dialogModel);
}
public void ShowMessageBox(string message, string title = "Hello Screens", MessageBoxOptions options = MessageBoxOptions.Ok, Action<IMessageBox> callback = null) {
var box = createMessageBox();
box.DisplayName = title;
box.Options = options;
box.Message = message;
if(callback != null)
box.Deactivated += delegate { callback(box); };
ActivateItem(box);
}
void CloseActiveItemCore() {
var oldItem = ActiveItem;
ActivateItem(null);
oldItem.Deactivate(true);
}
}
Strictly speaking, I didn’t actually need to implement IConductor to make this work (since I’m not composing it into anything). But I chose to do this in order to represent the role this class was playing in the system and keep things as architecturally consistent as possible. The implementation itself is pretty straight forward. Mainly, a conductor needs to make sure to Activate/Deactivate its items correctly and to properly update the ActiveItem property. I also created a couple of simple methods for showing dialogs and message boxes which are exposed through the IDialogManager interface. This class is registered as NonShared with MEF so that each portion of the application that wants to display local modals will get its own instance and be able to maintain its own state, as demonstrated with the CustomerViewModel discussed above.
Possibly one of the coolest features of this sample is how we control application shutdown. Since IShell inherits IGuardClose, in the Bootstrapper we just override OnStartup and wire Silverlight’s MainWindow.Closing event to call IShell.CanClose:
protected override void OnStartup(object sender, StartupEventArgs e) {
base.OnStartup(sender, e);
if(Application.IsRunningOutOfBrowser) {
mainWindow = Application.MainWindow;
mainWindow.Closing += MainWindowClosing;
}
}
void MainWindowClosing(object sender, ClosingEventArgs e) {
if (actuallyClosing)
return;
e.Cancel = true;
Execute.OnUIThread(() => {
var shell = IoC.Get<IShell>();
shell.CanClose(result => {
if(result) {
actuallyClosing = true;
mainWindow.Close();
}
});
});
}
The ShellViewModel inherits this functionality through its base class Conductor
[Export(typeof(IShell))]
public class ShellViewModel : Conductor<IWorkspace>.Collection.OneActive, IShell
{
readonly IDialogManager dialogs;
[ImportingConstructor]
public ShellViewModel(IDialogManager dialogs, [ImportMany]IEnumerable<IWorkspace> workspaces) {
this.dialogs = dialogs;
Items.AddRange(workspaces);
CloseStrategy = new ApplicationCloseStrategy();
}
public IDialogManager Dialogs {
get { return dialogs; }
}
}
And here’s the implementation of that strategy:
public class ApplicationCloseStrategy : ICloseStrategy<IWorkspace> {
IEnumerator<IWorkspace> enumerator;
bool finalResult;
Action<bool, IEnumerable<IWorkspace>> callback;
public void Execute(IEnumerable<IWorkspace> toClose, Action<bool, IEnumerable<IWorkspace>> callback) {
enumerator = toClose.GetEnumerator();
this.callback = callback;
finalResult = true;
Evaluate(finalResult);
}
void Evaluate(bool result)
{
finalResult = finalResult && result;
if (!enumerator.MoveNext() || !result)
callback(finalResult, new List<IWorkspace>());
else
{
var current = enumerator.Current;
var conductor = current as IConductor;
if (conductor != null)
{
var tasks = conductor.GetChildren()
.OfType<IHaveShutdownTask>()
.Select(x => x.GetShutdownTask())
.Where(x => x != null);
var sequential = new SequentialResult(tasks.GetEnumerator());
sequential.Completed += (s, e) => {
if(!e.WasCancelled)
Evaluate(!e.WasCancelled);
};
sequential.Execute(new ActionExecutionContext());
}
else Evaluate(true);
}
}
}
The interesting thing I did here was to reuse the IResult functionality for async shutdown of the application. Here’s how the custom strategy uses it:
The CustomerViewModel and OrderViewModel use this mechanism to display a modal dialog if there is dirty data. But, you could also use this for any number of async tasks. For example, suppose you had some long running process that you wanted to prevent shutdown of the application. This would work quite nicely for that too.