Silverlight-Muster

Model-View-ViewModel in Silverlight 2-Anwendungen

Shawn Wildermuth

Codedownload verfügbar in der MSDN-Codegalerie
Code online durchsuchen

Themen in diesem Artikel:
  • Silverlight 2-Entwicklung
  • Model-View-ViewModel-Muster
  • Ansichten und das Ansichtsmuster
  • Konzessionen an Silverlight 2
In diesem Artikel werden folgende Technologien verwendet:
Silverlight 2, Visual Studio

Inhalt

Das Problem
Anwendungsschichten in Silverlight 2
MVVM: Eine exemplarische Vorgehensweise
Erstellen des Modells
Ansichten und das Ansichtsmodell
Konzessionen an Silverlight 2
Der Stand der Dinge

Nach der Veröffentlichung von Silverlight 2 nimmt die Anzahl der darauf erstellten Anwendungen zu, und es machen sich einige Kinderkrankheiten bemerkbar. Die von der Silverlight 2-Vorlage unterstützte grundlegende Struktur beinhaltet eine enge Integration zwischen der Benutzeroberfläche und den Daten, mit denen Sie arbeiten. Diese enge Integration ist zwar zum Erlernen der Technologie nützlich, wird aber beim Testen, Umgestalten und Warten eher als hinderlich empfunden. Hier erfahren Sie, wie Sie die Benutzeroberfläche mithilfe ausgereifter Muster für den Anwendungsentwurf von den Daten trennen können.

Das Problem

Kern des Problems ist die enge Kopplung, die sich aus dem Mischen der Schichten Ihrer Anwendung ergibt. Wenn eine Schicht genau weiß, wie eine andere ihre Arbeit ausführt, ist die Anwendung eng gekoppelt. Gehen Sie von einer einfachen Dateneingabeanwendung aus, mit der zum Verkauf stehende Häuser in einer bestimmten Stadt abgefragt werden können. In einer eng gekoppelten Anwendung könnten Sie die Abfrage zum Durchführen der Suche in einem Schaltflächenhandler in der Benutzeroberfläche definieren. Während sich das Schema oder die Semantik der Suche ändert, muss sowohl die Datenschicht als auch die Benutzeroberflächenschicht aktualisiert werden.

Dies führt zu einem Problem bei der Codequalität und Komplexität. Jedes Mal, wenn sich die Datenschicht ändert, müssen Sie die Anwendungen testen und synchronisieren, um sicherzugehen, dass es sich bei den Änderungen nicht um bedeutende Änderungen handelt. Wenn alles eng aneinander gebunden ist, kann jede Bewegung in einem Teil der Anwendung fortgesetzte Änderungen im übrigen Code verursachen. Beim Erstellen einfacher Anwendungen mit Silverlight 2, beispielsweise eines Filmplayers oder eines Menüwidgets, dürfte die enge Kopplung der Anwendungskomponenten kein Problem sein. Mit zunehmender Größe des Projekts wird es jedoch komplizierter.

Die andere Seite des Problems sind die Komponententests. Wenn eine Anwendung eng gekoppelt ist, können Sie nur funktionale (oder Benutzeroberflächen-) Tests der Anwendung durchführen. Bei einem kleinen Projekt ist dies ebenfalls kein Problem, doch wenn ein Projekt an Größe und Komplexität zunimmt, wird es sehr wichtig, Anwendungsschichten separat testen zu können. Bedenken Sie, dass Komponententests nicht nur sicherstellen sollen, dass eine Komponente arbeitet, wenn Sie in einem System verwendet wird, sondern auch, dass sie die Arbeit im System anhaltend ausführt. Komponententests für Teile eines Systems sorgen dafür, dass Probleme bei Systemänderungen bereits frühzeitig im Prozess deutlich werden und nicht erst später (was bei Funktionstests der Fall wäre). Dann müssen Regressionstests durchgeführt werden (beispielsweise Komponententests an einem System bei jedem Build), um sicherzustellen, dass kleine Änderungen an einem System keine Fehlerkette verursachen.

