Aprender cómo la muestra de Reversi usa características de aplicaciones de la Tienda Windows

Applies to Windows only

La muestra de Reversi usa varias características comunes de aplicaciones de la Tienda Windows con XAML y C#. En este tema, describimos la manera en que la muestra usa algunas de estas características y proporcionamos vínculos a temas sobre características clave.

No es necesario que comprendas toda la muestra, pero suponemos que ya conoces XAML y C#, que comprendes los conceptos básicos de cada característica o que deseas aprender sobre ellos leyendo los temas vinculados. Para obtener más información sobre los aspectos básicos del desarrollo de aplicaciones, consulta Crear la primera aplicación de la Tienda Windows con C# o Visual Basic.

Para obtener una introducción general a la muestra, consulta Reversi, un juego de la Tienda Windows en XAML, C# y C++. Para comprender de qué manera varias características trabajan juntas como un todo, consulta Comprender la estructura de la aplicación Reversi. Para obtener información sobre cómo se migró el motor de juego de C# original a C++, consulta Obtener información sobre el motor de juego de Reversi para C++.

Descarga la aplicación de muestra de Reversi o examina el código fuente.

Icono y pantalla de presentación

El icono de la aplicación y la pantalla de presentación son las primeras cosas que el usuario ve. Puedes usarlos para proporcionar un punto de entrada atractivo y para mostrar tu marca. Los más básicos son triviales, pero puedes hacer cosas más complejas como se describe en la documentación.

Recursos clave:

La compatibilidad de Reversi con iconos y pantalla de presentación es básica. Esta incluye iconos cuadrados y anchos y una pantalla de presentación, como se muestra aquí en tamaño reducido.

Iconos y pantalla de presentación de Reversi

Los nombres de archivo de imagen se establecen en el archivo Package.appxmanifest. Puedes proporcionar imágenes a diversas escalas para admitir varios tamaños de pantalla. No es necesaria ninguna otra implementación para este uso simple.

Barra de la aplicación

Las barras proporcionan un lugar estándar para poner comandos de aplicación. De manera predeterminada, los usuarios pueden mostrar u ocultar una barra de aplicación según sea necesario, haciendo de ella un buen lugar para comandos usados con menos frecuencia. Esto ayuda a mantener la interfaz de usuario (UI) principal enfocada en interacciones directas con el contenido.

Recursos clave:

Reversi incluye algunos comandos secundarios aptos para la barra de la aplicación: la posibilidad de pausar el reloj y de deshacer o rehacer movimientos. Durante la reproducción normal del juego, la barra de la aplicación se oculta, pero el usuario puede deslizarla desde arriba o abajo de la pantalla para verla u ocultarla.

Barra de la aplicación de Reversi

Este código, de GamePage.xaml, muestra la definición de la barra de la aplicación. Si bien el fondo y el borde son transparentes, la propiedad Background se establece en {x:Null} para evitar que la barra invisible bloquee las pulsaciones y clics. Esta configuración es necesaria, porque la barra de la aplicación se extiende a lo largo de toda la pantalla y se superpone con la fila inferior del panel de juego.


<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 emplea los controles CommandBar y AppBarButton para obtener el comportamiento y estilo predeterminados. El comportamiento de un botón y su estado habilitado se proporcionan en los comandos de modelo de vista enlazados a las propiedades Command del botón, como se describe en la sección Comandos.

Los botones Reproducir y Pausar funcionan como un único botón de alternancia. Para lograr este efecto, las propiedades Visibility del botón se enlazan a la misma propiedad de modelo de vista. Ambos enlaces usan BooleanToVisibilityConverter, pero uno de ellos también tiene configurada una propiedad ConverterParameter que invierte el efecto del enlace. De esta forma, cada botón es visible solo cuando el otro no lo es. Para obtener información, consulta la sección Enlace de datos.

Notificaciones del sistema

Las notificaciones del sistema alertan a los usuarios cuando se produce un evento importante en la aplicación, aun cuando otra aplicación esté activa.

Recursos clave:

En Reversi, el equipo puede demorar un poco para hacer un movimiento. Si cambias a otra aplicación mientras esperas, una notificación del sistema te avisará cuando sea tu turno.

Notificación del sistema de Reversi

Reversi usa el mínimo código requerido para notificaciones del sistema y establece el campo Capacidad de aviso en , en el diseñador Package.appxmanifest. Dado que el código de las notificaciones del sistema es fácilmente reutilizable, se encuentra en una clase auxiliar, en la carpeta Common.

En GameViewModel.cs:


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


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


Controles flotantes de configuración

El acceso a Configuración proporciona acceso normalizado a la configuración de la aplicación.

Recursos clave:

Reversi tiene dos controles flotantes de Configuración, uno para las opciones de pantalla y otro para las opciones de juego nuevo.

Controles flotantes de configuración de Reversi

