Windows Phone 7-Entwicklung

Sudoku für Windows Phone 7

Adam Miller

Beispielcode herunterladen

Sudoku ist in den letzten 10 Jahren sehr beliebt geworden. Sudoku-Rätsel finden sich in den meisten Zeitungen gleich neben dem Kreuzworträtsel. Es gibt mittlerweile auch Spielesendungen im Fernsehen, die auf Sudoku basieren. Für den Fall, dass Sie mit Sudoku nicht vertraut sind – es handelt sich um ein Zahlenersetzungsspiel. Das Spielbrett besteht aus einem 9x9-Raster, und das Ziel besteht darin, die Zahlen 1 bis 9 so in diesem Raster anzuordnen, dass in jeder Zeile, in jeder Spalte und in jedem 3x3-Unterraster jede Zahl nur einmal vorkommt. Das Spiel ist für tragbare Geräte geeignet, und Windows Phone 7 stellt hier keine Ausnahme dar. Sie können im Anschluss an die kürzliche Veröffentlichung von Windows Phone 7 einige Sudoku-Anwendungen auf dem Markt erwarten. Sie können diesen sogar Ihre eigene Anwendung hinzufügen, wenn Sie die Informationen in diesem Artikel gelesen haben.

Einführung in MVVM

Meine Anwendung folgt ungefähr dem Model-View-ViewModel (MVVM)-Entwurfsmuster. Obwohl es keine echten Modelle geben wird (da diese Anwendung keinen Datenbankspeicher benötigt), handelt es sich um ein gutes Tool zum Lernen, da das ViewModel den Kern des Musters darstellt.

Es gibt möglicherweise eine Lernkurve, bis Sie das MVVM-Muster verstanden haben. Wenn Sie es jedoch einmal verstanden haben, können Sie die Benutzeroberfläche und die Geschäftslogik auf wirklich elegante Weise trennen. Außerdem zeigt es die Leistungsfähigkeit der Datenbindung in Silverlight, während es Sie gleichzeitig von der Mehrzahl der lästigen Aufgaben in Verbindung mit der Aktualisierung einer Benutzeroberfläche entlastet (FirstNameTextBox.Text = MyPerson.FirstName ist ein Thema der Vergangenheit!). Weitere Informationen zur Datenbindung in Silverlight finden Sie im MSDN Library-Artikel zur Datenbindung unter tinyurl.com/SLdatabind.

Aufgrund der geringen Größe und der Einfachheit dieser Anwendung wird hier kein MVVM-Framework einer Drittpartei verwendet. Es ist jedoch wahrscheinlich, dass Ihre Anwendung komplexer als diese Anwendung ist, und Sie sollten daher mit dem Framework einer Drittpartei, wie dem MVVM Light Toolkit (mvvmlight.codeplex.com), starten. Dieses stellt Ihnen kostenlos getesteten Code bereit, den Sie ansonsten selbst erstellen müssten (wie ich aus eigener Erfahrung weiß).

Erstellen der Anwendung

Nach der Installation der Entwicklertools von http://xbox.create.msdn.com beginnen Sie mit der Erstellung des neuen Windows Phone 7-Projekts, indem Sie Visual Studio öffnen und File | New | Project (Datei | Neu | Projekt) auswählen. Anschließend wählen Sie im Dialogfeld für das neue Projekt Visual C# | Silverlight for Windows Phone | Windows Phone Application (Visual C# | Silverlight for Windows Phone | Windows Phone-Anwendung) aus. Erstellen Sie zunächst zwei neue Ordner, Views und ViewModels, einem häufig verwendeten MVVM-Muster folgend. An diesem Punkt können Sie auch mit dem Debuggen beginnen, wenn Sie den Emulator wie im SDK bereitgestellt verwenden möchten.

