Información general sobre comandos

El comando es un mecanismo de entrada en Windows Presentation Foundation (WPF) que proporciona un control de entrada en un nivel más semántico que la entrada del dispositivo. Algunos ejemplos de comandos son las operaciones Copiar, Cortar y Pegar presentes en numerosas aplicaciones.

Esta información general define los comandos que se encuentran en WPF, las clases que forman parte del modelo de comandos, y el procedimiento para usar y crear comandos en las aplicaciones.

Este tema contiene las siguientes secciones:

¿Qué son los comandos?

Los comandos tienen varios propósitos. El primer propósito es separar la semántica y el objeto que invoca un comando de la lógica que ejecuta el comando. Esto permite que varios orígenes dispares invoquen la misma lógica de comando, así como personalizar la lógica de comando para distintos destinos. Por ejemplo, las operaciones de edición Copiar, Cortar y Pegar, que se encuentran en muchas aplicaciones, se pueden invocar mediante acciones de usuario diferentes si se implementan mediante comandos. Una aplicación podría permitir a un usuario cortar texto u objetos seleccionados mediante un clic en un botón, la selección de un elemento en un menú o una combinación de teclas, como CTRL+X. El uso de comandos permite enlazar cada tipo de acción del usuario a la misma lógica.

Otra finalidad de los comandos es indicar si una acción está disponible. Para continuar con el ejemplo del corte de un objeto o de texto, la acción solo tiene sentido si se selecciona algún elemento. Si un usuario intenta cortar un objeto o texto sin tener nada seleccionado, no ocurrirá nada. Para indicárselo al usuario, muchas aplicaciones deshabilitan los botones y elementos de menú para indicar si es posible realizar una acción. Un comando puede indicar si una acción es posible mediante la implementación del método CanExecute. Un botón se puede suscribir al evento CanExecuteChanged y se puede deshabilitar si CanExecute devuelve false, o bien habilitar si CanExecute devuelve true.

La semántica de un comando puede ser coherente entre aplicaciones y clases, pero la lógica de la acción es específica del objeto determinado sobre el que actúa. La combinación de teclas CTRL+X invoca el comando Cortar en clases de texto, clases de imagen y exploradores web, pero la lógica real para realizar la operación Cortar la define la aplicación que realiza el corte. Un RoutedCommand permite a los clientes implementar la lógica. Un objeto de texto puede cortar el texto seleccionado en el Portapapeles, mientras que un objeto de imagen puede cortar la imagen seleccionada. Cuando una aplicación controla el evento Executed, tiene acceso al destino del comando y puede emprender la acción adecuada según el tipo del destino.

Ejemplo de comando simple en WPF

La manera más sencilla de usar un comando en WPF es usar un objeto RoutedCommand predefinido de una de las clases de la biblioteca de comandos; use un control con compatibilidad nativa para controlar el comando y un control con compatibilidad nativa para invocar un comando. El comando Paste es uno de los comandos predefinidos en la clase ApplicationCommands. El control TextBox incorpora lógica para controlar el comando Paste. Y la clase MenuItem ofrece compatibilidad nativa para invocar comandos.

En el ejemplo siguiente se muestra cómo configurar un MenuItem para que cuando se hace clic en él invoque el comando Paste en TextBox, suponiendo que TextBox tenga el foco de teclado.

<StackPanel>
  <Menu>
    <MenuItem Command="ApplicationCommands.Paste" />
  </Menu>
  <TextBox />
</StackPanel>
// Creating the UI objects
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();

// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);

// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;

// Setting the command target to the TextBox
pasteMenuItem.CommandTarget = pasteTextBox;
' Creating the UI objects
Dim mainStackPanel As New StackPanel()
Dim pasteTextBox As New TextBox()
Dim stackPanelMenu As New Menu()
Dim pasteMenuItem As New MenuItem()

' Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem)
mainStackPanel.Children.Add(stackPanelMenu)
mainStackPanel.Children.Add(pasteTextBox)

' Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste

Cuatro conceptos básicos de los comandos de WPF

El modelo de comando enrutado de WPF se puede dividir en cuatro conceptos básicos: el comando, el origen del comando, el destino del comando y el enlace del comando:

  • El comando es la acción que se va a ejecutar.

  • El origen del comando es el objeto que invoca el comando.

  • El destino del comando objeto en el que se ejecuta el comando.

  • El enlace del comando es el objeto que asigna la lógica del comando al comando.

En el ejemplo anterior, el comando es Paste, MenuItem es el origen del comando, TextBox es el destino del comando y el enlace de comando lo proporciona el control TextBox. Es importante destacar que CommandBinding no siempre lo proporciona el control que es la clase de destino del comando. Con bastante frecuencia, el desarrollador de la aplicación debe crear CommandBinding, o es posible que CommandBinding se conecte a un antecesor del destino de comando.

Comandos:

Los comandos de WPF se crean mediante la implementación de la interfaz ICommand. ICommand expone dos métodos (Execute y CanExecute) y un evento (CanExecuteChanged). Execute realiza las acciones que están asociadas con el comando. CanExecute determina si el comando se puede ejecutar en el destino del comando actual. CanExecuteChanged se genera si el administrador de comandos que centraliza las operaciones de comandos detecta un cambio en el origen del comando que podría invalidar un comando que se ha generado, pero que el enlace del comando todavía no ha ejecutado. La implementación de WPF de ICommand es la clase RoutedCommand y es el foco de esta introducción.

Los orígenes principales de la entrada en WPF son el mouse, el teclado, el lápiz y los comandos enrutados. Las entradas más orientadas al dispositivo usan un RoutedEvent para notificar a los objetos de una página de la aplicación que se produjo un evento de entrada. Un RoutedCommand no es diferente. Los métodos Execute y CanExecute de un RoutedCommand no contienen la lógica de aplicación para el comando, sino que en su lugar generan eventos enrutados que se canalizan y propagan a través del árbol de elementos hasta que encuentran un objeto con un CommandBinding. El CommandBinding contiene los controladores para estos eventos, que son los que ejecutan el comando. Para obtener más información sobre el enrutamiento de eventos en WPF, consulte Información general sobre eventos enrutados.

El método Execute de un RoutedCommand genera los eventos PreviewExecuted y Executed en el destino del comando. El método CanExecute de un RoutedCommand genera los eventos CanExecute y PreviewCanExecute en el destino del comando. Estos eventos se tunelizan y propagan a través del árbol de elementos hasta que encuentran un objeto que tiene un CommandBinding para ese comando concreto.

WPF proporciona un conjunto de comandos enrutados comunes distribuido entre varias clases: MediaCommands, ApplicationCommands, NavigationCommands, ComponentCommands yEditingCommands. Estas clases solo constan de los objetos RoutedCommand y no de la lógica de implementación del comando. La lógica de implementación es responsabilidad del objeto en el que se ejecuta el comando.

Orígenes del comando

El origen del comando es el objeto que invoca el comando. Ejemplos de orígenes de comando son MenuItem, Button y KeyGesture.

Por lo general, los orígenes de comando de WPF implementan la interfaz ICommandSource.

ICommandSource expone tres propiedades: Command, CommandTarget y CommandParameter:

Las clases de WPF que implementan ICommandSource son ButtonBase, MenuItem, Hyperlink y InputBinding. ButtonBase, MenuItem y Hyperlink invocan un comando cuando se hace clic en ellas, y InputBinding invoca un comando cuando se ejecuta el InputGesture que tiene asociado.

En el ejemplo siguiente se muestra cómo usar un MenuItem en un ContextMenu como origen de comando para el comando Properties.

<StackPanel>
  <StackPanel.ContextMenu>
    <ContextMenu>
      <MenuItem Command="ApplicationCommands.Properties" />
    </ContextMenu>
  </StackPanel.ContextMenu>