Einige Entwickler könnten es als übertrieben ansehen, beim Erstellen einer Anwendung verschiedene Schichten zu definieren. Es ist jedoch eine Tatsache, dass Sie unabhängig davon, ob Sie beim Erstellen Schichten im Sinn haben oder nicht, an einer n-Stufen-Plattform arbeiten, die fertige Anwendung also Schichten aufweist. Doch ohne formelle Planung erhalten Sie entweder ein eng gekoppeltes System (und damit die bereits erwähnten Probleme) oder eine Anwendung mit einem Durcheinander an Code, die bei der Wartung Kopfschmerzen bereitet.

Es könnte leicht angenommen werden, dass für das Erstellen einer Anwendung mit separaten Schichten oder Stufen viel Infrastruktur erforderlich ist, damit sie gut funktioniert, doch tatsächlich ist es nicht schwer, eine einfache Trennung von Schichten zu implementieren. (Sie können komplexere Schichten einer Anwendung entwickeln, indem Sie die Umkehrung von Steuerungsverfahren verwenden, doch dabei wird ein anderes Problem als das in diesem Artikel erörterte behandelt.)

Anwendungsschichten in Silverlight 2

Mit Silverlight 2 müssen Sie nicht etwas Neues erfinden, um entscheiden zu können, wie die Schichten einer Anwendung angeordnet werden sollen. Es gibt einige bekannte Muster, die Sie für Ihren Entwurf verwenden können.

Ein Muster, das derzeit sehr im Gespräch ist, ist das Model-View-Controller (MVC)-Muster. Im MVC-Muster handelt es sich beim Modell um die Daten, die Ansicht ist die Benutzeroberfläche, und der Controller ist die programmgesteuerte Schnittstelle zwischen Ansicht, Modell und Benutzereingabe. Dieses Muster funktioniert jedoch nicht gut in deklarativen Benutzeroberflächen wie Windows Presentation Foundation (WPF) oder Silverlight, da das XAML, das von diesen Technologien verwendet wird, einen Teil der Schnittstelle zwischen der Eingabe und der Ansicht definieren kann (da Datenbindung, Trigger und Zustände in XAML deklariert werden können).

Model-View-Presenter (MVP) ist ein anderes häufiges Muster für das Erstellen von Anwendungsschichten. Im MVP-Muster ist der Presenter für das Festlegen und Verwalten des Zustands einer Ansicht verantwortlich. Genau wie MVC passt MVP nicht hundertprozentig in das Silverlight 2-Modell, da das XAML deklarative Datenbindung, Trigger und Statusverwaltung enthalten könnte. Was bedeutet das nun für uns?

Bei Silverlight 2 hat sich die WPF-Community glücklicherweise hinter ein Muster gestellt, das Model-View-ViewModel (MVVM) heißt. Dieses Muster ist eine Anpassung des MVC- und MVP-Musters, in dem das Ansichtsmodell ein Datenmodell und Verhalten für die Ansicht bereitstellt, es aber der Ansicht ermöglicht, sich deklarativ an das Ansichtsmodell zu binden. Die Ansicht wird eine Mischung aus XAML und C# (als Silverlight 2-Steuerelemente), das Modell stellt die Daten dar, die für die Anwendung verfügbar sind, und das Ansichtsmodell bereitet das Modell vor, um es an die Ansicht zu binden.

Das Modell ist besonders wichtig, da es den Zugriff auf die Daten umschließt, und zwar unabhängig davon, ob der Zugriff durch einen Satz von Webdiensten, einen ADO.NET Data Service oder eine andere Form des Datenabrufs erfolgt. Das Modell ist vom Ansichtsmodell getrennt, sodass die Daten der Ansicht (das Ansichtsmodell) isoliert von den eigentlichen Daten getestet werden können. In Abbildung 1 ist ein Beispiel für das MVVM-Muster dargestellt.

fig01.gif

Abbildung 1 Model-View-ViewModel-Muster

MVVM: Eine exemplarische Vorgehensweise