Das Sudoku-Spiel kann in drei Konzepttypen unterteilt werden: die einzelnen Kästchen (insgesamt 81 im typischen 9x9-Raster); die gesamte Spielfläche, auf der sich die Kästchen befinden; und ein Raster für die Zahlen von 1 bis 9 für die Eingabe. Klicken Sie mit der rechten Maustaste auf den Views-Ordner, und wählen Sie Add | New Item (Hinzufügen | Neues Element) aus, um die Ansichten für diese Elemente zu erstellen. Wählen Sie im Dialog die Option für das Windows Phone-Benutzersteuerelement aus, und nennen Sie die erste Datei „GameBoardView.xaml“. Wiederholen Sie diesen Vorgang für „SquareView.xaml“ und „InputView.xaml“. Fügen Sie nun im ViewModel-Ordner die folgenden Klassen hinzu: GameBoardViewModel und SquareViewModel. Für die Eingabeansicht ist kein ViewModel erforderlich. Sie müssen außerdem eine Basisklasse für Ihre ViewModels erstellen, um die Duplizierung von Code zu vermeiden. Fügen Sie Ihrem ViewModels-Ordner eine ViewModelBase-Klasse hinzu. An diesem Punkt sollte Ihre Lösung wie in Abbildung 1 aussehen.

image: Sudoku Windows Phone 7 Solution with Views and ViewModels

Abbildung 1 Windows Phone 7-Sudoku-Lösung mit Views und ViewModels

ViewModelBase-Klasse

Die ViewModelBase-Klasse muss die INotifyPropertyChanged-Schnittstelle implementieren, die in System.ComponentModel enthalten ist. Mit dieser Schnittstelle können die öffentlichen Eigenschaften in ViewModels an Steuerelemente in den Ansichten gebunden werden. Die Implementierung der INotifyPropertyChanged-Schnittstelle ist vergleichsweise einfach. Es muss nur das PropertyChanged-Ereignis implementiert werden. Ihre ViewModelBase.cs-Klasse sollte wie folgt aussehen (denken Sie an die Verwendungsanweisung für System.ComponentModel):

public class ViewModelBase : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler  
    PropertyChanged;
  private void NotifyPropertyChanged(String info)
  {
    if (PropertyChanged != null)
    {
      PropertyChanged(this, 
        new PropertyChangedEventArgs(info));
    }
  }
}

Die meisten MVVM-Frameworks von Drittparteien enthalten eine ViewModel-Basisklasse, die diesen Boilerplate-Code enthält. Alle Ihre ViewModels erben von ViewModelBase. Die Eigenschaften in einem ViewModel, an die die Benutzeroberfläche gebunden wird, müssen im Setter NotifyPropertyChanged aufrufen. Damit wird der Benutzeroberfläche die automatische Aktualisierung ermöglicht, wenn der Wert einer Eigenschaft geändert wird. Die Implementierung aller Eigenschaften auf diese Weise ist etwas langwierig. Dies gleicht den Vorteil etwas aus, den Sie dadurch erzielt haben, dass Sie die Benutzeroberfläche nicht mehr manuell aktualisieren müssen.

Implementieren der einzelnen Kästchen

Implementieren Sie zunächst die SquareViewModel-Klasse. Fügen Sie öffentliche Eigenschaften für Value, Row und Column als Integerwerte hinzu, und IsSelected, IsValid und IsEditable als Boolesche Werte. Obwohl die Benutzeroberfläche direkt an die Value-Eigenschaft gebunden werden kann, führt dies zu Problemen, da für nicht zugewiesene Kästchen eine „0“ angezeigt werden wird. Um dieses Problem zu lösen, können Sie entweder einen Bindungskonverter implementieren oder eine schreibgeschützte StringValue-Eigenschaft erstellen, die eine leere Zeichenfolge zurückgibt, wenn die Value-Eigenschaft Null ist.

Das SquareViewModel ist auch für die Benachrichtigung der Benutzeroberfläche über deren aktuellen Status verantwortlich. Die vier möglichen Statuszustände für ein einzelnes Kästchen in dieser Anwendung sind Default, Invalid, Selected und UnEditable. Normalerweise würde dies als enum implementiert werden. Jedoch verfügen enums im Silverlight-Framework nicht über alle Methoden, über die enums im vollständigen Microsoft .NET Framework verfügen. Dies verursacht die Auslösung einer Ausnahme während der Serialisierung, sodass die Statuszustände als Konstanten implementiert wurden:

public class BoxStates
{
  public const int Default = 1;
  public const int Invalid = 2;
  public const int Selected = 3;
  public const int UnEditable = 4;
}