</StackPanel>
StackPanel cmdSourcePanel = new StackPanel();
ContextMenu cmdSourceContextMenu = new ContextMenu();
MenuItem cmdSourceMenuItem = new MenuItem();

// Add ContextMenu to the StackPanel.
cmdSourcePanel.ContextMenu = cmdSourceContextMenu;
cmdSourcePanel.ContextMenu.Items.Add(cmdSourceMenuItem);

// Associate Command with MenuItem.
cmdSourceMenuItem.Command = ApplicationCommands.Properties;
Dim cmdSourcePanel As New StackPanel()
Dim cmdSourceContextMenu As New ContextMenu()
Dim cmdSourceMenuItem As New MenuItem()

' Add ContextMenu to the StackPanel.
cmdSourcePanel.ContextMenu = cmdSourceContextMenu
cmdSourcePanel.ContextMenu.Items.Add(cmdSourceMenuItem)

' Associate Command with MenuItem.
cmdSourceMenuItem.Command = ApplicationCommands.Properties

Normalmente, un origen de comando escuchará el evento CanExecuteChanged. Este evento informa al origen del comando de un posible cambio en la capacidad del comando para ejecutarse en el destino del comando actual. El origen del comando puede consultar el estado actual de RoutedCommand mediante el método CanExecute. A continuación, el origen del comando puede deshabilitarse automáticamente si no se puede ejecutar el comando. Un ejemplo de esto es un MenuItem que se atenúa automáticamente cuando no se puede ejecutar un comando.

Se puede usar un InputGesture como un origen de comando. Dos tipos de gestos de entrada en WPF son KeyGesture y MouseGesture. Se puede considerar un KeyGesture como un método abreviado de teclado, como CTRL+C. Un KeyGesture está formado por una Key y un conjunto de ModifierKeys. Un MouseGesture está formado por una MouseAction y un conjunto opcional de ModifierKeys.

Para que un InputGesture actúe como un origen del comando, debe estar asociado con un comando. Existen dos maneras de lograrlo. Una manera consiste en usar un InputBinding.

En el ejemplo siguiente se muestra cómo crear un KeyBinding entre un KeyGesture y un RoutedCommand.

<Window.InputBindings>
  <KeyBinding Key="B"
              Modifiers="Control" 
              Command="ApplicationCommands.Open" />
</Window.InputBindings>
KeyGesture OpenKeyGesture = new KeyGesture(
    Key.B,
    ModifierKeys.Control);

KeyBinding OpenCmdKeybinding = new KeyBinding(
    ApplicationCommands.Open,
    OpenKeyGesture);

this.InputBindings.Add(OpenCmdKeybinding);
Dim OpenKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)

Dim OpenCmdKeybinding As New KeyBinding(ApplicationCommands.Open, OpenKeyGesture)

Me.InputBindings.Add(OpenCmdKeybinding)

Otra manera de asociar un InputGesture a un RoutedCommand consiste en agregar el InputGesture a la InputGestureCollection en el RoutedCommand.

En el ejemplo siguiente se muestra cómo agregar un KeyGesture a la InputGestureCollection de un RoutedCommand.

KeyGesture OpenCmdKeyGesture = new KeyGesture(
    Key.B,
    ModifierKeys.Control);

ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture);
Dim OpenCmdKeyGesture As New KeyGesture(Key.B, ModifierKeys.Control)

ApplicationCommands.Open.InputGestures.Add(OpenCmdKeyGesture)

CommandBinding

Un CommandBinding asocia un comando a los controladores de eventos que implementan el comando.

La clase CommandBinding contiene una propiedad Command y los eventos PreviewExecuted, Executed, PreviewCanExecute y CanExecute.