Este código de App.xaml.cs muestra la manera en que Reversi controla el evento SettingsPane.CommandsRequested para crear objetos SettingsCommand. Cuando se activa, cada comando crea y muestra un control 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()));
}


Compartir contenido

Con el contrato para contenido compartido, tu aplicación puede compartir datos que los usuarios pueden enviar a otras aplicaciones. Así, por ejemplo, los usuarios pueden compartir datos de tu aplicación en una aplicación de correo electrónico para crear un mensaje.

Recursos clave:

Windows ofrece compatibilidad integrada para compartir una imagen de la aplicación, y Reversi no necesita ninguna función extra.

Enlace de datos

El enlace de datos permite conectar controles de la interfaz de usuario con los datos que muestran. Si se modifica uno, se actualizará el otro. El enlace de datos es común en formularios de entrada de datos, pero también puede usarse para controlar toda la interfaz de usuario y mantenerla separada de la lógica de la aplicación.

Recursos clave:

Reversi usa enlaces de datos para conectar la interfaz de usuario (o capa "vista") con la lógica de la aplicación (o capa "modelo de vista"). Estas capas ayudan a separar la interfaz de usuario de otro código y se conocen como el patrón Model-View-ViewModel (MVVM). Para obtener más información sobre el uso de este patrón en Reversi, consulta Estructura de aplicación de Reversi. Para ver una breve introducción a MVVM, consulta Usar el patrón Model-View-ViewModel.

La mayoría de los enlaces en Reversi se definen en XAML por medio de la extensión de marcado de enlace; aunque, en pocos casos, se usa el código subyacente (por ejemplo, en el archivo Board.xaml.cs). Cada página establece su propiedad DataContext que todos sus elementos usan como origen de datos en los enlaces.

Actualizaciones de la interfaz de usuario

Los enlaces de datos controlan la interfaz de usuario de Reversi. Las interacciones de la interfaz de usuario producen cambios en las propiedades de los orígenes de datos y los enlaces de datos responden a esos cambios actualizando la interfaz de usuario.

Estas actualizaciones funcionan porque las clases de modelo de vista de Reversi heredan la clase BindableBase. Esta clase se encuentra en el archivo Common/BindableBase.cs y ofrece una implementación de INotifyPropertyChanged estándar y unos cuantos métodos de compatibilidad. El método SetProperty actualiza el valor de respaldo de una propiedad, además de la interfaz de usuario asociada, mediante una llamada a un método único. El método OnPropertyChanged actualiza la interfaz de usuario enlazada a las propiedades especificadas. Esto es útil para controlar el intervalo de las actualizaciones y para las propiedades que obtienen valores a partir de otras propiedades.

Este código de GameViewModel.cs muestra el uso básico de SetProperty y OnPropertyChanged.


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


Conversión de valores

Puedes convertir todos los valores de una propiedad para adecuarlos al enlace, creando propiedades calculadas; es decir, propiedades que obtienen sus valores a partir de otras propiedades.

Este código de GameViewModel.cs muestra una propiedad calculada simple. La interfaz de usuario que se encuentra enlazada a esta propiedad se actualiza mediante la llamada OnPropertyChanged coincidente del ejemplo anterior.


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


Las propiedades calculadas son fáciles de crear para cualquier tipo de conversión que necesites, pero tienden a desordenar el código. Para conversiones comunes, recomendamos poner el código de conversión en una implementación IValueConverter reutilizable. Reversi usa las clases NullStateToVisibilityConverter y BooleanToVisibilityConverter en la carpeta Common/Converters para enlaces que muestran y ocultan varios elementos de la interfaz de usuario.

Este enlace de StartPage.xaml muestra u oculta un panel, en función de si una propiedad tiene o no un valor.


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


Este enlace de NewGameSettings.xaml muestra u oculta un panel, en función del estado de un control ToggleSwitch.


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


Para ver más ejemplos, consulta Barra de la aplicación.

Comandos

Los comportamientos de Button, por lo general, se implementan con controladores de evento Click en archivos de código subyacente. Reversi hace esto con los botones de navegación, pero para otros botones, separa el código de botón UI del no UI que el botón invoca. Para ello, las propiedades Button.Command se enlazan a propiedades de modelo de vista que devuelven implementaciones ICommand.

Las propiedades de comandos de Reversi son de tipo DelegateCommand o DelegateCommand<T>. Estas clases están en el archivo Common/DelegateCommand.cs y proporcionan implementaciones ICommand estándar y reutilizables. Puedes usar estas clases para simplificar la creación de comandos de uso único y mantener el código necesario recluido en implementaciones de propiedades únicas.

Este código de GameViewModel.cs muestra el comando de movimiento usado por los espacios de panel, que son botones personalizados. El operador ?? o "fusión nula" significa que el valor del campo se devuelve siempre que no sea null; de lo contrario, se establece el campo y se devuelve un valor nuevo. Esto significa que un objeto de comando único se crea la primera vez que se accede a una propiedad y el mismo objeto se reutiliza para todos los accesos futuros. El objeto de comando se inicializa llamando al método DelegateCommand<ISpace>.FromAsyncHandler con referencias a los métodos MoveAsync y CanMove. Estos métodos proporcionan la implementación para los métodos ICommand.Execute y CanExecute.


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


