Saiba como o aplicativo de exemplo Reversi usa os recursos do aplicativo da Windows Store

Applies to Windows only

A amostra de Reversi usa vários recursos comuns de aplicativos da Windows Store em XAML e C#. Este tópico descreve como a amostra usa alguns desses recursos e fornece links para os principais tópicos de recursos.

Esse tópico não exige que você entenda toda a amostra, mas pressupõe que você já conheça XAML e C# e entenda os princípios básicos de cada recurso, ou que esteja disposto a aprender via leitura dos tópicos relacionados. Para saber mais sobre os princípios básicos do desenvolvimento de aplicativos, veja Crie seu primeiro aplicativo da Windows Store em C# ou Visual Basic.

Para uma introdução geral sobre a amostra, veja Reversi, um jogo da Windows Store em XAML, C# e C++. Para entender como os vários recursos trabalham juntos como um todo, veja Entender a estrutura do aplicativo Reversi. Para aprender como o mecanismo do jogo em C# original foi portado para C++, veja Conheça o mecanismo do jogo Reversi em C++.

Baixe o exemplo de aplicativo Reversi ou procure o código-fonte.

Bloco e tela inicial

O bloco e a tela inicial do aplicativo são os primeiros elementos que o usuário do seu aplicativo vê. Você pode usá-los para fornecer um ponto de entrada atraente e para exibir sua marca. As noções básicas são simples, mas também há recursos mais complexos, como descrito na documentação.

Principais recursos:

O Reversi dá suporte apenas para bloco e tela inicial básicos. Isso inclui blocos quadrados e largos e uma tela inicial, mostrada aqui em tamanho reduzido.

Blocos e tela inicial do Reversi

Os nomes de arquivos de imagem são definidos no arquivo Package.appxmanifest. Você pode fornecer imagens em várias escalas para dar suporte a vários tamanhos de tela. Nenhuma outra implementação é necessária para esse uso simples.

Barra de aplicativos

As barras de aplicativos fornecem um local padrão para colocar comandos de aplicativo. Por padrão, os usuários podem mostrar ou ocultar uma barra de aplicativos, conforme o necessário, tornando-a um bom lugar para colocar os comandos usados com menos frequência. Isso ajuda a manter a interface do usuário focada nas interações diretas com o conteúdo.

Principais recursos:

O Reversi inclui alguns comandos secundários, que são bem adequados à barra de aplicativos: a capacidade de pausar o relógio e o recurso de desfazer ou refazer movimentos. Durante a execução normal do jogo, a barra de aplicativos fica oculta, mas o usuário pode passar o dedo de cima para baixo na tela para exibi-la ou ocultá-la.

Barra de aplicativos do Reversi

Esse código, em GamePage.xaml, mostra a definição da barra de aplicativos. Embora a tela de fundo e a borda sejam transparentes, a propriedade Background está definida como {x:Null} para evitar toques e cliques que bloqueiam a barra de aplicativos invisível. Essa configuração é necessária porque a barra de aplicativos se estende através de toda a tela e se sobrepõe à linha inferior do tabuleiro do jogo.


<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 usa os controles CommandBar e AppBarButton para obter o comportamento e o estilo padrão. O comportamento do botão e seu estado habilitado são fornecidos pelos comandos view-model vinculados às propriedades Command do botão, como descrito na seção Comandos.

Os botões Reproduzir e Pausar funcionam como um único botão de alternância. Para atingir esse efeito, as propriedades Visibility dos botões estão associadas à mesma propriedade view-model. Essas vinculações usam um BooleanToVisibilityConverter, mas uma delas também tem uma configuração de propriedade ConverterParameter que reverte o efeito da associação. Dessa forma, cada botão fica visível apenas quando o outro não está. Para saber mais, veja a seção Vinculação de dados.

Notificações do sistema

As notificações do sistema alertam os usuários quando um evento importante ocorre no aplicativo, mesmo que outro aplicativo esteja ativo no momento.

Principais recursos:

No Reversi, o computador pode demorar um pouco para executar o movimento. Se você alternar para outro aplicativo enquanto espera, uma notificação do sistema alertará você quando for a sua vez.

Notificação do sistema do Reversi

O Reversi usa o mínimo de código necessário para notificações do sistema e define o campo Habilitado para Notificações do Sistema como Sim no designer Package.appxmanifest. O código de notificação do sistema é facilmente reutilizável, portanto, está em uma classe do auxiliar na pasta Common.

Em GameViewModel.cs:


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


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


Submenus de configurações

O botão Configurações fornece acesso padronizado às configurações do aplicativo.

Principais recursos:

O Reversi tem dois submenus Configurações, um para exibir opções e outro, para opções de novo jogo.

Submenus de configurações do Reversi

