Como crear y/o utilizar el Charm de Settings en WinRT | C# | XAML

El charm de setting es una de esas cosas que en mi opinión aún están por afinar en WinRT, es tan necesario y común su forma de uso que siempre me pregunto porque no viene el problema ya resuelto.

Muchos de ustedes, y me incluyo, seguro que han tratado de usar el settings charm buscando algún componente ya creado para mostrar una nueva ventana y resulta que simplemente no existe.

He creado mi propia solución al respecto y estoy seguro que les será de mucha ayuda.

Analizando el Charm de Settings

Revisemos la implementación del settings charm
Settings Charm

  1. Espacio disponible en el charm para colocar opciones
  2. Settings genéricos que aplican a todo el sistema
  3. Settings propios para cada aplicación

En la parte 1 podemos colocar las opciones que necesitamos para nuestra aplicación, pero ATENCION son solo las opciones de menú y cada una de ellas es responsable de hacer todo lo necesario para que funcione.

Esto implica llamar a una página web , desplegar una ventana en nuestra App o hacer lo que le indiquemos.

Sin embargo la mayoría de las implementaciones dan un uso recurrente a cada una de estas opciones, y es el de desplegar otra ventana muy parecida a la de arriba pero mostrando en detalle lo pertinente a la opción seleccionada, por ejemplo veamos que se muestra en el menú anterior al acceder a la opción "Tiles"

Custom Settings Charm

  1. Ventana desplegada, parece obvio pero no están fácil darse cuenta a la primera, es simplemente otra ventana.
  2. Botón regresar, a Dónde? al settings charm, actualmente ya estamos en una ventana diferente
  3. Formulario personalizado

Diseñando nuestra funcionalidad del Settings Charm

Con base a lo visto anteriormente podemos aterrizar mejor la idea de lo que se necesita para hacer uso del una opción dentro del settings Charm

  1. El nombre de la opción
  2. Una ventana para mostrar un formulario
  3. Un formulario

Let's do it.

El nombre de la opción

Resulta que las opciones que se pueden colocar en el Charm pueden configurarse a nivel de Page, es decir cada Page que tenga nuestra App bien podría mostrar un conjunto de opciones diferentes.

Hay que tener presente este detalle en adelante, no solo por orden sino porque es parte fundamental de la implementación.

Para efectos de este artículo, la opción tendrá como nombre: "Configuraciones de JuanK".

Como cada Page tiene sus propios Settings, debemos crear una referencia al SettingsPane para abrirlo, el SettingsPane como supondrás no es más que lo que se muestra al acceder al Settings Charm.

Para lo único que necesitamos una referencia al SettingsPane es para agregar comandos ( opciones ), y esto es algo que no se puede hacer en todo momento. El SettingsPane solo deja establecer estas opciones en un momento específico y nos lo deja saber cuando se lanza el evento CommandsRequested.

Así que en el evento Loaded del Page obtenemos una referencia al SettingsPane específico para esta 'Vista' y una vez lo podemos acceder asignamos el manejador del evento:

SettingsPane.GetForCurrentView().CommandsRequested += MainPage_CommandsRequested;

Y creamos el esqueleto del método MainPage_CommandsRequested

void MainPage_CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)  
{
}

Es en este método donde crearemos el nuevo comando, sabemos que el evento puede dispararse más de una vez por ende lo primero que hacemos es eliminar cualquier comando adicionado con anterioridad y esto lo hacemos así

args.Request.ApplicationCommands.Clear();

Ahora si creamos el comando, el cual es un objeto de tipo SettingsCommand el cual recibe los siguientes parámetros

  • Id del comando, cualquier objeto que nos sirva como Id, yo usare una cadena
  • Texto del label
  • Un método (o event handler como quieras llamarlo) con las instrucciones que se ejecutarán al seleccionar la opción.

El event handler por simplicidad lo implemento usando una expresión lambda, lo cual me permite implementar un método inline.

var jkCommand = new SettingsCommand("jkConfig", "Configuraciones de JuanK",  
                                    (handler) =>
                                    {

                                    });

