WPF Wizard mit Caliburn.Micro

5. Dezember 2011

In vielen Applikation sind Wizards anzutreffen und auch im Rahmen von Desktop-Applikationen für Kunden kommt es immer wieder vor, dass die Bearbeitung durch den Anwender Schritt für Schritt durchgeführt werden soll. Ein Wizard zu implementieren, stellt den Entwickler immer wieder vor die gleichen Herausforderungen. In diesem Beitrag wird gezeigt, wie ein Wizard auf Basis der WPF Technologie und mit Hilfe des Caliburn.Mirco Frameworks unter Berücksichtigung des MVVM Patterns entwickelt werden kann.

Aus MVVM Gesichtspunkten heraus wäre es für einen Wizard wünschenswert, dass lediglich ein ViewModel mit mehreren Views verwendet wird. So werden die Daten in einem ViewModel gesammelt, aber nur die Teilabschnitte angezeigt, die zum jeweiligen Verarbeitungschritt gehören. Das Caliburn.Micro Framework unterstützt diese Möglichkeit, in dem es über den eigenen WindowManager für die Anzeige eines ViewModels den Context-Parameter auswertet und dann per Konvention nach einer passenden View sucht.

   1: Sdx.Sample.ViewModels.DialogViewModel vm = 

   2:   new Sdx.Sample.ViewModels.DialogViewModel();

   3: cmWndMgr.ShowDialog(vm);

   4: ...

   5: cmWndMgr.ShowDialog(vm,"Page1");

In der Zeile 3 wird durch das Caliburn.Micro Framework per Konvention die Viewklasse “Sdx.Sample.Views.DialogView” dem ViewModel zugeordnet. In der Zeile 5 hingegen wird die Viewklasse “Sdx.Sample.Views.Dialog.Page1” ermittelt. Nun möchte man nicht für jede Wizard-Seite einen eigenen Dialog anzeigen, sondern Ziel sollte es sein, den Inhalt in place auszutauschen. Auch hierfür bietet das Framework eine Möglichkeit auf Basis des WPF ContentControls und den verfügbaren attached Properties von Caliburn.Micro (siehe auch Beispiel hier).

   1: <ContentControl cal:View.Context="{Binding WizardContent.State, Mode=TwoWay}"  

   2:                 cal:View.Model="{Binding WizardContent}" />

Durch diese Definition wird in dem ContentControl die View entsprechend der Eigenschaft “State” am ViewModel “WizardContent” ausgewählt und angezeigt.

Aufbau eines Grundgerüsts

Ausgerüstet mit diesem Wissen kann nun eine Basisimplementierung für einen Wizard angegangen werden. Das Grundgerüst des Wizards soll eine Kopfzeile mit Information zum aktuellen Schritt, den Bereich für die View und in der Fußzeile vier Buttons (einer für die vorangegangene Seite, einer für die nächste Seite, einer für das Beenden und einer für das Abbrechen) aufweisen.

   1: <Window x:Class="Sdx.Sample.CaliburnMircoWizard.Views.WizardShellView"

   2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   4:         xmlns:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro"

   5:         Title="MainWindow" Height="350" Width="525">

   6:   <DockPanel LastChildFill="True">

   7:     <StackPanel DockPanel.Dock="Bottom" Margin="5" 

   8:                 Orientation="Horizontal" HorizontalAlignment="Right">

   9:       <Button Margin="5" Name="PreviousPage" Content="&lt;" Width="50" />

  10:       <Button Margin="5" Name="NextPage" Content="&gt;" Width="50" />

  11:       <Button Margin="5" Name="FinishWizard" Content="Finish" Width="50" />

  12:       <Button Margin="5" Name="CancelWizard" Content="Cancel" Width="50" />

  13:     </StackPanel>

  14:     <StackPanel Margin="5" DockPanel.Dock="Top" Orientation="Horizontal">

  15:       <Image Source="/Sdx.Sample.CaliburnMircoWizard;component/SDX_Logo.jpg"

  16:              Width="80" Height="30" />

  17:       <TextBlock Margin="10,0,10,0" Height="30" Name="StateDescription" 

  18:                  FontSize="14" FontWeight="Bold"/>

  19:     </StackPanel>

  20:     <ContentControl Margin="10" DockPanel.Dock="Top"

  21:                     cal:View.Context="{Binding WizardContent.State, Mode=TwoWay}"  

  22:                     cal:View.Model="{Binding WizardContent}"

  23:                     />

  24:   </DockPanel>

  25: </Window>