Command es el comando con el que se asocia CommandBinding. Los controladores de eventos que están conectados a los eventos PreviewExecuted y Executed implementan la lógica de comando. Los controladores de eventos conectados a los eventos PreviewCanExecute y CanExecute determinan si el comando se puede ejecutar en el destino del comando actual.

En el ejemplo siguiente se muestra cómo crear un CommandBinding en la Window raíz de una aplicación. El CommandBinding asocia el comando Open con los controladores Executed y CanExecute.

<Window.CommandBindings>
  <CommandBinding Command="ApplicationCommands.Open"
                  Executed="OpenCmdExecuted"
                  CanExecute="OpenCmdCanExecute"/>
</Window.CommandBindings>
// Creating CommandBinding and attaching an Executed and CanExecute handler
CommandBinding OpenCmdBinding = new CommandBinding(
    ApplicationCommands.Open,
    OpenCmdExecuted,
    OpenCmdCanExecute);

this.CommandBindings.Add(OpenCmdBinding);
' Creating CommandBinding and attaching an Executed and CanExecute handler
Dim OpenCmdBinding As New CommandBinding(ApplicationCommands.Open, AddressOf OpenCmdExecuted, AddressOf OpenCmdCanExecute)

Me.CommandBindings.Add(OpenCmdBinding)

Después, se crean ExecutedRoutedEventHandler y CanExecuteRoutedEventHandler. ExecutedRoutedEventHandler abre un MessageBox que muestra una cadena en la que se indica que se ha ejecutado el comando. CanExecuteRoutedEventHandler establece la propiedad CanExecute en true.

