了解黑白棋範例如何使用 Windows 市集應用程式功能

Applies to Windows only

黑白棋範例採用了使用 XAML 和 C# 的 Windows 市集應用程式的數種常見功能。本主題說明範例如何使用這些功能,並提供主要功能的主題連結。

本主題不需要您了解整個範例,但會假設您已經了解 XAML 與 C#,並且已經了解每種功能的基本知識,或是願意閱讀連結的主題來進一步學習。如需有關應用程式開發基本原則的相關資訊,請參閱使用 C# 或 Visual Basic 建立您的第一個 Windows 市集應用程式

如需範例的一般介紹,請參閱黑白棋,使用 XAML、C# 和 C++ 開發的 Windows 市集遊戲。若要了解各種功能如何一起運作,請參閱了解黑白棋應用程式結構。若要了解如何將原始 C# 遊戲引擎移植到 C++,請參閱了解黑白棋 C++ 遊戲引擎

下載黑白棋範例應用程式瀏覽原始程式碼

磚與啟動顯示畫面

應用程式磚與啟動顯示畫面是應用程式使用者最先看到的項目。您可以使用它們來提供吸引使用者的進入點,並顯示您的品牌。基本原理很簡單,但您也可以製作一些較複雜的項目 (如文件所述)。

主要資源:

黑白棋僅提供基本的磚和啟動顯示畫面支援。這包含正方形和寬形磚以及一個啟動顯示畫面,此處顯示縮小後的大小。

黑白棋磚與啟動顯示畫面

影像檔名稱要在 Package.appxmanifest 檔案中設定。您可以提供多個縮放尺寸的影像以支援多個畫面大小。這個簡單的用法不需要其他的實作。

應用程式列

應用程式列提供了一個放置應用程式命令的標準位置。根據預設,使用者可以視需求來顯示或隱藏應用程式列,您可以用它來放置較不常用的命令。這樣可協助您將重點放在與內容直接互動的主要 UI 上。

主要資源:

黑白棋包含數個適合應用程式列的次要命令:暫停時鐘,還有復原或重做移動。在正常的遊戲進行期間,應用程式列是隱藏的,但使用者可以從螢幕上方或下方往內撥動來顯示或隱藏它。

黑白棋應用程式列

下列來自 GamePage.xaml 的程式碼說明應用程式列定義。雖然背景與框線是透明的,但是我們將 Background 屬性設為 {x:Null},以避免不可見的應用程式列阻擋了點選或按一下的動作。此設定是必要的,因為應用程式列會延伸到整個螢幕,並會重疊到遊戲最下方的列。


<Page.BottomAppBar>
  <CommandBar x:Name="GamePageAppBar" Background="{x:Null}" 
    BorderBrush="Transparent" IsSticky="True" Margin="9,0">
    <CommandBar.SecondaryCommands>
      <AppBarButton Icon="Pause" Label="Pause"
        Command="{Binding Clock.PauseCommand}" Click="DismissAppBar"
        Visibility="{Binding Clock.IsPauseButtonVisible, 
          Converter={StaticResource BooleanToVisibilityConverter}}"/>
      <AppBarButton Icon="Play" Label="Play"
        Command="{Binding Clock.PlayCommand}" Click="DismissAppBar"          
        Visibility="{Binding Clock.IsPauseButtonVisible,
          Converter={StaticResource BooleanToVisibilityConverter}, 
          ConverterParameter=Reverse}"/>
      <AppBarButton Icon="Undo" Label="Undo" Command="{Binding UndoCommand}"/>
      <AppBarButton Icon="Redo" Label="Redo" Command="{Binding RedoCommand}"/>
    </CommandBar.SecondaryCommands>
  </CommandBar>
</Page.BottomAppBar>


黑白棋使用 CommandBarAppBarButton 控制項取得預設的行為與樣式。按鈕行為和它的啟用狀態是由繫結至按鈕 Command 屬性的檢視模型命令所提供,如命令一節所述。

[玩] 和 [暫停] 按鈕的作用如同單一切換按鈕。為達到這個效果,這些按鈕的 Visibility 屬性要繫結至相同的檢視模型屬性。這兩個繫結使用 BooleanToVisibilityConverter,但它們其中一個也含有反轉繫結效果的 ConverterParameter 屬性設定。如此一來,每個按鈕只有在另一個按鈕不可見時才會顯示。如需詳細資訊,請參閱資料繫結一節。