Esse código em App.xaml.cs mostra como o Reversi manipula o evento SettingsPane.CommandsRequested para criar objetos SettingsCommand. Quando ativado, cada comando cria e mostra um controle 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()));
}


Compartilhando conteúdo

O contrato de compartilhamento permite que seu aplicativo compartilhe dados que os usuários podem enviar a outros aplicativos. Por exemplo, os usuários podem compartilhar dados de seu aplicativo a um aplicativo de email para criar uma nova mensagem.

Principais recursos:

O Windows fornece suporte interno para compartilhamento de uma imagem do aplicativo, e o Reversi não precisa de funcionalidade adicional.

Vinculação de dados

A vinculação de dados permite conectar os controles da IU aos dados que eles exibem, portanto, as alterações em um atualizarão o outro. A vinculação de dados é comum em formulários de entrada de dados, mas também é possível usá-la para direcionar toda a IU e manter a IU separada da lógica do aplicativo.

Principais recursos:

O Reversi vinculações de dados para conectar a respectiva IU (ou camada "exibição") à lógica do aplicativo (ou camada "modelo de exibição"). Essa divisão em camadas ajuda a separar a interface do usuário de outro código e é conhecida como padrão MVVM (Model-View-ViewModel). Para saber mais sobre como o Reversi usa esse padrão, veja Estrutura do aplicativo Reversi. Para ver uma breve introdução ao MVVM, confira Usando o padrão Model-View-ViewModel.

A maioria das vinculações no Reversi são definidas em XAML por meio da extensão de marcação de vinculação, embora o code-behind seja usado em alguns casos (por exemplo, no arquivo Board.xaml.cs). Cada página define sua propriedade DataContext, que é usada por todos os elementos na página como a fonte de dados de suas vinculações.

Atualizações de IU

As vinculações de dados direcionam a IU do Reversi. As interações da IU criam alterações nas propriedades de fonte de dados e as vinculações de dados respondem a essas alterações atualizando a IU.

Essas atualizações funcionam porque as classes view-model do Reversi herdam a classe BindableBase. Essa classe está no arquivo Common/BindableBase.cs e fornece uma implementação INotifyPropertyChanged padrão e alguns métodos de suporte. O método SetProperty atualiza o valor de backup de uma propriedade e também qualquer interface do usuário vinculada usando uma única chamada de método. O método OnPropertyChanged atualiza a interface do usuário vinculada às propriedades especificadas. Isso é útil para controlar o cronograma das atualizações e para as propriedades que obtêm valores de outras propriedades.

Esse código em GameViewModel.cs mostra o uso básico de SetProperty e OnPropertyChanged.


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


Conversão de valor

É possível converter qualquer valor de propriedade em uma forma mais adequada à vinculação criando propriedades calculadas, que são as propriedades que obtêm valores de outras propriedades.

Esse código em GameViewModel.cs mostra uma propriedade calculada simples. A IU vinculada a essa propriedade é atualizada pela chamada OnPropertyChanged correspondente, do exemplo anterior.


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


Propriedades calculadas são fáceis de criar para qualquer tipo de conversão possivelmente necessária, mas elas tendem a bagunçar seu código. Para conversões comuns, o melhor é colocar o código de conversão em uma implementação IValueConverter reutilizável. O Reversi usa as classes NullStateToVisibilityConverter e BooleanToVisibilityConverter da pasta Common/Converters para vinculações que mostram e ocultam vários elementos de IU.

Essa vinculação em StartPage.xaml mostra ou oculta um painel, dependendo de a propriedade ter ou não um valor.


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


Essa vinculação em NewGameSettings.xaml mostra ou oculta um painel, dependendo do estado de um controle ToggleSwitch.


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


Para ver mais exemplos, consulte Barra de aplicativos.

Comandos

Comportamentos de Button geralmente são implementados com manipuladores de eventos Click em arquivos code-behind. O Reversi faz isso nos botões de navegação, mas para outros botões, ele separa a IU do botão do código não IU que o botão invoca. Para tanto, as propriedades Button.Command são vinculadas às propriedades view-model que retornam implementações ICommand.

As propriedades de comandos do Reversi são do tipo DelegateCommand ou DelegateCommand<T>. Essas classes estão no arquivo Common/DelegateCommand.cs e fornecem implementações ICommand padrão reutilizáveis. Você pode usar essas classes para simplificar a criação de comandos de uso único e para manter o código necessário confinado às implementações de propriedades únicas.

