本文章是由機器翻譯。

Windows Phone 7 開發

Sudoku for Windows Phone 7

Adam Miller

下載示例代碼

Sudoku 近 10 年來已變成一款很流行的遊戲,在大多數報紙上,填字遊戲的旁邊都會有 Sudoku。甚至還根據 Sudoku 創辦了遊戲節目。如果您不了解 Sudoku,您可以將它理解為一種數位填寫遊戲。遊戲台是一個 9x9 網格,目標是將數位 1 到數位 9 填入網格中,使每個數位在每個行和列以及 3x3 子網格中僅出現一次。這個遊戲的特性決定了它非常適合在可擕式設備上玩,Windows Phone 7 也不例外。在近期發佈 Windows Phone 7 之後,市場上很快就會出現一些 Sudoku 應用程式,並且您甚至可以按照本文中所述方法將自己的 Sudoku 應用程式添加到相應的清單中。

MVVM 簡介

我的應用程式大致上將遵循 Model-View-ViewModel (MVVM) 設計模式。雖然沒有任何實際模型(因為此應用程式不需要資料庫存儲),但它仍是一種很好的學習工具,因為 ViewModel 確實是這一模式的核心。

瞭解 MVVM 模式可能需要一個學習過程,但在您完成相關知識的學習後,便可以巧妙地實現 UI 與業務邏輯的分離。此外,它揭示了資料綁定在 Silverlight 中起到的作用,同時使您在更新 UI 時無需編寫大多數繁瑣的代碼(FirstNameTextBox.Text = MyPerson.FirstName 將一去不返!)。有關 Silverlight 中的資料綁定的詳細資訊,請參閱 tinyurl.com/SLdatabind 上的 MSDN 庫文章“資料綁定”。

考慮到此應用程式的大小、簡單性和本文的重點,將不使用協力廠商 MVVM 框架。但由於您的應用程式很可能變得比這個應用程式更為複雜,因此從協力廠商框架(如 MVVM Light Toolkit)開始將是您明智的選擇 (mvvmlight.codeplex.com)。它將為您提供經測試的免費代碼,您最後可以任意編寫這些代碼(經驗所得)。

創建應用程式

從 create.msdn.com 安裝了開發人員工具之後,首先通過打開 Visual Studio 並選擇“檔”|“新建”|“專案”來創建一個新的 Windows Phone 7 專案,然後在“新建專案”對話方塊中,選擇“Visual C#”|“Silverlight for Windows Phone”|“Windows Phone Application”。首先按照常用 MVVM 模式創建兩個新資料夾,即 Views 和 ViewModels。此時,如果您想流覽已作為 SDK 的一部分提供的模擬器,則還可以開始調試。

Sudoku 遊戲在概念上可分為三個類型:各個方格(9x9 遊戲台中通常共有 81 個方格);容納這些方格的整體遊戲台;用於輸入數位 1 到數位 9 的網格。若要創建這些項的視圖,請按右鍵 Views 資料夾,再選擇“添加”|“新建專案”。從對話方塊中選擇“Windows Phone 使用者控制項”,並將第一個檔命名為 GameBoardView.xaml。對 SquareView.xaml 和 InputView.xaml 重複上述操作。此時,在 ViewModel 資料夾中添加以下類:GameBoardViewModel 和 SquareViewModel。“輸入視圖”不需要 ViewModel。您還需要為 ViewModels 創建一個基類以避免代碼重複。向 ViewModels 資料夾添加 ViewModelBase 類。此時,您的解決方案應與圖 1 中所示內容類別似。

圖 1 包含 Views 和 ViewModels 的 Sudoku Windows Phone 7 解決方案

ViewModel 基類

ViewModelBase 類將需要實現在 System.ComponentModel 中找到的 INotifyPropertyChanged 介面。此介面允許將 ViewModels 中的公共屬性綁定到視圖中的控制項。INotifyPropertyChanged 介面的實現相當簡單 - 只需實現 PropertyChanged 事件即可。您的 ViewModelBase.cs 類看起來應與下麵的內容類別似(不要忘記 System.ComponentModel 的 using 語句):

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

大多數協力廠商 MVVM 框架將包括一個 ViewModel 基類,其中包含此樣本代碼。您所有的 ViewModel 都將從 ViewModelBase 繼承。UI 將綁定到的 ViewModel 中的屬性必須調用 setter 中的 NotifyPropertyChanged。這是允許 UI 在屬性值發生更改時自動更新的設置。按此方式實現所有屬性確實有點繁瑣,但這樣做換來的好處是,您不必編寫代碼來更新 UI。

實現各個方格

首先實現 SquareViewModel 類。將 Value、Row、Column 的公共屬性添加為整數;將 IsSelected、IsValid 和 IsEditable 添加為布林值。雖然可將 UI 直接綁定到 Value 屬性,但這將導致出現問題,因為將為未分配的方格顯示“0”。若要解決此問題,可以實現綁定轉換器或創建唯讀“StringValue”屬性,該屬性將在 Value 屬性為零時返回空字串。

SquareViewModel 還負責向 UI 通知其當前狀態。此應用程式中的單個方格具有四種狀態,即“Default”(預設)、“Invalid”(無效)、“Selected”(已選定)和“UnEditable”(不可編輯)。通常,這將作為枚舉實現;但 Silverlight 框架中的枚舉不包含完整 Microsoft .NET Framework 的枚舉所具有的幾種方法。這會導致在序列化期間引發異常,因此已將狀態實現為常數:

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