快顯通知

當您的應用程式中有重要的事件發生時,即使另一個應用程式目前正在使用中,快顯通知也會通知您的使用者。

主要資源:

在黑白棋中,電腦進行移動時可能需要一段時間。如果您在等待時切換至另一個應用程式,快顯通知會在輪到您通知您。

黑白棋快顯通知

黑白棋使用快顯通知要求的最基本程式碼,而且在 Package.appxmanifest 設計工具中將 [Toast 可達到] 欄位設為 [是]。快顯通知程式碼可輕易地重複使用,因此它位於 Common 資料夾的協助程式類別中。

在 GameViewModel.cs 中:


var window = Windows.UI.Xaml.Window.Current;
if (window != null && !window.Visible && !IsCurrentPlayerAi)
{
    Toast.Show("It's your turn!");
}


在 Toast.cs 中:


public static void Show(string text)
{
    const string template = 
        "<toast duration='short'><visual><binding template='ToastText01'>" +
        "<text id='1'>{0}</text></binding></visual></toast>";
    var toastXml = new XmlDocument();
    toastXml.LoadXml(String.Format(template, text));
    var toast = new ToastNotification(toastXml);
    ToastNotificationManager.CreateToastNotifier().Show(toast);
}


設定飛出視窗

設定常用鍵提供應用程式設定的標準化存取方式。

主要資源:

黑白棋有兩個設定飛出視窗,一個用於顯示選項,一個用於新遊戲選項。

黑白棋設定飛出視窗

下列來自 App.xaml.cs 的程式碼顯示黑白棋如何處理 SettingsPane.CommandsRequested 事件,以建立 SettingsCommand 物件。啟用時,每個命令都會建立並顯示一個 SettingsFlyout 控制項。


SettingsPane.GetForCurrentView().CommandsRequested += OnCommandsRequested;



private void OnCommandsRequested(SettingsPane sender,
    SettingsPaneCommandsRequestedEventArgs args)
{
    args.Request.ApplicationCommands.Add(new SettingsCommand("Display", "Display options", 
        _ => (new DisplaySettings() { DataContext = SettingsViewModel }).Show()));
    args.Request.ApplicationCommands.Add(new SettingsCommand("NewGame", "New game options", 
        _ => (new NewGameSettings() { DataContext = SettingsViewModel }).Show()));
}


分享內容

分享內容可讓您的應用程式分享使用者可以傳送到其他應用程式的資料。例如,使用者可以在電子郵件應用程式中共用您應用程式的資料,以建立新訊息。

主要資源:

Windows 提供內建支援來分享應用程式的影像,因此黑白棋不需要其他功能。

資料繫結

資料繫結可讓您將 UI 控制項連接至它們顯示的資料,如此一來,只要其中一個變更,另一個也會跟著更新。資料繫結在資料輸入表單中很常見,但您也可以使用它來驅動整個 UI,並區隔您的 UI 和應用程式邏輯。

主要資源:

黑白棋使用資料繫結將 UI (或「檢視」層) 連結至它的應用程式邏輯 (或「檢視模型」層)。這種分層方式有助於區隔 UI 和其他程式碼,稱之為「Model-View-ViewModel (MVVM) 模式」。 如需黑白棋如何使用此模式的資訊,請參閱黑白棋應用程式結構。如需有關 MVVM 的簡介,請參閱使用 Model-View-ViewModel 模式

黑白棋中的多數繫結都是利用繫結標記延伸定義在 XAML 中,但少數情況 (例如,在 Board.xaml.cs 檔案中) 會使用程式碼後置。每個頁面都會設定它的 DataContext 屬性,頁面中的所有元素都會使用該屬性做為它們資料繫結的資料來源。

UI 更新

資料繫結會驅動黑白棋中的 UI。UI 互動會導致資料來源屬性變更,而資料繫結會透過更新 UI 來回應這些變更。