void OpenCmdExecuted(object target, ExecutedRoutedEventArgs e)
{
    String command, targetobj;
    command = ((RoutedCommand)e.Command).Name;
    targetobj = ((FrameworkElement)target).Name;
    MessageBox.Show("The " + command +  " command has been invoked on target object " + targetobj);
}
Private Sub OpenCmdExecuted(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
    Dim command, targetobj As String
    command = CType(e.Command, RoutedCommand).Name
    targetobj = CType(sender, FrameworkElement).Name
    MessageBox.Show("The " + command + " command has been invoked on target object " + targetobj)
End Sub
void OpenCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}
Private Sub OpenCmdCanExecute(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
    e.CanExecute = True
End Sub

A CommandBinding se adjunta a un objeto específico, como el elemento Window raíz de la aplicación o un control. El objeto al que se adjunta CommandBinding define el ámbito del enlace. Por ejemplo, el evento Executed puede alcanzar un CommandBinding conectado a un antecesor del destino de comando, pero un CommandBinding conectado a un descendiente del destino de comando no se puede alcanzar. Esta es una consecuencia directa de la manera en que un evento RoutedEvent se tuneliza y propaga desde el objeto que genera el evento.

En algunas situaciones CommandBinding se adjunta al propio destino del comando, como sucede con la clase TextBox y los comandos Cut, Copy y Paste. Pero a menudo resulta más conveniente adjuntar CommandBinding a un antecesor del destino del comando, como la Window principal o el objeto de aplicación, especialmente si se puede usar el mismo CommandBinding para varios destinos del comando. Estas son las decisiones de diseño que querrá tener en cuenta al crear la infraestructura de comandos.

Destino del comando

El destino del comando es el elemento en el que se ejecuta el comando. En lo que se refiere a un RoutedCommand, el destino del comando es el elemento en el que se inicia el enrutamiento de Executed y CanExecute. Como se mencionó anteriormente, en WPF, la propiedad CommandTarget de ICommandSource solo es aplicable cuando ICommand es RoutedCommand. Si CommandTarget se establece en un ICommandSource y el comando correspondiente no es un RoutedCommand, se omite el destino del comando.

El origen del comando puede establecer de manera explícita el destino del comando. Si no se define el destino del comando, el elemento con el foco del teclado se usará como el destino del comando. Una de las ventajas de usar el elemento con el foco del teclado como el destino del comando es que permite al desarrollador de la aplicación usar el mismo origen del comando para invocar un comando en varios destinos sin necesidad de realizar un seguimiento del destino del comando. Por ejemplo, si un MenuItem invoca el comando Pegar en una aplicación que tiene un control TextBox y otro control PasswordBox, el destino puede ser TextBox o PasswordBox, en función del control que tenga el foco de teclado.

En el ejemplo siguiente se muestra cómo establecer de forma explícita el destino del comando en el marcado y en el código subyacente.

<StackPanel>
  <Menu>
    <MenuItem Command="ApplicationCommands.Paste"
              CommandTarget="{Binding ElementName=mainTextBox}" />
  </Menu>
  <TextBox Name="mainTextBox"/>
</StackPanel>
// Creating the UI objects
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();

// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);

// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;

// Setting the command target to the TextBox
pasteMenuItem.CommandTarget = pasteTextBox;
' Creating the UI objects
Dim mainStackPanel As New StackPanel()
Dim pasteTextBox As New TextBox()
Dim stackPanelMenu As New Menu()
Dim pasteMenuItem As New MenuItem()

' Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem)
mainStackPanel.Children.Add(stackPanelMenu)
mainStackPanel.Children.Add(pasteTextBox)

' Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste

CommandManager

CommandManager proporciona una serie de funciones relacionadas con los comandos. Proporciona un conjunto de métodos estáticos para agregar y quitar los controladores de eventos PreviewExecuted, Executed, PreviewCanExecute y CanExecute a y desde un elemento específico. Proporciona un medio para registrar objetos CommandBinding y InputBinding en una clase específica. CommandManager también proporciona un medio, a través del evento RequerySuggested, para notificar a un comando cuándo debe generar el evento CanExecuteChanged.

El método InvalidateRequerySuggested fuerza a CommandManager a generar el evento RequerySuggested. Esto es útil para las condiciones que deben deshabilitar o habilitar un comando, pero de las que CommandManager no es consciente.

Biblioteca de comandos

WPF proporciona un conjunto de comandos predefinidos. La biblioteca de comandos se compone de las clases siguientes: ApplicationCommands, NavigationCommands, MediaCommands, EditingCommands y ComponentCommands. Estas clases proporcionan comandos como Cut, BrowseBack y BrowseForward, Play, Stop y Pause.

Muchos de estos comandos incluyen un conjunto de enlaces de entrada predeterminados. Por ejemplo, si especifica que la aplicación controla el comando Copiar, obtendrá automáticamente el enlace de teclado "CTRL + C". También obtendrá enlaces para otros dispositivos de entrada, como información de voz y gestos del lápiz de Tablet PC.

Al hacer referencia a los comandos de las diversas bibliotecas de comandos mediante XAML, normalmente puede omitir el nombre de clase de la clase de biblioteca que expone la propiedad de comando estática. En general, los nombres de comando son inequívocos como cadenas y los tipos propietarios existen para proporcionar una agrupación lógica de comandos, pero no son necesarios para la desambiguación. Por ejemplo, puede especificar Command="Cut" en lugar del comando Command="ApplicationCommands.Cut" más detallado. Se trata de un mecanismo de conveniencia que está integrado en el procesador de WPF XAML para los comandos (más concretamente, es un comportamiento del convertidor de tipos de ICommand, al que el procesador de WPF XAML hace referencia en tiempo de carga).

Creación de comandos personalizados

Si los comandos de las clases de la biblioteca de comandos no satisfacen sus necesidades, puede crear sus propios comandos. Existen dos maneras de crear un comando personalizado. La primera es empezar desde cero e implementar la interfaz ICommand. La otra forma, y el enfoque más común, consiste en crear un comando RoutedCommand o RoutedUICommand.

Para obtener un ejemplo de cómo crear un RoutedCommand personalizado, vea Create a Custom RoutedCommand Sample (Crear un ejemplo de RoutedCommand personalizado).

Vea también