Das ViewModel zum WizardShellView ist abgeleitet von der Caliburn.Micro Klasse Screen und die Kommunikation mit dem ViewModel, welches im Wizard angezeigt werden soll, erfolgt über ein Interface.

   1: public interface IWizardScreen

   2: {

   3:   string State { get; set; }

   4:   string StateDescription { get; }

   5:   void NextPage();

   6:   bool CanNextPage { get; }

   7:   void PreviousPage();

   8:   bool CanPreviousPage { get; }

   9:   void CancelWizard();

  10:   bool CanCancelWizard { get; }

  11:   void FinishWizard();

  12:   bool CanFinishWizard { get; }

  13: }

Die “State” Eigenschaft ermöglicht dem ViewModel die Anzeige auszutauschen und über die Methoden passend zu den Buttons die notwendige Steuerung. Über die Guard-Properties “CanXXX” wird das Enablen/Disablen der Buttons ermöglicht. Innerhalb des konkreten Wizard-ViewModels, dem Wizard-Content, kann dann die Steuerung im einfachsten Fall bei einem 2-seitigen Wizard so aussehen.

   1: ...

   2: private string _State;

   3: public string State

   4: {

   5:   get { return _State; }

   6:   set { _State = value; NotifyOfPropertyChange(() => State); }

   7: }

   8: public void NextPage()

   9: {

  10:   if (State == "Page1")

  11:   {

  12:     // Businesslogik für nächste Wizardseite

  13:     State = "Page2";

  14:   }

  15: }

  16: public bool CanNextPage

  17: {

  18:   get { return State == "Page1" ? true : false; }

  19: }

  20: ...

Zu den verschiedenen States sind nun unterschiedliche Views bereitzustellen und der Wizard insgesamt könnte exemplarisch so aussehen:

WPF_CM_Wizard

Dieses Beispiel zeigt die Grundzüge einer Wizard Steuerung. In der Praxis werden noch mehr Anforderungen zu erfüllen sein. Vor allem die Validierung der Eingaben vor Ausführung des nächsten Schrittes ist hier noch nicht diskutiert. Mit der Steuerung bzw. dem zu Grunde gelegten Interface sind jedoch die Grundsteine gelegt. Eine Teilvalidierung des ViewModels ist in den Guard-Properties zu verankern. Ein nichtlinearer Workflow kann in den Methoden anhand der bisher vom Anwender eingegebenen Werte durch setzen des entsprechenden States umgesetzt werden.

Fazit

Einen Wizard mit Hilfe des MVVM Patterns ist dank der Unterstützung vom Caliburn.Mirco Framework für eine WPF Anwendung mit einfachen Mitteln und schnell umsetzbar. Die Konventionen des Caliburn.Micro Frameworks ermöglichen die Verwendung eines ViewModels mit mehreren Views. Für den Wizard-State genügt ein einfaches Interface zwischen der Shell des Wizards und dem anzuzeigenden ViewModel. Die Views können so nur den für den State notwendigen Inhalt anzeigen.

In diesem kurzen Beispiel ist die Validierung ausgeklammert worden. Wie dies umgesetzt werden könnte, wird in einem späteren Beitrag gezeigt. Der Wechsel zwischen den States des ViewModels kann eventuell auch noch besser unterstützt werden, so dass große switch-Statements vermieden werden können.