Um besser zu verstehen, wie das MVVM-Muster implementiert wird, sehen Sie sich ein Beispiel an. Dieses Beispiel stellt nicht unbedingt dar, wie eigentlicher Code verwendet würde. Es soll einfach nur das Muster erklären.

Das Beispiel setzt sich aus fünf separaten Projekten in einer einzelnen Visual Studio-Lösung zusammen. (Obwohl Sie nicht jede Schicht als separates Projekt erstellen müssen, ist dies oft angebracht.) Das Beispiel trennt die Projekte weiter, indem sie in Client- und Serverordnern abgelegt werden. Im Serverordner sind zwei Projekte vorhanden: eine ASP.NET-Webanwendung (MVVMExample), die Ihre Silverlight-Projekte und -Dienste hosten soll, und ein .NET-Bibliotheksprojekt, das das Datenmodell enthält.

Im Clientordner sind drei Projekte enthalten: ein Silverlight-Projekt (MVVM.Client) für die Hauptbenutzeroberfläche der Anwendung, eine Silverlight-Clientbibliothek (MVVM.Client.Data), die das Modell und das Ansichtsmodell sowie Dienstverweise enthält, und ein Silverlight-Projekt (MVVM.Client.Tests), das die Komponententests enthält. Sie sehen die Aufschlüsselung dieser Projekte in Abbildung 2.

fig02.gif

Abbildung 2 Projektlayout

Für dieses Beispiel wurde ASP.NET, Entity Framework und ein ADO.NET Data Service auf dem Server verwendet. Im Grunde ist ein einfaches Datenmodell auf dem Server vorhanden, das durch einen REST-basierten Dienst verfügbar gemacht wird. Eine weitere Erklärung dieser Details ist in meinem Artikel über die Verwendung von ADO.NET Data Services in Silverlight 2 vom September 2008 enthalten (Erstellen datenzentrischer Webanwendungen mit Silverlight 2).

Erstellen des Modells

Um Schichten in der Silverlight-Anwendung zu ermöglichen, muss zunächst das Modell für die Anwendungsdaten im MVVM.Client.Data-Projekt definiert werden. Beim Definieren des Modells geht es zum Teil darum, die Entitätstypen festzulegen, mit denen Sie innerhalb einer Anwendung arbeiten. Die Entitätstypen hängen davon ab, wie die Anwendung mit den Serverdaten interagiert. Wenn Sie beispielsweise Webdienste verwenden, werden Ihre Entitäten wahrscheinlich Datenvertragsklassen sein, die durch Erstellen eines Dienstverweises auf Ihren Webdienst generiert werden. Alternativ könnten Sie einfache XML-Elemente verwenden, wenn Sie rohes XML bei Ihrem Datenzugriff abrufen. Hier wird ein ADO.NET Data Service verwendet. Wenn also ein Dienstverweis auf den Datendienst erstellt wird, wird ein Satz von Entitäten für Sie erstellt.

In diesem Beispiel hat der Dienstverweis drei Klassen erstellt, die für Sie wichtig sind: Game, Supplier und GameEntities (das Kontextobjekt zum Zugriff auf den Datendienst). Die Game- und Supplier-Klasse sind die eigentlichen Entitäten, die zum Interagieren mit der Ansicht verwendet werden, und die GameEntities-Klasse dient zum internen Zugriff auf den Datendienst zum Abrufen von Daten.

Bevor Sie das Modell aber erstellen können, müssen Sie eine Schnittstelle für die Kommunikation zwischen dem Modell und dem Ansichtsmodell erstellen. Diese Schnittstelle enthält in der Regel Methoden, Eigenschaften und Ereignisse, die zum Zugriff auf Daten erforderlich sind. Diese Funktionen werden von einer Schnittstelle dargestellt, damit sie bei Bedarf durch andere Implementierungen ersetzt werden können (beispielsweise durch Tests). Die Modellschnittstelle im hier dargestellten Beispiel heißt „IGameCatalog“.

public interface IGameCatalog
{
  void GetGames();
  void GetGamesByGenre(string genre);
  void SaveChanges();