Una vez creado el comando se adiciona a la lista de comandos así:

args.Request.ApplicationCommands.Add(jkCommand);

Al ejecutarlo este es el resultado, desde luego al utilizar la opción no pasa nada porque aparte de mostrar la opción no hemos hecho nada más.

Settings Charm

código completo:

private async void pageRoot_Loaded_1(object sender, RoutedEventArgs e)  
{
    SettingsPane.GetForCurrentView().CommandsRequested += MainPage_CommandsRequested;
}

void MainPage_CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)  
{
    args.Request.ApplicationCommands.Clear();
    var jkCommand = new SettingsCommand("jkConfig", "Configuraciones de JuanK",
                                        (handler) =>
                                        {

                                        });

    args.Request.ApplicationCommands.Add(jkCommand);
}

Una ventana para mostrar un formulario

Hemos creado una opción, ahora hay que hacerla funcionar, para ello necesitamos crear una ventana que se comporte de manera similar a como lo hace el SettingsPane en cuanto a posición, composición y tamaño.

Hay muchos controles XAML que podemos utilizar para tal fin, pero un factor decisivo es que esta ventana debe parecer flotar sobre las demás.

Esto lo podemos hacer fácilmente utilizando un objeto Popup, fíjense en que he dicho objeto y no control. Popup no es un control es un objeto que nos permite mostrar un control cualquiera de manera 'flotante' sobre la ventana principal, pero en si Popup no es visible solo lo es el control que se le indica que muestre.

Haremos, de manera breve, algo de diseño. Hay que crear un componente que encapsule la lógica del Popup para que nos permita hacer lo siguiente

  • Mostrar un control cualquiera
  • Modificar el ancho de la ventana mostrada
  • Controlar que cosas hacer al salir de la ventana ( cosas como llamar otras ventanas por ejemplo )

Creamos una clase llamada SettingsWindowHelper y definimos los siguientes elementos

public class SettingsWindowHelper  
{
    /// <summary>Default Window Size</summary>
    public const double DEFAULT_WIDTH = 346;
    /// <summary>Allow to show any control floating over the main Window</summary>
    private Popup _popup;
    /// <summary>Delegate to execute when Popup is closed</summary>
    Action CloseAction;


    public SettingsWindowHelper()
    {
        _popup = new Popup();
        _popup.IsLightDismissEnabled = true;
        _popup.Closed += OnPopupClosed;
    }
}

Cada campo creado tiene la descripción apropiada, en el constructor inicializamos una nueva instancia del Popup y asignamos la propiedad IsLightDismissEnabled la cual permite que el Popup se cierre de manera automática el perder el foco.

Seguidamente le indicamos al Popup que al cerrarse ejecute el método OnPopupClosed el cual implementamos a continuación.

/// <summary>
/// Raise when internal Popup object is closed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void OnPopupClosed(object sender, object e)  
{
    if (CloseAction != null)
        CloseAction.Invoke();
}

Básicamente cuando se le ha asignado valor a CloseAction este (un objeto tipo Action) es ejecutado, es decir se ejecuta la acción indicada cuando se cierra la aplicación. Por el momento CloseAction siempre es null, pero veremos a continuación como cambiar eso.

Hasta el momento nuestra clase SettingsWindowHelper funciona de manera genérica independiente de cualquier uso que se le de.

Ya teniendo la infraestructura montada solo resta mostrar lo que queremos.

Para ello creamos un método ShowFlyout el cual recibe los siguientes parámetros

  • Control que se debe mostrar
  • Acción a realizar cuando se cierre la ventana de settings, por defecto ninguna
  • Ancho de la ventana de settings, por defecto 346 el ancho del SettingsPane inicial.

La idea es que el control de usuario ocupe siempre todo el alto de la pantalla y un ancho determinado, siendo el ancho por defecto sea el mismo ancho del SettingsPane (346) de tal manera que cuando se pase del SettingsPane a la ventana de settings no hayan cambios bruscos de forma .