Öffnen Sie nun SquareView.xaml. Sie werden feststellen, dass einige der Formate in Bezug auf die Schriftgröße und die Schriftfarbe auf Steuerelementebene angewendet wurden. Die voreingestellten Formatressourcen befinden sich in der Regel in einer eigenen Ressourcendatei. In diesem Fall stellt Ihnen Windows Phone 7 diese jedoch standardmäßig bereit. Die Ressourcen werden auf der MSDN Library-Seite „Theme Resources for Windows Phone“ unter tinyurl.com/WP7Resources beschrieben. Einige dieser Formate werden in dieser Anwendung verwendet werden, sodass die Farben der Anwendung dem vom Benutzer ausgewählten Thema entsprechen. Das Thema kann im Emulator ausgewählt werden, indem Sie zum Startbildschirm wechseln und More Arrow | Settings | Theme auswählen. Hier können Sie die Hintergrund- und Akzentfarben ändern (Abbildung 2).

image: Windows Phone 7 Theme Settings Screen

Abbildung 2 Windows Phone 7 – Bildschirm für die Einstellung des Themas

Platzieren Sie innerhalb des Rasters in SquareView.xaml einen Rahmen und einen Textblock:

<Grid x:Name="LayoutRoot" MouseLeftButtonDown=
    "LayoutRoot_MouseLeftButtonDown">
    <Border x:Name="BoxGridBorder" 
      BorderBrush="{StaticResource PhoneForegroundBrush}" 
      BorderThickness="{Binding Path=BorderThickness}">
      <TextBlock x:Name="MainText" 
        VerticalAlignment="Center" Margin="0" Padding="0" 
        TextAlignment="Center" Text=
        "{Binding Path=StringValue}">
      </TextBlock>
    </Border>
  </Grid>

Der Code-Behind für SquareView.xaml.cs kann im entsprechenden Codedownload angezeigt werden. Für den Konstruktor ist eine Instanz von SquareViewModel erforderlich. Diese wird bereitgestellt, wenn die Spielfläche gebunden wird. Außerdem wird ein angepasstes Ereignis ausgelöst, wenn der Benutzer innerhalb des Rasters klickt. Die Verwendung angepasster Ereignisse stellt eine Möglichkeit dar, ViewModels die Kommunikation untereinander zu ermöglichen. Im Fall größerer Anwendungen kann dieser Ansatz jedoch zu Verwirrung führen. Eine andere Option besteht in der Implementierung einer Messenger-Klasse, die die Kommunikation ermöglicht. Die meisten MVVM-Frameworks stellen eine Messenger-Klasse bereit (diese wird manchmal als Mediator-Klasse bezeichnet).

Aus der Sicht eines MVVM-Puristen erscheint es möglicherweise chaotisch, die Benutzeroberfläche mittels Code-Behind zu aktualisieren. Diese Elemente eignen sich jedoch nicht gut für einen BindingConverter. Die BorderThickness von BoxGridBorder basiert auf zwei Eigenschaften, und die Foreground- und Background-Pinsel stammen aus den Anwendungsressourcen, auf die nur schwer über einen BindingConverter zugegriffen werden kann.

Implementieren der Spielfläche

Die GameBoard-Anzeige und ViewModel können nun implementiert werden. Die Anzeige ist einfach, lediglich ein 9x9-Raster. Der im Codedownload verfügbare Code-Behind ist beinahe ebenso einfach. Es handelt sich um eine öffentliche Eigenschaft, die der Veröffentlichung des ViewModel dient, und um einige private Methoden für die Behandlung des untergeordneten Feldklicks und die Bindung des Spielarrays.

 Das ViewModel enthält den größten Teil des Codes. Es enthält Methoden für die Validierung der Fläche nach Benutzereingaben, die Lösung des Puzzles und das Laden der Fläche aus dem Speicher. Die Fläche wird beim Speichern zu XML serialisiert, und zum Speichern der Datei wird IsolatedStorage verwendet. Informieren Sie sich im Quellcodedownload über die vollständige Implementierung; der Speichercode ist am interessantesten und wird in Abbildung 3 gezeigt (beachten Sie, dass Sie einen Verweis auf System.Xml.Serialization benötigen).

Abbildung 3 Der Speichercode für die Spielflächenspeicherung