  event EventHandler<GameLoadingEventArgs> GameLoadingComplete;
  event EventHandler<GameCatalogErrorEventArgs> GameLoadingError;
  event EventHandler GameSavingComplete;
  event EventHandler<GameCatalogErrorEventArgs> GameSavingError;
}

Die IGameCatalog-Schnittstelle enthält Methoden zum Abrufen und Speichern von Daten. Doch die Vorgänge geben keine eigentlichen Daten zurück. Stattdessen haben sie entsprechende Ereignisse für Erfolg und Fehlschlagen. Dieses Verhalten ermöglicht eine asynchrone Ausführung, um der Silverlight 2-Anforderung für asynchrone Netzwerkaktivität nachzukommen Ein asynchroner Entwurf wird in WPF zwar oft empfohlen, aber dieser spezielle Entwurf funktioniert gut in Silverlight 2, da für Silverlight 2 ein asynchroner Entwurf erforderlich ist.

Um dem Aufrufer der Schnittstelle eine Benachrichtigung über die Ergebnisse zu ermöglichen, wird im Beispiel eine GameLoadingEventArgs-Klasse implementiert, die in den Ereignissen verwendet wird, um die Ergebnisse einer Anforderung zu senden. Diese Klasse macht den Entitätstyp (Game) als eine aufzählbare Liste von Entitäten verfügbar, die die vom Aufrufer angeforderten Ergebnisse enthält, wie im folgenden Code ersichtlich ist.

public class GameLoadingEventArgs : EventArgs
{
  public IEnumerable<Game> Results { get; private set; }

  public GameLoadingEventArgs(IEnumerable<Game> results)
  {
    Results = results;
  }
}

Nachdem Sie nun die Schnittstelle definiert haben, können Sie die Modellklasse (GameCatalog) erstellen, die die IGameCatalog-Schnittstelle implementiert. Die GameCatalog-Klasse umschließt einfach den ADO.NET Data Service, sodass beim Eingang einer Anforderung von Daten (GetGames oder GetGamesByGenre) die Anforderung ausgeführt und ein Ereignis, das die Daten enthält, (oder gegebenenfalls ein Fehler) ausgelöst wird. Dieser Code soll den Zugriff auf die Daten vereinfachen, ohne dem Aufrufer spezifische Kenntnisse zu vermitteln. Die Klasse enthält einen überladenen Konstruktor zur Angabe des URI des Diensts, doch dies ist nicht immer erforderlich und könnte stattdessen als Konfigurationselement implementiert werden. Abbildung 3 zeigt den Code für die GameCatalog-Klasse.

Abbildung 3 Die GameCatalog-Klasse

public class GameCatalog : IGameCatalog
{
  Uri theServiceRoot;
  GamesEntities theEntities;
  const int MAX_RESULTS = 50;

  public GameCatalog() : this(new Uri("/Games.svc", UriKind.Relative))
  {
  }

  public GameCatalog(Uri serviceRoot)
  {
    theServiceRoot = serviceRoot;
  }

  public event EventHandler<GameLoadingEventArgs> GameLoadingComplete;
  public event EventHandler<GameCatalogErrorEventArgs> GameLoadingError;
  public event EventHandler GameSavingComplete;
  public event EventHandler<GameCatalogErrorEventArgs> GameSavingError;

  public void GetGames()
  {
    // Get all the games ordered by release date
    var qry = (from g in Entities.Games
               orderby g.ReleaseDate descending
               select g).Take(MAX_RESULTS) as DataServiceQuery<Game>;

    ExecuteGameQuery(qry);
  }

  public void GetGamesByGenre(string genre)
  {
    // Get all the games ordered by release date
    var qry = (from g in Entities.Games
               where g.Genre.ToLower() == genre.ToLower()
               orderby g.ReleaseDate
               select g).Take(MAX_RESULTS) as DataServiceQuery<Game>;

    ExecuteGameQuery(qry);
  }

  public void SaveChanges()
  {
    // Save Not Yet Implemented
    throw new NotImplementedException();
  }