這些更新之所以可以運作,是因為黑白棋檢視模型類別會繼承 BindableBase 類別。此類別位於 Common/BindableBase.cs 檔案中,並提供標準的 INotifyPropertyChanged 實作以及一些支援方法。SetProperty 方法會使用單一方法呼叫,更新屬性的支援值及任何繫結的 UI。OnPropertyChanged 方法會更新繫結至指定屬性的 UI。這對於控制更新的時間和從其他屬性取得值的屬性而言很有用。

下列來自 GameViewModel.cs 的程式碼顯示 SetPropertyOnPropertyChanged 的基本用法。


public State CurrentPlayer
{
    get { return _currentPlayer; }
    set
    {
        SetProperty(ref _currentPlayer, value);
        OnPropertyChanged("IsCurrentPlayerAi");
        OnPropertyChanged("IsPlayerOneAi");
        OnPropertyChanged("IsPlayerTwoAi");
        OnPropertyChanged("CurrentPlayerAiSearchDepth");
    }
}


值轉換

您可以透過建立「計算的屬性」(即從其他屬性取得值的屬性),將任何屬性值轉換成更適合繫結的格式。

下列來自 GameViewModel.cs 的程式碼顯示計算的屬性範例。繫結至此屬性的 UI 會透過比對上一個範例中的 OnPropertyChanged 呼叫來更新。


public bool IsPlayerOneAi { get { return (int)PlayerOne > 0; } }


任何您可能需要的轉換種類都可以輕鬆地建立計算的屬性,但它們往往會使程式碼變得雜亂。對於一般轉換,較佳的做法是將轉換程式碼放入可重複使用的 IValueConverter 實作中。對於顯示和隱藏多種 UI 元素的繫結,黑白棋使用 Common/Converters 資料夾中的 NullStateToVisibilityConverterBooleanToVisibilityConverter 類別。

下列來自 StartPage.xaml 的繫結會依據屬性是否含有值來顯示或隱藏面板。


<StackPanel Visibility="{Binding GameViewModel, 
  Converter={StaticResource NullStateToVisibilityConverter}}">


下列來自 NewGameSettings.xaml 的繫結會依據 ToggleSwitch 控制項的狀態來顯示或隱藏面板。


<StackPanel Orientation="Horizontal" 
  Visibility="{Binding IsOn, ElementName=PlayerOneSwitch, 
    Converter={StaticResource BooleanToVisibilityConverter}}">


如需更多範例,請參閱應用程式列

命令

Button 行為通常會與程式碼後置檔案中的 Click 事件處理常式一起實作。黑白棋會在瀏覽按鈕這樣做,但對於其他按鈕,它會區隔按鈕 UI 和按鈕叫用的非 UI 程式碼。 若要這樣做,Button.Command 屬性會繫結至傳回 ICommand 實作的檢視模型屬性。

黑白棋命令屬性的類型為 DelegateCommandDelegateCommand<T>。這些類別位於 Common/DelegateCommand.cs 檔案,而且會提供可重複使用的標準 ICommand 實作。您可以使用這些類別來簡化一次性命令的建立程序,並以單一屬性實作必要的程式碼。

下列來自 GameViewModel.cs 的程式碼顯示棋格使用的移動命令,那些是自訂按鈕。 ?? 或 "null-coalescing" 運算子表示只有在欄位值不是 null 時才會傳回;否則會設定欄位並傳回新值。這表示單一命令物件會在第一次存取屬性時建立,而所有後續的存取會重複使用相同的物件。您可以使用 MoveAsyncCanMove 方法的參考呼叫 DelegateCommand<ISpace>.FromAsyncHandler 方法來初始化命令物件。這些方法會為 ICommand.ExecuteCanExecute 方法提供實作。


public DelegateCommand<ISpace> MoveCommand 
{ 
    get 
    { 
        return _moveCommand ?? (_moveCommand = 
            DelegateCommand<ISpace>.FromAsyncHandler(MoveAsync, CanMove));
    } 
}


資料繫結會呼叫 CanExecute 方法,以更新按鈕的啟用狀態。不過,命令繫結依賴與其他繫結類似的變更通知 (在 UI 更新中探討)。下列來自 GameViewModel.cs 的程式碼顯示 UpdateView 方法如何同步處理檢視模型狀態與模型狀態,然後在繼續下一個移動之前為每個命令呼叫 OnCanExecuteChanged