El enlace de datos llama al método CanExecute para actualizar el estado habilitado del botón. Sin embargo, los enlaces de comandos dependen de la notificación de cambio similar a la de otros enlaces (explicado en Actualizaciones de la interfaz de usuario). Este código de GameViewModel.cs muestra la manera en que el método UpdateView sincroniza el estado de modelo de vista con el estado de modelo y después llama a OnCanExecuteChanged para cada comando, antes de continuar con el siguiente movimiento.


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


Propiedades de dependencia personalizadas

Reversi usa propiedades de dependencia personalizadas en sus controles personalizados para poder usar actualizaciones de enlaces de datos y realizar cambios estados visuales. Los estados visuales y las transiciones animadas se definen en XAML mediante la clase VisualStateManager. Pero no hay forma de enlazar un estado visual directamente a una propiedad de modelo de vista. Las propiedades de dependencia personalizas proporcionan destinos para enlazar a propiedades de vista de modelo. Las propiedades de dependencia incluyen devoluciones de llamadas de propiedad cambiada que hacen las llamadas necesarias al método VisualStateManager.GoToState.

Este código muestra la manera en que el control PlayerStatus usa código subyacente, para enlazar sus propiedades de dependencia personalizadas a propiedades de modelo de vista. Aquí solo se muestra una de las propiedades de dependencia, incluido el método de la devolución de llamada de propiedad cambiada. La devolución de llamada y la invalidación de método OnApplyTemplate llaman al método de actualización. Sin embargo, la llamada OnApplyTemplate inicializa el control para su primera aparición en pantalla, por lo cual no usa transiciones 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 asincrónico

El código asincrónico permite que la interfaz de usuario siga respondiendo mientras la aplicación permanece ocupada en operaciones lentas.

Recursos clave:

Reversi utiliza código asincrónico para realizar movimientos en un juego. Cada movimiento tarda al menos un segundo en completarse, incluida la animación del movimiento, y los movimientos de inteligencia artificial pueden tardar mucho más. Sin embargo, la interfaz de usuario sigue respondiendo en todo momento y los comandos de usuario (por ejemplo, deshacer) cancelarán un movimiento en curso.

Este código de GameViewModel.cs muestra el modo en que Reversi utiliza las palabras clave async y await, la clase Task y los tokens de cancelación. Ten en cuenta el uso de AsTask para integrarse con el código asincrónico de Windows en tiempo de ejecución en la clase Game. (Para obtener más información, consulta la sección siguiente.)


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


Uso de componentes de Windows en tiempo de ejecución

Implementar parte del código como un componente de Windows en tiempo de ejecución te permite reutilizar dicho código en distintas aplicaciones, en distintas plataformas o con diferentes lenguajes. También puedes reemplazar con más facilidad el componente con una implementación alternativa en otro lenguaje.

Recurso clave:

Reversi implementa su lógica principal del juego como un componente de Windows en tiempo de ejecución para desacoplarla completamente de la aplicación. Esto permite la reutilización de código y la extensibilidad futura. Reversi también incluye una versión para C++ del motor de juego como una alternativa de mayor rendimiento a la versión C# original. Para obtener más información, consulta Obtener información sobre el motor de juego de Reversi para C++.

Este código de Game.cs muestra el modo en que Reversi utiliza código asincrónico basado en Task (incluidas las palabras clave async y await) pero expone los resultados a través de interfaces asincrónicas de Windows en tiempo de ejecución. También muestra cómo la clase Game consume el token de cancelación del código GameViewModel.

El primero y el tercer método en el código de ejemplo llama al método AsyncInfo.Run para devolver IAsyncOperation<T>. De este modo, se encapsula el valor devuelto de la tarea y se habilita la cancelación. En el segundo ejemplo se llama al método WindowsRuntimeSystemExtensions.AsAsyncAction para devolver IAsyncAction. Esto es útil en las tareas que no tienen valores devueltos y que no requieren cancelación alguna.


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


Temas relacionados

Aplicación de muestra de Reversi
Reversi, un juego de la Tienda Windows en XAML, C# y C++
Usar el patrón Model-View-ViewModel (MVVM)
Aprender cómo la muestra de Reversi usa características de aplicaciones de la Tienda Windows
Comprender la estructura de la aplicación Reversi
Obtener información sobre el motor de juego de Reversi para C++
Crear la primera aplicación de la Tienda Windows con C# o Visual Basic
Guía básica para crear aplicaciones de Windows en tiempo de ejecución con C# o Visual Basic
Enlace de datos

 

 

Mostrar:
© 2014 Microsoft