現在打開 SquareView.xaml。您會發現,已在控制項級別為字型大小和顏色應用了某些樣式。通常,可以在單獨的資源檔中找到預設置樣式資源,但在此示例中,Windows Phone 7 預設情況下將向您的應用程式提供這些資源。tinyurl.com/WP7Resources 上的 MSDN 庫頁“Windows Phone 的主題資源”中對這些資源進行了描述。此應用程式將使用其中的一些樣式,以使其顏色與使用者選定的主題匹配。可通過轉到主頁螢幕,並按一下“更多”箭頭|“設置”|“主題”在模擬器中選擇主題。您可以從此處更改背景色和強調文字顏色(圖 2)。

圖 2 Windows Phone 7 主題設置螢幕

在 SquareView.xaml 中的網格內,放置一個 Border 和一個 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>

可在附帶的代碼下載中看到 SquareView.xaml.cs 的隱藏代碼。此構造函數需要 SquareViewModel 的實例。這將在綁定遊戲台時提供。此外,還有一個當使用者在網格內按一下時引發的自訂事件。使用自訂事件是允許 ViewModel 互相通信的一種方法;但對於大型應用程式,這種方法會比較麻煩。您還有一個選擇,即實現將推動通信的 Messenger 類。大多數 MVVM 框架都提供了 Messenger(有時稱作 Mediator)類。

單就 MVVM 而言,使用隱藏代碼更新 UI 可能看起來比較麻煩,但這些項本身不適合于 BindingConverter。BoxGridBorder 的 BorderThickness 基於兩個屬性,並且前景畫筆和背景畫筆來自應用程式資源,無法從 BindingConverter 中輕易訪問它們。

實現遊戲台

現在可以實現 GameBoard 視圖和 ViewModel。此視圖就是一個 9x9 網格,非常簡單。代碼下載中提供的代碼隱藏很簡單 - 只是一個用於公開 ViewModel 的公共屬性和幾個用於處理子框按一下和綁定遊戲陣列的私有方法。

ViewModel 包含大部分代碼。它包含用於在使用者輸入後驗證遊戲台的方法、用於解決難題的方法以及用於保存和載入存儲中的遊戲台的方法。在保存時會將遊戲台序列化為 XML,並使用 IsolatedStorage 保存此檔。有關完全實現,請參閱原始程式碼下載;存儲代碼是最有趣的,如圖 3 中所示(請注意,您需要對 System.Xml.Serialization 的引用)。

圖 3 遊戲台存儲代碼

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;}

實現輸入網格

輸入視圖也非常簡單,它只是堆疊面板中嵌入的幾個按鈕。 圖 4 中所示的隱藏代碼公開了自訂事件以向應用程式發送已按一下按鈕的值,以及兩個用於説明使該遊戲能在縱向模式或橫向模式中可玩的方法。

圖 4 輸入視圖的隱藏代碼

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;}

將視圖都集中到 MainPage.xaml 上

最後,將此應用程式與 MainPage.xaml 的實現結合在一起。 將輸入視圖和遊戲台視圖置於一個網格中。 由於此應用程式需要所有可用的螢幕空間,因此必須刪除在創建專案時自動插入的 PageTitle TextBlock。 ApplicationTitle TextBlock 僅在縱向模式中可見。 還將利用 Windows Phone 7 應用程式欄。 通過使用此應用程式欄,可使應用程式感覺上與手機的集成性更高,並將為 Sudoku 應用程式提供一個很好的介面,以允許使用者解答、重置和開始新謎題:

<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>

從 Microsoft 專門為 Windows Phone 7 提供的圖示集中獲取圖像,這些圖像將與工具一起安裝到 C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.0\Icons。 在將圖像導入專案後,選擇圖像屬性,並將“生成操作”從“資源”更改為“內容”,然後將“複製到輸出目錄”從“不復制”更改為“如果較新則複製”。

此應用程式謎題的最後一個部分是實現 MainPage 隱藏代碼。 在構造函數中,設置 SupportedOrientations 屬性以允許應用程式在使用者旋轉手機時隨之旋轉。 另外,處理 InputView 的 SendInput 事件,並將輸入值傳送到 GameBoard:

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);}

還必須實現導航方法以便載入和保存遊戲台:

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);}

在旋轉手機時,應用程式將收到一個通知。 InputView 會從該位置開始從遊戲台下方移動到其右側並進行旋轉(參見圖 5)。

圖 5 用於處理手機旋轉的代碼

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);}

這也是處理功能表項目按一下的位置:

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();}

此時,該遊戲已完成,可以開始玩了(參見圖 67)。

圖 6 縱向模式下的 Sudoku

圖 7 橫向模式下的已解答的遊戲

現在您就可以玩這個很好玩的遊戲了,下次您就要排隊等候了。 本文演示了如何開始創建基於 Silverlight 的 Windows Phone 7 應用程式。 本文還演示了如何使用序列化和使用者存儲來保持應用程式,以及如何允許應用程式支援多個方向。 另外,現在您應熟悉了 MVVM 模式以及如何將其與資料綁定一起使用。

Adam Miller 是位於 Lincoln, Neb 的 Nebraska Global 的一名軟體工程師。您可以訪問他的博客:blog.milrr.com。

衷心感謝以下技術專家對本文的審閱:Larry Lieberman 和 Nick Sherrill