private void UpdateView()
{
    SyncModelProperties();
    UpdateBoard();
    UndoCommand.RaiseCanExecuteChanged();
    RedoCommand.RaiseCanExecuteChanged();
    MoveCommand.RaiseCanExecuteChanged();
}


自訂相依性屬性

黑白棋在其自訂控制項中使用自訂相依性屬性,因此可以使用資料繫結更新來驅動視覺狀態變更。視覺狀態與動畫轉換使用 VisualStateManager 類別定義在 XAML 中。不過,視覺狀態無法直接繫結到檢視模型屬性。自訂相依性屬性會提供繫結至檢視模型屬性的目標。相依性屬性包含屬性變更的回呼,它會進行必要的 VisualStateManager.GoToState 方法呼叫。

下列程式碼顯示 PlayerStatus 控制項如何使用程式碼後置,將它的自訂相依性屬性繫結至檢視模型屬性。此處僅顯示一個相依性屬性,包括屬性變更的回呼方法。回呼與 OnApplyTemplate 方法覆寫都會呼叫更新方法。不過,OnApplyTemplate 呼叫會在控制項第一次顯示於畫面上時將它初始化,因此不會使用動畫轉換。


public PlayerStatus()
{
    DefaultStyleKey = typeof(PlayerStatus);
    SetBinding(CurrentPlayerProperty, new Binding { 
        Path = new PropertyPath("CurrentPlayer") });
    SetBinding(IsClockShowingProperty, new Binding { 
        Path = new PropertyPath("Settings.IsClockShowing") });
    SetBinding(IsGameOverProperty, new Binding { 
        Path = new PropertyPath("IsGameOver") });
    SetBinding(WinnerProperty, new Binding { 
        Path = new PropertyPath("Winner") });
}

protected override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    UpdatePlayerState(false);
    UpdateClockState(false);
    UpdateGameOverState(false);
}

public bool IsClockShowing
{
    get { return (bool)GetValue(IsClockShowingProperty); }
    set { SetValue(IsClockShowingProperty, value); }
}

public static readonly DependencyProperty IsClockShowingProperty =
    DependencyProperty.Register("IsClockShowing", typeof(bool),
    typeof(PlayerStatus), new PropertyMetadata(true, IsClockShowingChanged));

private static void IsClockShowingChanged(DependencyObject d, 
    DependencyPropertyChangedEventArgs e)
{
    (d as PlayerStatus).UpdateClockState(true);
}

private void UpdateClockState(bool useTransitions)
{
    GoToState(IsClockShowing ? "ClockShowing" : "ClockHidden", useTransitions);
}

private void GoToState(string state, bool useTransitions)
{
    VisualStateManager.GoToState(this, state, useTransitions);
}


非同步程式碼

非同步程式碼可以在您的應用程式忙於進行非常耗時的操作時,協助讓您的 UI 保持回應狀態。

主要資源:

黑白棋使用非同步程式碼來執行遊戲中的移動。每個移動 (包含移動動畫在內) 至少都需要一秒來完成,而 AI 移動可能需要更久的時間。但是,UI 隨時都能保持回應狀態,而使用者命令 (例如復原) 將取消進行中的移動。

這個來自 GameViewModel.cs 的程式碼示範黑白棋如何使用 asyncawait 關鍵字、Task 類別及取消權杖。請注意,使用 AsTaskGame 類別中的 Windows 執行階段非同步程式碼整合 (如需詳細資訊,請參閱下一節)。


private async Task MoveAsync(ISpace move)
{
    var cancellationToken = GetNewCancellationToken();
    LastMoveAffectedSpaces = await Game.MoveAsync(move).AsTask(cancellationToken);
    if (cancellationToken.IsCancellationRequested) return;
    await OnMoveCompletedAsync(cancellationToken);
}