public void SaveToDisk()
{
  using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
  {
    if (store.FileExists(FileName))
    {
      store.DeleteFile(FileName);
    }

    using (IsolatedStorageFileStream stream = store.CreateFile(FileName))
    {
      using (StreamWriter writer = new StreamWriter(stream))
      {
        List<SquareViewModel> s = new List<SquareViewModel>();
        foreach (SquareViewModel item in GameArray)
          s.Add(item);

        XmlSerializer serializer = new XmlSerializer(s.GetType());
        serializer.Serialize(writer, s);
      }
    }
  }
}

public static GameBoardViewModel LoadFromDisk()
{
  GameBoardViewModel result = null;

  using (IsolatedStorageFile store = IsolatedStorageFile.
    GetUserStoreForApplication())
  {
    if (store.FileExists(FileName))
    {
      using (IsolatedStorageFileStream stream = 
        store.OpenFile(FileName, FileMode.Open))
      {
        using (StreamReader reader = new StreamReader(stream))
        {
          List<SquareViewModel> s = new List<SquareViewModel>();
          XmlSerializer serializer = new XmlSerializer(s.GetType());
          s = (List<SquareViewModel>)serializer.Deserialize(
            new StringReader(reader.ReadToEnd()));

          result = new GameBoardViewModel();
          result.GameArray = LoadFromSquareList(s);
        }
      }
    }
  }

  return result;
}

Implementieren des Eingaberasters

Die Eingabeanzeige ist ebenfalls einfach. Es handelt sich lediglich um einige in Stackpanels verschachtelte Schaltflächen. Der Code-Behind wird in Abbildung 4 gezeigt und enthält ein angepasstes Ereignis, um den Wert der angeklickten Schaltfläche an die Anwendung zu senden, und außerdem zwei Methoden, die die Spielbarkeit des Spiels im Hoch- oder Querformat unterstützen.

Abbildung 4 Der Code-Behind für die Eingabeanzeige

public event EventHandler SendInput;

private void UserInput_Click(object sender, RoutedEventArgs e)
{
  int inputValue = int.Parse(((Button)sender).Tag.ToString());
  if (SendInput != null)
      SendInput(inputValue, null);
}

public void RotateVertical()
{
  TopRow.Orientation = Orientation.Vertical;
  BottomRow.Orientation = Orientation.Vertical;
  OuterPanel.Orientation = Orientation.Horizontal;
}

public void RotateHorizontal()
{
  TopRow.Orientation = Orientation.Horizontal;
  BottomRow.Orientation = Orientation.Horizontal;
  OuterPanel.Orientation = Orientation.Vertical;
}

Zusammenführung von Ansichten auf MainPage.xaml

Schließlich wird die Anwendung durch die Implementierung von MainPage.xaml zusammengeführt. Die Eingabe- und Spielfeldanzeigen werden in einem Raster platziert. Diese Anwendung benötigt die gesamte Bildschirmfläche, sodass der PageTitle-Textblock, der bei der Erstellung des Projekts automatisch eingefügt wurde, entfernt werden muss. Der ApplicationTitle-Textblock ist nur im Hochformatmodus sichtbar. Die Windows Phone 7-Anwendungsleiste wird ebenfalls genutzt. Durch die Verwendung der Anwendungsleiste erscheint die Anwendung besser in das Telefon integriert, und der Sudoku-Anwendung wird eine Oberfläche bereitgestellt, auf der die Benutzer das Puzzle lösen und zurücksetzen sowie ein neues Puzzle starten können:

<phone:PhoneApplicationPage.ApplicationBar>
   <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
     <shell:ApplicationBarIconButton x:Name="NewGame"  
      IconUri="/Images/appbar.favs.rest.png" Text="New Game" 
      Click="NewGame_Click"></shell:ApplicationBarIconButton>
     <shell:ApplicationBarIconButton x:Name="Solve" 
      IconUri="/Images/appbar.share.rest.png" Text="Solve" 
      Click="Solve_Click"></shell:ApplicationBarIconButton>
     <shell:ApplicationBarIconButton x:Name="Clear" 
      IconUri="/Images/appbar.refresh.rest.png" Text="Clear" 
      Click="Clear_Click"></shell:ApplicationBarIconButton>
  </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

Die Bilder stammen aus einem Symbolsatz, den Microsoft speziell für Windows Phone 7 bereitstellt, und werden zusammen mit den Tools unter C:\Programme (x86)\Microsoft SDKs\Windows Phone\v7.0\Icons installiert. Nach dem Importieren der Bilder in das Projekt wählen Sie die Bildeigenschaften aus, ändern die Aktion von „Ressource“ zu „Inhalt“, und wählen für die Option zum Kopieren zum Ausgabeverzeichnis anstelle von „Do Not Copy“ (Nicht kopieren) „Copy If Newer“ (Kopieren, wenn neuer) aus.