  // Call the query asynchronously and add the results to the collection
  void ExecuteGameQuery(DataServiceQuery<Game> qry)
  {
    // Execute the query
    qry.BeginExecute(new AsyncCallback(a =>
    {
      try
      {
        IEnumerable<Game> results = qry.EndExecute(a);

        if (GameLoadingComplete != null)
        {
          GameLoadingComplete(this, new GameLoadingEventArgs(results));
        }
      }
      catch (Exception ex)
      {
        if (GameLoadingError != null)
        {
          GameLoadingError(this, new GameCatalogErrorEventArgs(ex));
        }
      }

    }), null);
  }

  GamesEntities Entities
  {
    get
    {
      if (theEntities == null)
      {
        theEntities = new GamesEntities(theServiceRoot);
      }
      return theEntities;
    }
  }
}

Beachten Sie die ExecuteGameQuery-Methode, die die ADO.NET Data Service-Abfrage übernimmt und ausführt. Diese Methode führt das Ergebnis asynchron aus und gibt das Ergebnis an den Aufrufer zurück.

Beachten Sie, dass das Modell die Abfrage ausführt, aber einfach Ereignisse auslöst, wenn sie abgeschlossen ist. Vielleicht fragen Sie sich, warum das Modell nicht sicherstellt, dass die Ereignisse die Aufrufe des Benutzeroberflächenthreads in Silverlight 2 marshallen. Der Grund besteht darin, dass Silverlight (wie ähnliche Benutzeroberflächen, z. B. Windows Forms und WPF) die Benutzeroberfläche nur von einem Haupt- oder UI-Thread aus aktualisieren kann. Doch wenn das Marshalling in diesem Code durchgeführt wird, würde das Modell an die Benutzeroberfläche gebunden, was der erklärten Absicht (dem Trennen der Probleme) zuwiderläuft. Wenn Sie annehmen, dass die Daten auf dem UI-Thread zurückgegeben werden müssen, binden Sie diese Klasse an Benutzeroberflächenaufrufe, doch dies steht im Gegensatz dazu, warum separate Schichten in einer Anwendung verwendet werden.

Ansichten und das Ansichtsmodell

Es mag offensichtlich scheinen, dass das Ansichtsmodell erstellt wird, um Daten direkt für die Ansichtsklasse(n) verfügbar zu machen. Das Problem bei diesem Ansatz besteht darin, dass das Ansichtsmodell nur Daten verfügbar machen sollte, die direkt von der Ansicht benötigt werden. Daher müssen Sie verstehen, was für die Ansicht erforderlich ist. In vielen Fällen werden Sie das Ansichtsmodell und die Ansicht parallel erstellen und das Ansichtsmodell umgestalten, wenn für die Ansicht neue Anforderungen auftreten. Obwohl das Ansichtsmodell der Ansicht Daten verfügbar macht, interagiert die Ansicht auch mit den Entitätsklassen (indirekt, da die Entitäten des Modells vom Ansichtsmodell an die Ansicht übergeben werden).

Dieses Beispiel zeigt einen einfachen Entwurf, der zum Durchsuchen der XBox 360-Spieledaten dient, wie in Abbildung 4 dargestellt. Dieser Entwurf impliziert, dass eine Liste von Game-Entitäten aus dem Modell erforderlich ist, die nach Genre gefiltert sind (ausgewählt über die Dropdownliste). Um diese Anforderung zu erfüllen, ist für das Beispiel ein Ansichtsmodell erforderlich, das Folgendes verfügbar macht:

  • Eine Game-Liste für das derzeit ausgewählte Genre, die an Daten gebunden werden kann.
  • Eine Methode, um das ausgewählte Genre anzufordern.
  • Ein Ereignis, das die Benutzeroberfläche benachrichtigt, dass die Liste der Spiele aktualisiert wurde (da die Datenanforderungen asynchron sein werden).

fig04.gif

Abbildung 4 Beispiel einer Benutzeroberfläche