private async Task AiMoveAsync()
{
    var cancellationToken = GetNewCancellationToken();

    // Unlike the MoveAsync method, the AiMoveAsync method requires a try/catch 
    // block for cancellation. This is because the AI search checks for 
    // cancellation deep within a recursive, iterative search process
    // that is easiest to halt by throwing an exception. 
    try
    {
        // The WhenAll method call enables the delay and the AI search to 
        // occur concurrently. However, in order to retrieve the return 
        // value of the first task, both tasks must have the same signature,
        // thus requiring the delay task to have a (meaningless) return value.  
        var results = await Task.WhenAll(
            Game.GetBestMoveAsync(CurrentPlayerAiSearchDepth)
                .AsTask(cancellationToken),
            Task.Run(async () =>
            {
                await DelayAsync(MinimumTurnLength, cancellationToken);
                return (ISpace)null;
            })
        );

        // Perform the AI move only after both the 
        // search and the minimum delay have passed.
        LastMoveAffectedSpaces = await Game.MoveAsync(
            results[0]).AsTask(cancellationToken);
        if (cancellationToken.IsCancellationRequested) return;

        await OnMoveCompletedAsync(cancellationToken);
    }
    catch (OperationCanceledException)
    {
        System.Diagnostics.Debug.WriteLine("cancelled with exception");
    }
}


使用 Windows 執行階段元件

實作部分程式碼以做為 Windows 執行階段元件,讓您能夠在不同應用程式、不同平台或使用不同語言,重複使用該程式碼。您也可以在其他語言中使用替代的實作,更輕易地取代該元件。

主要資源:

黑白棋會將它的核心遊戲邏輯實作為 Windows 執行階段元件,以便將它從應用程式中完整分離出來。這使它能夠支援未來的擴充性和程式碼重複使用。黑白棋也包含遊戲引擎的 C++ 版本,以做為效能較原始 C# 版本高的替代選項。如需詳細資訊,請參閱了解黑白棋 C++ 遊戲引擎

這個來自 Game.cs 的程式碼示範黑白棋如何使用以 Task 為基礎的非同步程式碼 (包含 asyncawait 關鍵字),但是會透過 Windows 執行階段非同步介面公開結果。其中也會示範 Game 類別如何使用來自 GameViewModel 程式碼的取消權杖。

範例程式碼中的第一個和第三個方法會呼叫 AsyncInfo.Run 方法以傳回 IAsyncOperation<T>。這會包裝工作的傳回值並啟用取消。第二個範例會呼叫 WindowsRuntimeSystemExtensions.AsAsyncAction 方法以傳回 IAsyncAction。這對於沒有傳回值且不需取消的工作而言非常有用。


public IAsyncOperation<IList<ISpace>> MoveAsync(ISpace move)
{
    // Use a lock to prevent the ResetAsync method from modifying the game 
    // state at the same time that a different thread is in this method.
    lock (_lockObject)
    {
        return AsyncInfo.Run(cancellationToken => Task.Run(() =>
        {
            if (cancellationToken.IsCancellationRequested) return null;
            var changedSpaces = Move(move);
            SyncMoveStack(move);
            return changedSpaces;
        }, cancellationToken));
    }
}



public IAsyncAction AiMoveAsync(int searchDepth)
{
    return Task.Run(async () => 
    {
        // If it is the AI's turn and we're not at the end of the move stack,
        // just use the next move in the stack. This is necessary to preserve
        // the forward stack, but it also prevents the AI from having to search again. 
        var bestMove = Moves.Count < MoveStack.Count ? 
            MoveStack[Moves.Count] : await GetBestMoveAsync(searchDepth);
        await MoveAsync(bestMove);
    }).AsAsyncAction();
}

public IAsyncOperation<ISpace> GetBestMoveAsync(int searchDepth)
{
    if (searchDepth < 1) throw new ArgumentException(
        "must be 1 or greater.", "searchDepth");

    return AsyncInfo.Run(cancellationToken => Task.Run(() => 
    {
        return (ISpace)reversiAI.GetBestMove(Board, 
            CurrentPlayer == State.One, searchDepth, cancellationToken);
    }, cancellationToken));
}


相關主題

黑白棋範例應用程式
黑白棋,使用 XAML、C# 和 C++ 開發的 Windows 市集遊戲
使用 Model-View-ViewModel (MVVM) 模式
了解黑白棋範例如何使用 Windows 市集應用程式功能
了解黑白棋應用程式結構
了解黑白棋 C++ 遊戲引擎
使用 C# 或 Visual Basic 建立您的第一個 Windows 市集應用程式
使用 C# 或 Visual Basic 建立 Windows 執行階段應用程式的藍圖
資料繫結

 

 

顯示:
© 2014 Microsoft