Esse código em GameViewModel.cs mostra o comando de movimento usado pelos espaços do tabuleiro, que são botões personalizados. O operador ?? ou "null-coalescing" significa que o valor de campo será retornado apenas se não for null; caso contrário, o campo será definido e o novo valor será retornado. Isso significa que um único objeto de comando será criado na primeira vez que uma propriedade for acessada e o mesmo objeto será reutilizado em todos os acessos futuros. O objeto de comando é inicializado chamando o método DelegateCommand<ISpace>.FromAsyncHandler com referências aos métodos MoveAsync e CanMove. Esses métodos fornecem a implementação dos métodos ICommand.Execute e CanExecute.


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


O método CanExecute é chamado pela vinculação de dados para atualizar o estado habilitado do botão. Entretanto, as vinculações de comandos dependem da notificação de alteração, similar a outras vinculações (discutido em Atualizações da IU). Esse código em GameViewModel.cs mostra como o método UpdateView sincroniza o estado view-model com o estado de modelo e, então, chama OnCanExecuteChanged para cada comando antes de continuar com o próximo movimento.


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


Propriedades de dependência personalizadas

O Reversi usa propriedades de dependência personalizadas em seus controles personalizados para que seja possível usar as atualizações de vinculação de dados para impulsionar alterações do estado visual. Estados visuais e transições animadas são definidos em XAML usando a classe VisualStateManager. Entretanto, não há qualquer maneira de vincular um estado visual diretamente a uma propriedade view-model. Propriedades de dependência personalizadas fornecem destinos para vinculação às propriedades view-model. As propriedades de dependência incluem retornos de chamadas alteradas por propriedade que tornam necessárias as chamadas de método VisualStateManager.GoToState.

Esse código mostra como o controle PlayerStatus usa o code-behind para vincular suas propriedades de dependência vinculadas às propriedades view-model. Somente uma das propriedades de dependência é mostrada aqui, incluindo o respectivo método de retorno de chamada alterada por propriedade. O retorno de chamada e a substituição de método OnApplyTemplate chamam o método de atualização. Entretanto, a chamada OnApplyTemplate inicializa o controle da primeira aparência na tela, portanto, ela não usa transições animadas.


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


Código assíncrono

O código assíncrono ajuda sua interface do usuário a ter capacidade de resposta enquanto seu aplicativo está ocupado com operações demoradas.

Principais recursos:

O Reversi usa código assíncrono para fazer movimentos em um jogo. Cada movimento demora pelo menos um segundo para ser concluído, incluindo a animação do movimento, e os movimentos da inteligência artificial podem demorar mais. Entretanto, a interface do usuário permanece reativa a todo momento, e os comandos do usuário (como desfazer) cancelam um movimento em andamento.

Este código de GameViewModel.cs mostra como o Reversi usa as palavras-chave async e await, a classe Task e os tokens de cancelamento. Observe o uso de AsTask para a integração do código assíncrono do Tempo de Execução do Windows na classe Game. (Para saber mais, veja a próxima seção.)


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


Usando um componente do Tempo de execução do Windows

A implementação de algum código como componente do Tempo de Execução do Windows permite reutilizá-lo em diferentes aplicativos, plataformas ou linguagens. Você também pode substituir facilmente o componente por uma implementação alternativa em outra linguagem.

Recurso-chave:

O Reversi implementa sua lógica central do jogo como um componente do Tempo de Execução do Windows para separá-la totalmente do aplicativo. Isso possibilita extensibilidade e reutilização do código no futuro. O Reversi inclui também uma versão em C++ do mecanismo do jogo como uma alternativa de maior desempenho à versão em C# original. Para saber mais, veja Conheça o mecanismo do jogo Reversi em C++.

Este código de Game.cs mostra como o Reversi usa código assíncrono baseado em Task (incluindo as palavras-chave async e await), mas expõe os resultados através das interfaces assíncronas do Tempo de Execução do Windows. Ele também mostra como o token de cancelamento do código GameViewModel é consumido pela classe Game.

O primeiro e o terceiro métodos no exemplo de código chamam o método AsyncInfo.Run para retornar um IAsyncOperation<T>. Isso encapsula o valor de retorno da tarefa e habilita o cancelamento. O segundo exemplo chama o método WindowsRuntimeSystemExtensions.AsAsyncAction para retornar um IAsyncAction. Isso é útil para tarefas que não precisam retornar valores nem precisam de cancelamento.


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


Tópicos relacionados

Exemplo de aplicativo Reversi
Reversi, um jogo da Windows Store em XAML, C# e C++
Usar o padrão MVVM (Model-View-ViewModel)
Saiba como o aplicativo de exemplo Reversi usa os recursos do aplicativo da Windows Store
Entender a estrutura do aplicativo Reversi
Conheça o mecanismo do jogo Reversi em C++
Crie seu primeiro aplicativo da Windows Store em C# ou Visual Basic
Mapa de aplicativos do Tempo de Execução do Windows em C# ou Visual Basic
Vinculação de dados

 

 

Mostrar:
© 2014 Microsoft