Wenn das Ansichtsmodell diesen Satz an Anforderungen unterstützt, kann es direkt an das XAML gebunden werden, wie in GameView.XAML (im MVVM.Client-Projekt befindlich) dargestellt. Diese Bindung wird durch Erstellen einer neuen Instanz des Ansichtsmodells in den Ressourcen der Ansicht und dann durch Binden des Hauptcontainers (in diesem Fall ist es ein Raster) an das Ansichtsmodell implementiert. Dies impliziert, dass die gesamte XAML-Datei aufgrund des Ansichtsmodells direkt datengebunden sein wird. Abbildung 5 zeigt den GameView.XAML-Code

Abbildung 5 GameView.XAML

// GameView.XAML
<UserControl x:Class="MVVM.Client.Views.GameView"
             xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:data="clr-namespace:MVVM.Client.Data;assembly=MVVM.Client.Data">

  <UserControl.Resources>
    <data:GamesViewModel x:Key="TheViewModel" />
  </UserControl.Resources>

  <Grid x:Name="LayoutRoot"
        DataContext="{Binding Path=Games, Source={StaticResource TheViewModel}}">
    ...
  </Grid>
</UserControl>

Das Ansichtsmodell muss diese Anforderungen mithilfe einer IGameCatalog-Schnittstelle erfüllen. Im Allgemeinen ist es nützlich, wenn der Standardkonstruktor eines Ansichtsmodells ein Standardmodell erstellt, damit die Bindung an das XAML einfach ist, doch Sie sollten auch eine Überladung des Konstruktors aufnehmen, in der das Modell bereitgestellt wird, um Szenarios wie beispielsweise Tests zu ermöglichen. Das Beispiel des Ansichtsmodells (GameViewModel) sieht wie in Abbildung 6 dargestellt aus.

Abbildung 6 GameViewModel-Klasse

public class GamesViewModel
{
  IGameCatalog theCatalog;
  ObservableCollection<Game> theGames = new ObservableCollection<Game>();

  public event EventHandler LoadComplete;
  public event EventHandler ErrorLoading;

 public GamesViewModel() : 
    this(new GameCatalog())
  {
  }

  public GamesViewModel(IGameCatalog catalog)
  {
    theCatalog = catalog;
    theCatalog.GameLoadingComplete += 
      new EventHandler<GameLoadingEventArgs>(games_GameLoadingComplete);
    theCatalog.GameLoadingError += 
      new EventHandler<GameCatalogErrorEventArgs>(games_GameLoadingError);
  }

  void games_GameLoadingError(object sender, GameCatalogErrorEventArgs e)
  {
    // Fire Event on UI Thread
    Application.Current.RootVisual.Dispatcher.BeginInvoke(() =>
      {
        if (ErrorLoading != null) ErrorLoading(this, null);
      });
  }

  void games_GameLoadingComplete(object sender, GameLoadingEventArgs e)
  {
    // Fire Event on UI Thread
    Application.Current.RootVisual.Dispatcher.BeginInvoke(() =>
      {
        // Clear the list
        theGames.Clear();

        // Add the new games
        foreach (Game g in e.Results) theGames.Add(g);

        if (LoadComplete != null) LoadComplete(this, null);
      });
  }

  public void LoadGames()
  {
    theCatalog.GetGames();
  }

  public void LoadGamesByGenre(string genre)
  {
    theCatalog.GetGamesByGenre(genre);
  }

  public ObservableCollection<Game> Games
  {
    get
    {
      return theGames;
    }
  }
}

Von besonderem Interesse beim Ansichtsmodell sind die Handler für GameLoadingComplete (und GameLoadingError). Diese Handler erhalten Ereignisse aus dem Modell und lösen dann Ereignisse in der Ansicht aus. Interessant ist hier, dass das Modell dem Ansichtsmodell die Liste der Ergebnisse übergibt, doch statt die Ergebnisse direkt an die zugrunde liegende Ansicht zu übergeben, speichert das Ansichtsmodell die Ergebnisse in seiner eigenen bindbaren Liste (ObservableCollection<Game>).