También debemos tener en cuenta que control debe posicionarse a la derecha de la pantalla, no en el centro ni a la izquierda sino que de manera esperada por el usuario este control se dibuje justo donde aparece el SettingsPane, para ello se debe calcular que sea cual sea el ancho del control su borde derecho siempre finalice en el borde derecho de la ventana.

Al final, cambiando la propiedad IsOpen a true, se abre el Popup, es decir se muestra el control que contiene de acuerdo a los parámetros establecidos.

/// <summary>
/// Shows an user control over the current Window
/// </summary>
/// <param name="control">User control to show as settings window</param>
/// <param name="closeAction">Method to execute when Popup is closed</param>
/// <param name="width">Window Width</param>
public void ShowFlyout(UserControl control, Action closeAction = null,  
                        double width = DEFAULT_WIDTH)
{
    //Asignar acción a ejecutar al cerrar el Popup
    CloseAction = closeAction;

    //Asignar ancho y alto del Popup
    _popup.Width = width;
    _popup.Height = Window.Current.Bounds.Height;

    /* Asignar el ancho y alto del control
        * Aunque este puede ya traerlos definidos
        * en este caso es conveniente adecuarlo a 
        * la estructura que hemos planteado*/
    control.Width = width;
    control.Height = Window.Current.Bounds.Height;

    //Asignar el control del parámetro al Popup
    _popup.Child = control;

    //Establecer en que parte de la ventana se comienza a dibujar el Popup
    _popup.VerticalOffset = 0;
    _popup.HorizontalOffset = Window.Current.Bounds.Width - width;

    //Mostrar el Popup, sus contenidos
    _popup.IsOpen = true;
}

Eso es todo ya tenemos lista nuestra funcionalidad en la clase SettingsWindowHelper, ahora hay que someterla a prueba creando un formulario.

Un formulario

Desde Visual Studio creamos un nuevo ítem de tipo UserControl

Visual Studio Adding UserControl

Dentro del XAML de este control nos aseguramos de reemplazar el Grid que viene por defecto con el siguiente código

<StackPanel Style="{StaticResource LayoutRootStyle}">  
    <Button Style="{StaticResource BackButtonStyle}" Click="Button_Click_1" />
    <TextBlock HorizontalAlignment="Center" 
                TextWrapping="Wrap" Text="Test de JuanK" 
                FontSize="55"
                />
</StackPanel>  

Lo cual nos trae el siguiente resultado visual:

Back Button and TextBlock

Si por alguna razón el botón no te sale de esa forma, intenta primero agregar a la solución un nuevo ítem de tipo BasicPage eso creará por si solo los estilos que te hacen falta.

Abriendo el archivo UserControlTest.xaml.cs adiciona/modifica el siguiente método para que este haga que al presionar clic en el botón de la flecha regresemos al SettingsPane

private void Button_Click_1(object sender, RoutedEventArgs e)  
{
    //Referenciar el Popup que es el control padre de este user control
    var pop = this.Parent as Popup;

    //Si elpadre es en efecto un Popup cerrarlo
    if (pop != null)
        pop.IsOpen = false;

    //Mostrar el SettingsPane
    SettingsPane.Show();
}

Ya tenemos listo el control que vamos a mostrar, integremos.

Regresando al Page donde creamos el nuevo comando, adicionamos código al handler del comando para crear un nuevo objeto SettingsWindowHelper al cual le llamaremos el método ShowFlyout con un único parámetro indicando el control que deseamos mostrar:

void MainPage_CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)  
{
    args.Request.ApplicationCommands.Clear();
    var jkCommand = new SettingsCommand("jkConfig", "Configuraciones de JuanK",
                                        (handler) =>
                                        {
                                            var settingsHelper = new SettingsWindowHelper();
                                            settingsHelper.ShowFlyout(new UserControlTest());

                                        });

    args.Request.ApplicationCommands.Add(jkCommand);
}

El resultado:

Custom Settings Charm

Espero sus comentarios y no duden en compartirlo ;)

Comparte este artículo

comments powered by Disqus