Tipps und Tricks zum MVVM-Pattern (Teil 3)
Kommentare

Anzeigen von Dialogen

Ein häufiges Problem beim MVVM-Pattern ist das Anzeigen von Dialogen. An einer bestimmten Stelle im ViewModel soll dem Benutzer ein Dialog angezeigt werden. Doch da das ViewModel

Anzeigen von Dialogen

Ein häufiges Problem beim MVVM-Pattern ist das Anzeigen von Dialogen. An einer bestimmten Stelle im ViewModel soll dem Benutzer ein Dialog angezeigt werden. Doch da das ViewModel keine UI-Elemente enthalten darf/soll, kann dort nicht einfach der Dialog erzeugt und angezeigt werden. Dies geschieht insbesondere aus dem Grund, dass das ViewModel weiterhin „unit-testbar“ bleiben soll. Würde im ViewModel ein Dialog angezeigt werden, wäre es für Unit Tests unbrauchbar. Zur Lösung dieses Problems gibt es mehrere Möglichkeiten. Beispielsweise kann das Ganze über eine Mediatorklasse geregelt werden, die die Anzeige des Dialogs übernimmt. In den meisten Projekten hat sich allerdings eine weitaus einfachere Variante durchgesetzt. Diese besteht darin, dass ein ViewModel lediglich ein Event auslöst. Dieser Event wird von der View abonniert, die im Event Handler den eigentlichen Dialog anzeigt. Daten werden über die Eventargs übergeben. Der kleine unschöne Effekt, dass in der View ein Event Handler installiert werden muss, schränkt die Funktionalität des MVVM-Patterns bezüglich Testbarkeit und Trennung von UI- und UI-Logik in keiner Weise ein. Daher ist diese Vorgehensweise vollkommen legitim. Das hier verwendete Beispiel zeigt ebenfalls diese Vorgehensweise. So lässt sich aus dem MainViewModel der OpenListDialog anzeigen, der zum Öffnen einer Personenliste verwendet wird. Dazu werden zunächst die OpenListEventArgs definiert, die die ID der zu öffnenden Liste speichern:

public class OpenListEventArgs:EventArgs
{
  public int? ListIDToOpen { get; set; }
}  

Im MainViewModel wird das OpenList-Event definiert, das die OpenListEventArgs verwendet (Listing 4). In der Execute-Methode des OpenListCommand wird das OpenList-Event ausgelöst. Anschließend wird geprüft, ob die ListIDToOpen Property der Eventargs gesetzt wurde. Wenn ja, wird die LoadPersons-Methode mit der entsprechenden ID aufgerufen.

public class MainViewModel : ViewModelBase
{
  ...
  public event EventHandler OpenList;
  public MainViewModel()
  {
    ...
    OpenListCommand = new ActionCommand(OnOpenListExecuted);
    ...
  }
  ...
  public ICommand OpenListCommand { get; private set; }
  private void OnOpenListExecuted(object parameter)
  {
    var openListEventArgs = new OpenListEventArgs();
    if (OpenList != null)
    {
      OpenList(this, openListEventArgs);
      if (openListEventArgs.ListIDToOpen.HasValue)
      {
        LoadPersons(openListEventArgs.ListIDToOpen.Value);
      }
    }
  }
  ...
}  

In der View muss lediglich ein Event Handler für das OpenList-Event erstellt werden, der den eigentlichen Dialog anzeigt und die ListIDToOpen Property der Eventargs auf die ID der ausgewählten Personenliste setzt.

public partial class MainWindow : Window
{
  private MainViewModel _viewModel;
  public MainWindow()
  {
    InitializeComponent();

    _viewModel = DataContext as MainViewModel;
    _viewModel.OpenList += OnOpenList;
  }
  void OnOpenList(object sender, OpenListEventArgs e)
  {
    var dlg = new OpenListDialog { Owner = this, ...};
    if (dlg.ShowDialog() == true)
    {
      e.ListIDToOpen = dlg.ViewModel.CurrentList.PersonListID;
    }
  }  

Der Vorteil der Variante mit dem Event ist, dass sich das ViewModel jetzt auch einfach in einem Unit Test verwenden lässt (Listing 6). Dazu installiert der Unit Test wie auch die View einen Event Handler für das OpenList-Event. Statt darin einen Dialog anzuzeigen, wird die ListIDToOpen Property der Eventargs direkt auf eine ID gesetzt. In Listing 6 auf die ID 2, die eine Testliste mit genau 8 Personen enthält.

[TestMethod]
public void OpenListTest()
{
  MainViewModel vm = new MainViewModel();
  vm.OpenList += vm_OpenList;
  vm.OpenListCommand.Execute(null);
  Assert.AreEqual(vm.Persons.Count, 8);
}

void vm_OpenList(object sender, OpenListEventArgs e)
{
  e.ListIDToOpen = 2;
}  
Unterstützen von Designzeitdaten

Beim Entwickeln einer WPF-Anwendung im größeren Stil sind oft unterschiedliche Personen in Form von Entwicklern und Designern involviert. Für den Designer ist es von großem Vorteil, wenn bereits zur Designzeit Daten angezeigt werden. Passt er beispielsweise das DataTemplate der Elemente in einer ListBox an, ist dies deutlich einfacher, wenn die ListBox zur Designzeit bereits die Objekttypen enthält, die auch zur Laufzeit in der ListBox sein werden (im hier verwendeten Beispiel Person-Objekte). Um Designzeitdaten zur Verfügung zu stellen, wird mit so genannten Data-Providern gearbeitet. Das ViewModel lädt die Daten des Models über einen Data-Provider (Abb. 5). Das interessante an dieser Geschichte ist, dass der Data-Provider zur Laufzeit ein anderer sein kann als zur Designzeit. Auf diese Weise lassen sich einfach Designzeitdaten bereitstellen.

Abb. 5: Das Model-View-ViewModel-Pattern
Abb. 5: Das Model-View-ViewModel-Pattern
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -