Использование функций приложения Магазина Windows на примере игры Reversi

Applies to Windows only

В примере Reversi используются некоторые общие функции приложений Магазина Windows на XAML и C#. В этом разделе рассказывается об использовании этих функций в примере, а также предоставляются ссылки на разделы, посвященные ключевым функциям.

Для чтения этого раздела не требуется понимания всего примера, но предполагается, что вы уже знакомы с языками программирования XAML и C# и понимаете основы каждой функции или хотите в этом разобраться, читая разделы по ссылкам. Подробнее об основах разработки приложений см. раздел Создание первого приложения Магазина Windows на C# или Visual Basic.

Чтобы получить общее представление об образце, см. раздел Reversi — игра Магазина Windows на XAML, C# и C++. Для понимания того, каким образом различные функции работают вместе как единое целое, см. раздел Разбор структуры приложения Reversi. Чтобы узнать, как исходное ядро игры на C# было перенесено в C++, см. раздел Сведения об игровом ядре Reversi на C++.

Скачайте пример приложения Reversi или просмотрите исходный код.

Плитка и экран-заставка

Плитка и экран-заставка приложения — это первое, что видит пользователь вашего приложения. С их помощью вы можете создать привлекательную точку входа, а также отобразить свою торговую марку. Основы тривиальны, но вы можете также выполнить более сложные действия, описанные в этой документации.

Ключевые ресурсы:

В приложении Reversi используется только базовая поддержка плиток и экрана-заставки. Эта поддержка включает квадратные и широкие плитки и экран-заставку, показанный здесь в уменьшенном виде.

Плитки и экран-заставка приложения Reversi

Имена файлов изображений заданы в файле Package.appxmanifest. Вы можете предоставить изображения в нескольких масштабах для поддержки разных размеров экрана. Для этого простого использования не требуется никакой другой реализации.

Панель приложения

Панели приложения предоставляют стандартное место для размещения команд приложения. По умолчанию пользователи могут отобразить или скрыть панель приложения, что делает ее хорошим местом для редко используемых команд. Это поможет вам сфокусировать основной пользовательский интерфейс на непосредственных взаимодействиях с содержимым.

Ключевые ресурсы:

Пример Reversi включает несколько вспомогательных команд, которые хорошо подходят для панели приложения: приостановка часов и возврат хода. Во время нормальной игры панель приложения скрыта, но пользователь может отобразить или скрыть ее жестом прокрутки от верхнего или нижнего края экрана.

Строка команд приложения Reversi

В этом коде из 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>


Reversi использует элементы управления CommandBar и AppBarButton для получения стандартного поведения и стиля. Поведение кнопки и ее включенное состояние предоставляются командами модели представления, привязанными к свойствам Command, о чем рассказывается в разделе Команды.

Кнопки Воспроизведение и Пауза работают как один выключатель. Чтобы достичь такого эффекта, свойства Visibility кнопок привязаны к тем же свойствам модели представления. Обе привязки используют BooleanToVisibilityConverter, но у одной из них также есть свойство ConverterParameter, изменяющее эффект привязки на обратный. Таким образом, каждая кнопка видна только тогда, когда не видна другая. Подробнее см. в разделе Привязка данных.

Всплывающие уведомления

Всплывающие уведомления сообщают пользователям о важных событиях в вашем приложении, даже если в данный момент активным является другое приложение.

Ключевые ресурсы:

В приложении Reversi компьютер может задуматься на некоторое время, прежде чем сделать ход. Если, ожидая ответа, вы переключитесь на другое приложение, всплывающее уведомление сообщит вам о ходе компьютера.

Всплывающее уведомление приложения Reversi

В приложении Reversi для всплывающих уведомлений используется минимум кода, а для поля Всплывающие уведомления устанавливается значение Да в конструкторе Package.appxmanifest. Код всплывающего уведомления легко повторно использовать, поэтому он находится в классе поддержки в папке 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);
}


Всплывающие элементы "Параметры"

Чудо-кнопка "Параметры" предоставляет стандартизированный доступ к параметрам приложения.

Ключевые ресурсы:

У приложения Reversi есть два всплывающих элемента "Параметры": один для параметров отображения, а другой для параметров новой партии.

Всплывающие элементы параметров приложения Reversi

Этот код из файла App.xaml.cs показывает, как приложение Reversi обрабатывает событие 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 предлагает встроенную поддержку отправки изображения приложения, и Reversi не требуются дополнительные функциональные возможности.

Привязка данных

С помощью привязки данных вы можете соединить элементы управления пользовательского интерфейса с отображаемыми ими данными таким образом, чтобы изменения в данных отражались в элементах управления и наоборот. Привязка данных часто применяется для форм ввода данных, но ее можно также использовать для управления всем пользовательским интерфейсом, а также чтобы отделить его от логики приложения.

Ключевые ресурсы:

В приложении Reversi используются привязки данных для связывания пользовательского интерфейса (или уровня представления) с логикой приложения (или уровнем модели представления). Многоуровневость помогает отделить пользовательский интерфейс от другого кода и называется шаблоном Model-View-ViewModel (MVVM). Дополнительные сведения о том, как приложение Reversi использует этот шаблон, см. в разделе Структура приложения Reversi. Краткое введение в MVVM см. в статье Использование шаблона Model-View-ViewModel (MVVM).

Большинство привязок в приложении Reversi определяются в XAML с помощью Расширения разметки для привязки, хотя код программной части используется в некоторых случаях (например, в файле Board.xaml.cs). Каждая страница задает свое свойство DataContext, используемое всеми элементами на странице в качестве источника данных для их привязок.

Обновления пользовательского интерфейса

Привязки данных управляют пользовательским интерфейсом в приложении Reversi. Взаимодействия с пользовательским интерфейсом вызывают изменения свойств источника данных, и привязки данных реагируют на эти изменения, обновляя пользовательский интерфейс.

Эти обновления работают, поскольку классы модели представления Reversi наследуют от класса BindableBase. Этот класс находится в файле Common/BindableBase.cs и представляет стандартную реализацию INotifyPropertyChanged и некоторые методы поддержки. Метод SetProperty обновляет резервное значение свойства, а также все привязанные элементы пользовательского интерфейса с помощью одного вызова метода. Метод OnPropertyChanged обновляет пользовательский интерфейс, привязанный к указанным свойствам. Это удобно для управления временем обновлений, а также для свойств, получающих свои значения от других свойств.

В этом коде из GameViewModel.cs продемонстрировано использование обоих методов — SetProperty и OnPropertyChanged.


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


Преобразование значения

Вы можете преобразовать любые значения свойств в форму, более подходящую для привязки, создав вычисляемые свойства, то есть свойства, получающие свои значения из других свойств.

В этом коде из GameViewModel.cs показано простое вычисляемое свойство. Пользовательский интерфейс, привязанный к этому свойству, обновляется согласующим вызовом OnPropertyChanged из предыдущего примера.


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


Вычисляемые свойства легко создаются для любого типа преобразования, который может понадобиться, но они имеют тенденцию к загромождению вашего кода. Для часто используемых преобразований лучше поместить код в повторно используемую реализацию IValueConverter. В приложении Reversi используются классы NullStateToVisibilityConverter и BooleanToVisibilityConverter в папке Common/Converters для привязок, которые отображают и скрывают различные элементы пользовательского интерфейса.

Эти привязки из 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 в файлах кода программной части. В приложении Reversi так сделано для кнопок навигации, но для других кнопок пользовательский интерфейс кнопки отделен от не имеющего отношения к интерфейсу кода, который вызывается этой кнопкой. Для этого свойства Button.Command привязаны к свойствам модели представления, возвращаемым реализациями ICommand.

Свойства команд приложения Reversi относятся к типу DelegateCommand или DelegateCommand<T>. Эти классы содержатся в файле Common/DelegateCommand.cs и предоставляют стандартные реализации ICommand с поддержкой повторного использования. Вы можете использовать эти классы для упрощения создания команд одноразового использования и для ограничения необходимого кода рамками реализации отдельного свойства.

Этот код из GameViewModel.cs демонстрирует команду перемещения, используемую областями доски, которые являются пользовательскими кнопками. ??, или оператор "поглощения нуля", означает, что значение поля возвращается, только если оно не равно null. В противном случае полю присваивается новое значение, которое возвращается методом. Это означает, что одиночный командный объект создается при первом обращении к свойству, а при всех последующих обращениях этот объект используется снова. Командный объект инициализируется при вызове метода DelegateCommand<ISpace>.FromAsyncHandler со ссылками на методы MoveAsync и CanMove. Эти методы представляют реализацию методов ICommand.Execute и CanExecute.


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


Метод CanExecute вызывается привязкой данных для обновления включенного состояния кнопки. Однако привязки команд основаны на уведомлении об изменениях, аналогично уведомлениям для других привязок (см. Обновление пользовательского интерфейса). В этом коде из GameViewModel.cs показано, как метод UpdateView синхронизирует состояние модели представления с состоянием модели, а затем для каждой команды вызывает метод OnCanExecuteChanged, прежде чем перейти к следующему ходу.


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


Пользовательские свойства зависимостей

В приложении Reversi применяются пользовательские свойства зависимостей в пользовательских элементах управления, так что оно может использовать обновление привязок данных для изменения визуального состояния. Визуальные состояния и анимированные переходы определяются в XAML с помощью класса VisualStateManager. Однако визуальное состояние невозможно напрямую привязать к свойству модели представления. Пользовательские свойства зависимостей предоставляют конечные объекты для привязки к свойствам модели представления. Свойства зависимостей включают обратные вызовы при изменения свойства, вызывающие метод 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);
}


Асинхронный код

Асинхронный код позволяет пользовательскому интерфейсу продолжать реагировать на запросы, когда приложение занято выполнением операций, требующих много времени.

Ключевые ресурсы:

Для выполнения ходов в партии в приложении Reversi используется асинхронный код. Выполнение каждого хода требует по меньшей мере секунду, включая анимацию хода, а для выполнения ходов с использованием искусственного интеллекта может понадобиться значительно больше времени. Тем не менее пользовательский интерфейс все это время продолжает реагировать на запросы, а команды пользователя (например, отмена хода) позволяют отменить выполнение хода.

В этом коде из файла GameViewModel.cs показано, как в приложении Reversi используются ключевые слова async и await, класс Task и токены отмены. Обратите внимание на использование метода AsTask для интеграции с асинхронным кодом среды выполнения Windows в классе Game. (Подробнее см. в следующем разделе.)


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 позволяет вам повторно использовать этот код в других приложениях, которые написаны на различных языках и работают на разных платформах. Кроме того, компонент легче заменить альтернативной реализацией на другом языке.

Ключевой ресурс:

В приложении Reversi базовая логика игры реализована как компонент среды выполнения Windows, чтобы полностью отвязать ее от приложения. Это позволяет ему поддерживать возможность расширения в будущем и повторное использование кода. Reversi включает также альтернативную версию ядра игры на C++, производительность которой выше, чем у исходной версии на C#. Подробнее: Сведения об игровом ядре Reversi на C++.

В этом коде из файла Game.cs показано, как в приложении Reversi используется асинхронный код на основе класса Task (включая ключевые слова async и await), но результаты выводятся через асинхронные интерфейсы среды выполнения Windows. В нем также показано, как токен отмены из кода GameViewModel обрабатывается классом Game.

В первом и третьем методах примера вызывается метод 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));
}


Связанные разделы

Пример приложения Reversi
Reversi — игра Магазина Windows на XAML, C# и C++
Использование шаблона Model-View-ViewModel (MVVM)
Использование функций приложения Магазина Windows на примере игры Reversi
Разбор структуры приложения Reversi
Сведения об игровом ядре Reversi на C++
Создание первого приложения Магазина Windows на C# или Visual Basic
Схема создания приложений среды выполнения Windows на C# или Visual Basic
Привязка данных

 

 

Показ:
© 2014 Microsoft