Zu diesem Verhalten kommt es, weil das Ansichtsmodell direkt an die Ansicht gebunden wird, sodass die Ergebnisse in der Ansicht über die Datenbindung angezeigt werden. Da das Ansichtsmodell Kenntnis der Benutzeroberfläche hat (weil sein Zweck darin besteht, Anforderungen der Benutzeroberfläche zu erfüllen), kann es dann sicherstellen, dass die Ereignisse, die von ihm ausgelöst werden, auf dem UI-Thread ausgelöst werden (über Dispatcher.BeginInvoke, obwohl Sie andere Methoden zum Aufrufen des UI-Threads verwenden können, wenn Sie dies bevorzugen).

Konzessionen an Silverlight 2

Das MVVM-Muster wird in vielen WPF-Projekten mit großem Erfolg eingesetzt. Das Problem bei der Verwendung in Silverlight 2 besteht darin, dass Silverlight 2 Befehle und Trigger unterstützen muss, damit dieses Muster einfach und nahtlos ist. Wenn dies der Fall wäre, könnte das XAML die Methoden des Ansichtsmodells direkt aufrufen, wenn der Benutzer mit der Anwendung interagiert.

In Silverlight 2 erfordert dieses Verhalten etwas mehr Arbeit, doch glücklicherweise muss dazu nur wenig Code geschrieben werden. Wenn der Benutzer beispielsweise ein anderes Genre mithilfe der Dropdownliste auswählt, ist ein Befehl wünschenswert, der die GameViewModel.GetGameByGenre-Methode für Sie ausführt. Da die erforderliche Infrastruktur nicht verfügbar ist, müssen Sie einfach Code zu diesem Zweck verwenden. Wenn sich die Auswahl des Kombinationsfelds (genreComboBox) ändert, lädt das Beispiel die Spiele aus dem Ansichtsmodell manuell in Code statt in einem Befehl. Erforderlich ist hierbei lediglich, dass die Anforderung zum Laden der Daten tatsächlich erfolgt. Da Sie an die Liste der Spiele gebunden sind, ändert das zugrunde liegende Ansichtsmodell einfach die Sammlung, an die Sie gebunden sind, und die aktualisierten Daten werden automatisch angezeigt. Dieser Code ist in Abbildung 7 dargestellt.

Abbildung 7 Aktualisieren von Daten auf der Benutzeroberfläche

void genreComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  string item = genreComboBox.SelectedItem as string;
  if (item != null)
  {
    LoadGames(item);
  }
}

void LoadGames(string genre)
{
  loadingBar.Visibility = Visibility.Visible;
  if (genre == "(All)")
  {
    viewModel.LoadGames();
  }
  else
  {
    viewModel.LoadGamesByGenre(genre);
  }

}

Es gibt mehrere Stellen, an denen der Mangel an Elementbindung und Befehlen Silverlight 2-Entwickler dazu zwingt, dieses Verhalten in Code zu behandeln. Da der Code Teil der Ansicht ist, werden die Schichten der Anwendung nicht beeinträchtigt, aber dieses Verfahren ist nicht so direkt wie die ganz aus XAML bestehenden Beispiele in WPF.

Der Stand der Dinge

In Silverlight 2 müssen Sie keine monolithischen Anwendungen erstellen. Das Erstellen von Schichten in Silverlight 2-Anwendungen ist mithilfe des Model-View-ViewModel-Musters, das dazu einfach von WPF entliehen wird, ganz leicht. Des Weiteren ermöglicht der Schichtenansatz Ihnen, die Verantwortungen in Ihren Anwendungen lose zu koppeln, sodass sie einfacher gewartet, erweitert, getestet und bereitgestellt werden können.

Ich möchte Laurent Bugnion (Autor von Silverlight 2 Unleashed) sowie anderen auf der WPF Disciples-Mailingliste für ihre Unterstützung beim Verfassen dieses Artikels danken. Laurent Bugnion führt einen Blog unter blog.galasoft.ch.

Shawn Wildermuth ist ein Microsoft MVP (C#) und der Gründer von Wildermuth Consulting Services. Er ist Autor mehrerer Bücher und zahlreicher Artikel. Darüber hinaus leitet Shawn Wildermuth derzeit die Silverlight-Tour, um in den USA Silverlight 2 zu lehren. Sie können ihn unter shawn@wildermuthconsulting.com erreichen.