Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Muchas aplicaciones contienen colecciones de contenido en forma de listas, cuadrículas y árboles que los usuarios pueden manipular. Por ejemplo, es posible que los usuarios puedan eliminar, cambiar el nombre, marcar o actualizar elementos. En este artículo se muestra cómo usar comandos contextuales para implementar este tipo de acciones de una manera que proporcione la mejor experiencia posible para todos los tipos de entrada.
API importantes: interfaz ICommand, propiedad UIElement.ContextFlyout, interfaz INotifyPropertyChanged
Creación de comandos para todos los tipos de entrada
Dado que los usuarios pueden interactuar con una aplicación de Windows con una amplia gama de dispositivos y entradas, la aplicación debe exponer comandos mediante menús contextuales independientes del tipo de entrada y aceleradores específicos de entrada. La inclusión de ambos permite al usuario invocar rápidamente comandos en el contenido, independientemente de la entrada o el tipo de dispositivo.
En esta tabla se muestran algunos comandos de colección típicos y formas de exponer esos comandos.
| Command | Independiente de la entrada | Acelerador del mouse | Acelerador de teclado | Acelerador táctil |
|---|---|---|---|---|
| Eliminar elemento | Menú contextual | Botón flotante | Clave DEL | Deslizar el dedo para eliminar |
| Elemento de marca | Menú contextual | Botón flotante | Ctrl+Mayús+G | Deslizar el dedo para marcar |
| Actualizar datos | Menú contextual | N/A | Tecla F5 | Deslizar para actualizar |
| Marcar un elemento como favorito | Menú contextual | Botón flotante | F, Ctrl+S | Desliza para marcar como favorito |
En general, debe hacer que todos los comandos de un elemento estén disponibles en el menú contextual del elemento. Los menús contextuales son accesibles para los usuarios independientemente del tipo de entrada y deben contener todos los comandos contextuales que el usuario puede realizar.
Para los comandos a los que se accede con frecuencia, considere la posibilidad de usar aceleradores de entrada. Los aceleradores de entrada permiten al usuario realizar acciones rápidamente, en función de su dispositivo de entrada. Entre los aceleradores de entrada se incluyen:
- Deslizar para acción (acelerador táctil)
- Deslizar para actualizar datos (acelerador táctil)
- Métodos abreviados de teclado (acelerador de teclado)
- Teclas de acceso (acelerador de teclado)
- Botones de ratón y pluma al pasar el puntero (acelerador de punteros)
Nota:
Los usuarios deben poder acceder a todos los comandos desde cualquier tipo de dispositivo. Por ejemplo, si los comandos de la aplicación solo se exponen a través de aceleradores de puntero de botón al pasar el cursor, los usuarios de pantallas táctiles no podrán acceder a ellos. Como mínimo, use un menú contextual para proporcionar acceso a todos los comandos.
Ejemplo: El modelo de datos PodcastObject
Para demostrar nuestras sólidas recomendaciones, en este artículo se elabora una lista de podcasts para una aplicación de podcasts. En el código de ejemplo se muestra cómo permitir al usuario marcar como favorito un podcast determinado de una lista.
Esta es la definición del objeto podcast con el que trabajaremos:
public class PodcastObject : INotifyPropertyChanged
{
// The title of the podcast
public String Title { get; set; }
// The podcast's description
public String Description { get; set; }
// Describes if the user has set this podcast as a favorite
public bool IsFavorite
{
get
{
return _isFavorite;
}
set
{
_isFavorite = value;
OnPropertyChanged("IsFavorite");
}
}
private bool _isFavorite = false;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
Observe que PodcastObject implementa INotifyPropertyChanged para responder a los cambios de propiedad cuando el usuario alterna la propiedad IsFavorite.
Definición de comandos con la interfaz ICommand
La interfaz ICommand le ayuda a definir un comando que está disponible para varios tipos de entrada. Por ejemplo, en lugar de escribir el mismo código para un comando delete en dos controladores de eventos diferentes, uno para cuando el usuario presiona la tecla Eliminar y otro para cuando el usuario hace clic con el botón derecho en "Eliminar" en un menú contextual, puede implementar la lógica de eliminación una vez, como un ICommand, y luego hacer que esté disponible para diferentes tipos de entrada.
Es necesario definir el ICommand que representa la acción "Favorito". Usaremos el método Execute del comando para marcar como favorito un podcast. El podcast concreto se proporcionará al método execute a través del parámetro del comando, que se puede enlazar mediante la propiedad CommandParameter.
public class FavoriteCommand: ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
// Perform the logic to "favorite" an item.
(parameter as PodcastObject).IsFavorite = true;
}
}
Para usar el mismo comando con varias colecciones y elementos, puede almacenar el comando como un recurso en la página o en la aplicación.
<Application.Resources>
<local:FavoriteCommand x:Key="favoriteCommand" />
</Application.Resources>
Para ejecutar el comando, llame a su método Execute .
// Favorite the item using the defined command
var favoriteCommand = Application.Current.Resources["favoriteCommand"] as ICommand;
favoriteCommand.Execute(PodcastObject);
Crear un UserControl para responder a una variedad de entradas
Cuando tiene una lista de elementos y cada uno de esos elementos debe responder a varias entradas, puede simplificar el código definiendo un UserControl para el elemento y usándolo para definir los controladores de eventos y menú contextuales de los elementos.
Para crear un UserControl en Visual Studio:
- En el Explorador de soluciones, haga clic con el botón derecho en el proyecto. Aparecerá un menú contextual.
- Seleccione Agregar > nuevo elemento...
Aparece el cuadro de diálogo Agregar nuevo elemento . - Seleccione UserControl en la lista de elementos. Asígnele el nombre que desee y haga clic en Agregar. Visual Studio generará un código auxiliar UserControl automáticamente.
En nuestro ejemplo de podcast, cada podcast se mostrará en una lista, que ofrecerá una variedad de formas de marcar un podcast como favorito. El usuario podrá realizar las siguientes acciones para marcar el podcast como "Favorito":
- Invocar un menú contextual
- Realizar métodos abreviados de teclado
- Mostrar un botón flotante
- Realizar un gesto de deslizar el dedo
Para encapsular estos comportamientos y usar FavoriteCommand, vamos a crear un nuevo UserControl denominado "PodcastUserControl" para representar un podcast en la lista.
PodcastUserControl muestra los campos de PodcastObject como TextBlocks y responde a diversas interacciones del usuario. En este artículo se hará referencia y se expandirá sobre PodcastUserControl.
PodcastUserControl.xaml
<UserControl
x:Class="ContextCommanding.PodcastUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
IsTabStop="True" UseSystemFocusVisuals="True"
>
<Grid Margin="12,0,12,0">
<StackPanel>
<TextBlock Text="{x:Bind PodcastObject.Title, Mode=OneWay}" Style="{StaticResource TitleTextBlockStyle}" />
<TextBlock Text="{x:Bind PodcastObject.Description, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}" />
<TextBlock Text="{x:Bind PodcastObject.IsFavorite, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}"/>
</StackPanel>
</Grid>
</UserControl>
PodcastUserControl.xaml.cs
public sealed partial class PodcastUserControl : UserControl
{
public static readonly DependencyProperty PodcastObjectProperty =
DependencyProperty.Register(
"PodcastObject",
typeof(PodcastObject),
typeof(PodcastUserControl),
new PropertyMetadata(null));
public PodcastObject PodcastObject
{
get { return (PodcastObject)GetValue(PodcastObjectProperty); }
set { SetValue(PodcastObjectProperty, value); }
}
public PodcastUserControl()
{
this.InitializeComponent();
// TODO: We will add event handlers here.
}
}
Observe que PodcastUserControl mantiene una referencia a PodcastObject como DependencyProperty. Esto nos permite enlazar PodcastObjects al PodcastUserControl.
Después de haber generado algunos PodcastObjects, puede crear una lista de podcasts mediante el enlace de PodcastObjects a un ListView. Los objetos PodcastUserControl describen la visualización de PodcastObjects y, por tanto, se establecen mediante ItemTemplate de ListView.
MainPage.xaml
<ListView x:Name="ListOfPodcasts"
ItemsSource="{x:Bind podcasts}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:PodcastObject">
<local:PodcastUserControl PodcastObject="{x:Bind Mode=OneWay}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<!-- The PodcastUserControl will entirely fill the ListView item and handle tabbing within itself. -->
<Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemRevealStyle}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="0"/>
<Setter Property="IsTabStop" Value="False"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Crear menús contextuales
Los menús contextuales muestran una lista de comandos o opciones cuando el usuario los solicita. Los menús contextuales proporcionan comandos contextuales relacionados con su elemento adjunto y, por lo general, se reservan para acciones secundarias específicas de ese elemento.
El usuario puede invocar menús contextuales mediante estas "acciones de contexto":
| Input | Acción de contexto |
|---|---|
| Ratón | Haga clic con el botón derecho |
| Teclado | Mayús+F10, botón menú |
| Toque | Pulsación larga en el elemento |
| Lápiz | Pulsación del botón tipo barril, pulsación larga en el elemento |
| Controlador para juegos | Botón de menú |
Dado que el usuario puede abrir un menú contextual independientemente del tipo de entrada, el menú contextual debe contener todos los comandos contextuales disponibles para el elemento de lista.
ContextFlyout
La propiedad ContextFlyout, definida por la clase UIElement, facilita la creación de un menú contextual que funcione con todos los tipos de entrada. Proporcionas un control flotante que representa el menú contextual mediante el MenuFlyout o el CommandBarFlyout, y cuando el usuario realiza una "acción de contexto" como se define anteriormente, se mostrará el MenuFlyout o el CommandBarFlyout correspondiente al elemento.
Consulte menús y menús contextuales para obtener ayuda en la identificación de escenarios de menús frente a menús contextuales y las instrucciones sobre cuándo usar el desplegable de menú frente al desplegable de la barra de comandos.
En este ejemplo, usaremos MenuFlyout y empezaremos agregando contextFlyout al PodcastUserControl. MenuFlyout especificado como ContextFlyout contiene un solo elemento para marcar como favorito un podcast. Observe que este MenuFlyoutItem usa el elemento favoriteCommand definido anteriormente, con el CommandParameter enlazado al PodcastObject.
PodcastUserControl.xaml
<UserControl>
<UserControl.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Favorite" Command="{StaticResource favoriteCommand}" CommandParameter="{x:Bind PodcastObject, Mode=OneWay}" />
</MenuFlyout>
</UserControl.ContextFlyout>
<Grid Margin="12,0,12,0">
<!-- ... -->
</Grid>
</UserControl>
Tenga en cuenta que también puede usar el evento ContextRequested para responder a las acciones de contexto. El evento ContextRequested no se activará si se ha especificado contextFlyout.
Creación de aceleradores de entrada
Aunque cada elemento de la colección debe tener un menú contextual que contenga todos los comandos contextuales, es posible que desee permitir que los usuarios realicen rápidamente un conjunto más pequeño de comandos que se realizan con frecuencia. Por ejemplo, una aplicación de correo puede tener comandos secundarios como Reply, Archive, Move to Folder, Set Flag y Delete que aparecen en un menú contextual, pero los comandos más comunes son Delete y Flag. Después de identificar qué comandos son más comunes, puede usar aceleradores basados en entrada para que estos comandos sean más fáciles de realizar por un usuario.
En la aplicación podcast, el comando que se realiza con frecuencia es el comando "Favorito".
Aceleradores de teclado
Accesos directos y manejo directo de teclas
Dependiendo del tipo de contenido, puede identificar determinadas combinaciones de teclas que deben realizar una acción. En una aplicación de correo electrónico, por ejemplo, se puede usar la clave DEL para eliminar el correo electrónico seleccionado. En una aplicación de podcast, las teclas Ctrl+S o F podrían marcar un podcast como favorito para escuchar más tarde. Aunque algunos comandos tienen métodos abreviados de teclado conocidos y comunes, como DEL para eliminar, otros comandos tienen métodos abreviados específicos de la aplicación o del dominio. Use atajos conocidos si es posible, o considere proporcionar texto de recordatorio en un tooltip para enseñar al usuario sobre el atajo.
La aplicación puede responder cuando el usuario presiona una tecla mediante el evento KeyDown . En general, los usuarios esperan que la aplicación responda cuando presione la tecla por primera vez, en lugar de esperar hasta que suelte la tecla.
En este ejemplo se explica cómo agregar el controlador KeyDown al PodcastUserControl a un podcast favorito cuando el usuario presiona Ctrl+S o F. Usa el mismo comando que antes.
PodcastUserControl.xaml.cs
// Respond to the F and Ctrl+S keys to favorite the focused item.
protected override void OnKeyDown(KeyRoutedEventArgs e)
{
var ctrlState = CoreWindow.GetForCurrentThread().GetKeyState(VirtualKey.Control);
var isCtrlPressed = (ctrlState & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down || (ctrlState & CoreVirtualKeyStates.Locked) == CoreVirtualKeyStates.Locked;
if (e.Key == Windows.System.VirtualKey.F || (e.Key == Windows.System.VirtualKey.S && isCtrlPressed))
{
// Favorite the item using the defined command
var favoriteCommand = Application.Current.Resources["favoriteCommand"] as ICommand;
favoriteCommand.Execute(PodcastObject);
}
}
Aceleradores de mouse
Los usuarios están familiarizados con los menús contextuales con el botón derecho, pero es posible que desee permitir a los usuarios realizar comandos comunes con un solo clic del mouse. Para habilitar esta experiencia, puede incluir botones dedicados en el lienzo del elemento de colección. Para permitir que los usuarios actúen rápidamente con el mouse y para minimizar el desorden visual, puede elegir mostrar solo estos botones cuando el usuario tiene su puntero dentro de un elemento de lista determinado.
En este ejemplo, el comando Favorite se representa mediante un botón definido directamente en PodcastUserControl. Tenga en cuenta que el botón de este ejemplo usa el mismo comando, FavoriteCommand, que antes. Para alternar la visibilidad de este botón, puede usar VisualStateManager para cambiar entre estados visuales cuando el puntero entra y sale del control.
PodcastUserControl.xaml
<UserControl>
<UserControl.ContextFlyout>
<!-- ... -->
</UserControl.ContextFlyout>
<Grid Margin="12,0,12,0">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="HoveringStates">
<VisualState x:Name="HoverButtonsShown">
<VisualState.Setters>
<Setter Target="hoverArea.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="HoverButtonsHidden" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="{x:Bind PodcastObject.Title, Mode=OneWay}" Style="{StaticResource TitleTextBlockStyle}" />
<TextBlock Text="{x:Bind PodcastObject.Description, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}" />
<TextBlock Text="{x:Bind PodcastObject.IsFavorite, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}"/>
</StackPanel>
<Grid Grid.Column="1" x:Name="hoverArea" Visibility="Collapsed" VerticalAlignment="Stretch">
<AppBarButton Icon="OutlineStar" Label="Favorite" Command="{StaticResource favoriteCommand}" CommandParameter="{x:Bind PodcastObject, Mode=OneWay}" IsTabStop="False" VerticalAlignment="Stretch" />
</Grid>
</Grid>
</UserControl>
Los botones de desplazamiento deben aparecer y desaparecer cuando el mouse entra y sale del elemento. Para responder a los eventos del mouse, puede usar los eventos PointerEntered y PointerExited en podcastUserControl.
PodcastUserControl.xaml.cs
protected override void OnPointerEntered(PointerRoutedEventArgs e)
{
base.OnPointerEntered(e);
// Only show hover buttons when the user is using mouse or pen.
if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse || e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Pen)
{
VisualStateManager.GoToState(this, "HoverButtonsShown", true);
}
}
protected override void OnPointerExited(PointerRoutedEventArgs e)
{
base.OnPointerExited(e);
VisualStateManager.GoToState(this, "HoverButtonsHidden", true);
}
Los botones que se muestran en el estado de desplazamiento solo serán accesibles a través del tipo de entrada de puntero. Dado que estos botones están limitados a la entrada de puntero, puede optar por minimizar o quitar el relleno alrededor del icono del botón para optimizar la entrada del puntero. Si decide hacerlo, asegúrese de que la superficie del botón sea al menos 20x20px para permanecer utilizable con el lápiz y el mouse.
Aceleradores táctiles
Swipe
El comando de deslizamiento es un acelerador táctil que permite a los usuarios de dispositivos táctiles realizar acciones secundarias comunes. Deslizar el dedo permite a los usuarios táctiles interactuar de forma rápida y natural con el contenido, mediante acciones comunes como Deslizar para eliminar o Deslizar para invocar. Consulte el artículo de comandos de deslizar el dedo para obtener más información.
Para integrar el deslizar en su colección, necesita dos componentes: SwipeItems, que aloja los comandos; y un SwipeControl, que envuelve el elemento y permite la interacción mediante deslizamiento.
Los SwipeItems se pueden definir como un recurso en PodcastUserControl. En este ejemplo, SwipeItems contiene un comando para favoritar un elemento.
<UserControl.Resources>
<SymbolIconSource x:Key="FavoriteIcon" Symbol="Favorite"/>
<SwipeItems x:Key="RevealOtherCommands" Mode="Reveal">
<SwipeItem IconSource="{StaticResource FavoriteIcon}" Text="Favorite" Background="Yellow" Invoked="SwipeItem_Invoked"/>
</SwipeItems>
</UserControl.Resources>
SwipeControl ajusta el elemento y permite al usuario interactuar con él mediante el gesto de deslizar el dedo. Observe que SwipeControl contiene una referencia a SwipeItems como sus RightItems. El elemento Favorito se mostrará cuando el usuario deslice el dedo de derecha a izquierda.
<SwipeControl x:Name="swipeContainer" RightItems="{StaticResource RevealOtherCommands}">
<!-- The visual state groups moved from the Grid to the SwipeControl, since the SwipeControl wraps the Grid. -->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="HoveringStates">
<VisualState x:Name="HoverButtonsShown">
<VisualState.Setters>
<Setter Target="hoverArea.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="HoverButtonsHidden" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Margin="12,0,12,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="{x:Bind PodcastObject.Title, Mode=OneWay}" Style="{StaticResource TitleTextBlockStyle}" />
<TextBlock Text="{x:Bind PodcastObject.Description, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}" />
<TextBlock Text="{x:Bind PodcastObject.IsFavorite, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}"/>
</StackPanel>
<Grid Grid.Column="1" x:Name="hoverArea" Visibility="Collapsed" VerticalAlignment="Stretch">
<AppBarButton Icon="OutlineStar" Command="{StaticResource favoriteCommand}" CommandParameter="{x:Bind PodcastObject, Mode=OneWay}" IsTabStop="False" LabelPosition="Collapsed" VerticalAlignment="Stretch" />
</Grid>
</Grid>
</SwipeControl>
Cuando el usuario desliza el dedo para invocar el comando Favorito, se llama al método Invoked.
private void SwipeItem_Invoked(SwipeItem sender, SwipeItemInvokedEventArgs args)
{
// Favorite the item using the defined command
var favoriteCommand = Application.Current.Resources["favoriteCommand"] as ICommand;
favoriteCommand.Execute(PodcastObject);
}
Deslizar para actualizar
La función de deslizar para actualizar permite que un usuario deslice hacia abajo en una colección de datos mediante el tacto para recuperar más datos. Consulte el artículo deslizar para actualizar para obtener más información.
Aceleradores de lápiz
El tipo de entrada del lápiz proporciona la precisión de la entrada de puntero. Los usuarios pueden realizar acciones comunes, como abrir menús contextuales mediante aceleradores basados en lápiz. Para abrir un menú contextual, los usuarios pueden pulsar la pantalla con el botón de barril presionado, o presionar largamente el contenido. Los usuarios también pueden usar el lápiz para mantener el puntero sobre el contenido para comprender mejor la interfaz de usuario, como mostrar información sobre herramientas, o para mostrar acciones de desplazamiento secundarias, similares al mouse.
Para optimizar tu aplicación para la entrada por lápiz, consulta el artículo de interacción de bolígrafo y lápiz .
Recommendations
- Asegúrese de que los usuarios pueden acceder a todos los comandos desde todos los tipos de dispositivos Windows.
- Incluya un menú contextual que proporcione acceso a todos los comandos disponibles para un elemento de colección.
- Proporcione aceleradores de entrada para comandos usados con frecuencia.
- Use la interfaz ICommand para implementar comandos.