07 janeiro 2010

Building a Shell Application using Caliburn

I'm planing to use Caliburn for a large project this year, and the first thing I need to do is to implement the infrastructure for the WPF applications. My favorite user interface model is, for years now, the Shell model, which allows one active screen at a time plus some secondary popup screens and eventual dialog windows. Caliburn is highly customizable and one of the pieces you can choose to replace is its DefaultWindowManager, which is the object responsible for displaying views in a dialog or popup screen. I will use this post to show my implementation of the Shell model using Caliburn, which I think can also be used as a Caliburn Getting Started. I will assume you have already read the Caliburn documentation and is familiar with the MVVM pattern and the Shell Application Model.

Step 1: Create the project: The first thing to do is to open VS2008 and create a WPFApplication. I'm assuming you have caliburn installed, so add references to the following assemblies:
  • Caliburn.Core;
  • Caliburn.PresentationFramework;
  • Microsoft.Practices.ServiceLocation.
These assemblies are available in the Bin folder of the Caliburn instalation.

Step 2: Create the infrastructure types: The second step is to create three types that will contain all the infrastructure code for using Caliburn as a Shell application. WindowManager Caliburn uses a DefaultWindowManager to show dialog and popup windows, but it does not allow us to customize these windows' properties. In order to do so, will extend the DefaultWindowManager and configure Caliburn to use our WindowManager instead of the default.
public class WindowManager : DefaultWindowManager, IWindowManager {
    public WindowManager(IViewStrategy viewStrategy, IBinder binder)
        : base(viewStrategy, binder) {
    }

    //Display a view in a dialog (modal) window
    public new bool? ShowDialog(object rootModel, 
                                object context, 
                                Action<ISubordinate, Action> handleShutdownModel) {
        var window = base.CreateWindow(rootModel, context, handleShutdownModel);
        window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
        window.WindowStyle = WindowStyle.ToolWindow;
        window.Title = ((IPresenter)rootModel).DisplayName;
        return window.ShowDialog();
     }

    //Display a view in a popup (non-modal) window
    public new void Show(object rootModel, 
                         object context, 
                         Action<ISubordinate, Action> handleShutdownModel) {
        var window = base.CreateWindow(rootModel, context, handleShutdownModel);
        window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
        window.Title = ((IPresenter)rootModel).DisplayName;
        window.Show();
    }
}
This window manager reintroduces the Show and ShowDialog methods, which we can use to customize the popup and dialog windows. BaseShellViewModel Caliburn allows us to inform a view model to be used as an application shell. Let's define this view model:
public abstract class BaseShellViewModel : MultiPresenterManager {
    protected IServiceLocator Locator { get; private set; }

    public BaseShellViewModel(IServiceLocator locator) {
        this.Locator = locator;
    }
    public void Show<T>() where T : IPresenter {
        this.ShutdownCurrent();
        this.Open(Locator.GetInstance<T>());
    }
    public void ShowDialog<T>() where T : IPresenter {
        Locator.GetInstance<IWindowManager>().ShowDialog(
            Locator.GetInstance<T>()
        );
    }
    public void Popup<T>() where T : IPresenter {
        Locator.GetInstance<IWindowManager>().Show(
            Locator.GetInstance<T>()
        );
    }
}
Look that this base shell view model publish methods to open views in the shell, in a popup window and as a dialog. BaseApplication One of the ways to use Caliburn is to make your WPF application extend the CaliburnApplication class. Here we'll create a base application class, which extends CaliburnApplication, and make our WPF applications extend this base application, keeping all Caliburn configuration within this base class.
public abstract class BaseApplication<TShellViewModel> : CaliburnApplication 
    where TShellViewModel : BaseShellViewModel {

    protected override object CreateRootModel() {
        var binder = Container.GetInstance<DefaultBinder>();
        binder.EnableBindingConventions();
        binder.EnableMessageConventions();
        return Container.GetInstance<TShellViewModel>();
    }

    protected override void ConfigurePresentationFramework(
        PresentationFrameworkModule module
    ) {
        module.UsingWindowManager<WindowManager>();
    }
}
As we need to inform Caliburn which is our shell view model, I introduced a generic parameter for that. Note also the call to UsingWindowManager, which informs Caliburn that it must use our WindowManager instead of the default one.

Step 3: Setup the application Now that we have the infrastructure code, let's configure our WPF application to use it. The ShellViewModel The first thing to do is to create our ShellViewModel. Create a folder named ViewModels in the project and a class named ShellViewModel inside it. Make this class extend our BaseShellViewModel.
public class ShellViewModel : BaseShellViewModel {
    public ShellViewModel(IServiceLocator locator)
        :base(locator) {
    }
}
The ShellView Now that we have a ShellViewModel, let's create our ShellView. Create a folder named Views in the project and a WPF Window called ShellView inside it. Change this view according to the code below. This will make our view act as a dock station for the application views.
<window minheight="768" minwidth="1024" title="Symbion" 
    windowstartuplocation="CenterScreen" windowstate="Maximized" 
    x:class="CaliburnDemo.Views.ShellView" 
    xmlns:cal="http://www.caliburnproject.org" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <dockpanel>
        <dockpanel background="Gray" name="DockViewPanel">
            <itemscontrol itemssource="{Binding Presenters}">
                <itemscontrol.itemtemplate>
                    <datatemplate>
                        <contentcontrol cal:view.model="{Binding}"></contentcontrol> 
                    </datatemplate>
                </itemscontrol.itemtemplate>
            </itemscontrol>
        </dockpanel>
    </dockpanel>
</window>
App.xaml and App.xaml.cs The last thing to do is to make our application to extend our BaseApplication.
public partial class BaseApplication : BaseApplication<ShellViewModel> { 
}

public partial class App : BaseApplication {
}
As WPF does not support generics, we need to workaround it by creating a concrete base class to our application, fixing our ShellViewModel as the generic parameter. Now change the app markut to reflect the above change.
<this:baseapplication x:class="CaliburnDemo.App" 
    xmlns:this="clr-namespace:CaliburnDemo"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <application.resources>

    </application.resources>
</this:baseapplication>
Everthing set up!

Step 4: Create a test view Now that we have everything set up, let's create a test view and run our application. Create a view model, in the ViewModels folder, named TestViewModel. Make it implement the IPresenter interface and be sure to remove the exceptions from the generated interface implementation methods. Give it a DisplayName too. Create a UserControl, in the Views folder, named TestView and paint it some color to mark it. Add a method to our ShellViewModel as follows:
public void ShowViews() {
    Show<TestViewModel>();
    Popup<TestViewModel>();
    ShowDialog<TestViewModel>();
}
Add a button to the ShellView as follows:
<button cal:message.attach="ShowViews" height="50" name="ShowViews" width="178">Show Views</button>
Run it and have fun!

Nenhum comentário:

Postar um comentário