Schließlich wird der Code-Behind für MainPage implementiert, um die Erstellung dieser Anwendung abzuschließen. Im Konstruktor ist die SupportedOrientations-Eigenschaft festgelegt, um der Anwendung die Rotation zu gestatten, wenn der Benutzer das Telefon dreht. Außerdem wird das SendInput-Ereignis von InputView behandelt, und der Eingabewert wird an GameBoard weitergeleitet:

public MainPage()
{
  InitializeComponent();
  SupportedOrientations = SupportedPageOrientation.Portrait |
    SupportedPageOrientation.Landscape;
  InputControl.SendInput += new 
    EventHandler(InputControl_SendInput);
}

void InputControl_SendInput(object sender, EventArgs e)
{
  MainBoard.GameBoard.SendInput((int)sender);
}

Außerdem müssen die Navigation-Methoden implementiert werden, um das Laden und Speichern der Spielfläche zu behandeln:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
  GameBoardViewModel board = 
    GameBoardViewModel.LoadFromDisk();
  if (board == null)
    board = GameBoardViewModel.LoadNewPuzzle();

  MainBoard.GameBoard = board;
  base.OnNavigatedTo(e);
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
  MainBoard.GameBoard.SaveToDisk();
  base.OnNavigatedFrom(e);
}

Wenn das Telefon gedreht wird, erhält die Anwendung eine Benachrichtigung. InputView wird vom unteren Rand der Spielfläche zum rechten Rand verschoben und gedreht (siehe Abbildung 5).

Abbildung 5 Code für die Behandlung der Telefonrotation

protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
  switch (e.Orientation)
  {
    case PageOrientation.Landscape:
    case PageOrientation.LandscapeLeft:
    case PageOrientation.LandscapeRight:
      TitlePanel.Visibility = Visibility.Collapsed;
      Grid.SetColumn(InputControl, 1);
      Grid.SetRow(InputControl, 0);
      InputControl.RotateVertical();
      break;
    case PageOrientation.Portrait:
    case PageOrientation.PortraitUp:
    case PageOrientation.PortraitDown:
      TitlePanel.Visibility = Visibility.Visible;
      Grid.SetColumn(InputControl, 0);
      Grid.SetRow(InputControl, 1);
      InputControl.RotateHorizontal();
      break;
    default:
      break;
  }
  base.OnOrientationChanged(e);
}

Hier werden auch die Menüklicks behandelt:

private void NewGame_Click(object sender, EventArgs e)
{
  MainBoard.GameBoard = GameBoardViewModel.LoadNewPuzzle();
}

private void Solve_Click(object sender, EventArgs e)
{
  MainBoard.GameBoard.Solve();
}

private void Clear_Click(object sender, EventArgs e)
{
  MainBoard.GameBoard.Clear();
}

An diesem Punkt ist die Entwicklung des Spiels abgeschlossen, und es kann gespielt werden (siehe Abbildungen 6 und 7).

image: Sudoku Game in Portrait Mode

Abbildung 6 Sudoku-Spiel im Hochformatmodus

image: Solved Game in Landscape Mode

Abbildung 7 Gelöstes Spiel im Querformatmodus

Nun steht Ihnen ein nettes Spiel zur Verfügung, wenn Sie das nächste Mal in einer Schlange stehen. In diesem Artikel wurden die ersten Schritte bei der Erstellung von Silverlight-basierten Windows Phone 7-Anwendungen gezeigt. Außerdem wurde die Verwendung der Serialisierung und des Benutzerspeichers vorgestellt, um eine Anwendung konsistent zu gestalten, und wie Sie der Anwendung die Verwendung in verschiedenen Ausrichtungen ermöglichen. Sie sollten nun auch mit dem MVVM-Muster und der Verwendung der Datenbindung in diesem Zusammenhang vertraut sein.

Adam Miller ist Softwareentwickler bei Nebraska Global in Lincoln, Nebraska. Sie können Ihm auf blog.milrr.com folgen.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Larry Lieberman und Nick Sherrill