Información general sobre acciones del usuario

Actualización: noviembre 2007

El subsistema de Windows Presentation Foundation (WPF) proporciona una API eficaz para aceptar datos de entrada desde distintos dispositivos, como el mouse, el teclado y el lápiz óptico.

En este tema se describen los servicios proporcionados por WPF y se explica la arquitectura de los sistemas de entrada.

Este tema contiene las secciones siguientes.

  • API de entrada
  • Enrutamiento de eventos
  • Controlar los eventos de entrada
  • Entrada de texto
  • Foco
  • Posición del mouse
  • Captura del mouse
  • Comandos
  • El sistema de entrada y los elementos base
  • Pasos adicionales
  • Temas relacionados

API de entrada

La exposición de la API de entrada primaria se encuentra en las clases de elementos base: UIElement, ContentElement, FrameworkElement y FrameworkContentElement. Para obtener más información sobre los elementos base, vea Información general sobre elementos base. Estas clases proporcionan funcionalidad para los eventos de entrada relacionados con la presión de teclas, los botones del mouse, la rueda del mouse, el movimiento del mouse, la administración del foco y la captura del mouse, por citar algunos. Colocando la API de entrada en los elementos base, en lugar de tratar todos los eventos de entrada como un servicio, la arquitectura de entrada permite que los eventos de entrada tengan su origen en un objeto determinado de la interfaz de usuario y admitan un esquema de enrutamiento de eventos en el que más de un elemento tenga oportunidad de controlar un evento de entrada. Muchos eventos de entrada tienen un par de eventos asociados. Por ejemplo, el evento de presión de tecla está asociado a los eventos KeyDown y PreviewKeyDown. La diferencia entre estos eventos estriba en cómo se enrutan al elemento de destino. Tunelización de eventos de vista previa en el árbol de elementos, del elemento raíz al elemento de destino. Propagación de eventos de propagación, del elemento de destino al elemento raíz. El enrutamiento de eventos de WPF se explica de forma más detallada posteriormente en esta información general y en Información general sobre eventos enrutados.

Clases de teclado y de mouse

Además de la API de entrada de las clases de elementos base, las clases Keyboard y Mouse proporcionan una API adicional para trabajar con los datos de entrada del teclado y del mouse.

Como ejemplos de API de entrada de la clase Keyboard pueden citarse la propiedad Modifiers, que devuelve las ModifierKeys que se encuentran presionadas en estos momentos y el método IsKeyDown, que determina si está presionada una tecla especificada.

En el ejemplo siguiente se usa el método GetKeyStates para determinar si una Key se encuentra en estado presionado.

// Uses the Keyboard.GetKeyStates to determine if a key is down.
// A bitwise AND operation is used in the comparison. 
// e is an instance of KeyEventArgs.
if ((Keyboard.GetKeyStates(Key.Return) & KeyStates.Down) > 0)
{
    btnNone.Background = Brushes.Red;
}

Como ejemplos de la API de entrada de la clase Mouse pueden citarse MiddleButton, que obtiene el estado del botón central del mouse, y DirectlyOver, que obtiene el elemento sobre el que se encuentra actualmente el puntero del mouse.

En el ejemplo siguiente se determina si el LeftButton del mouse se encuentra en estado Pressed.

if (Mouse.LeftButton == MouseButtonState.Pressed)
{
    UpdateSampleResults("Left Button Pressed");
}

Las clases Mouse y Keyboard se abordan con más detalle a lo largo de esta información general.

Entrada del lápiz

WPF dispone de compatibilidad integrada para Stylus. Stylus es un sistema de entrada manuscrita que se popularizó gracias a Tablet PC. Las aplicaciones de WPF pueden considerar el lápiz como si se tratara de un mouse mediante la API del mouse, pero WPF también expone una abstracción de dispositivo de lápiz que utiliza un modelo similar al del teclado y el mouse. Todas las API relacionadas con el lápiz incluyen la palabra "Stylus".

Dado que el lápiz puede actuar como un mouse, las aplicaciones que sólo son compatibles con la entrada del mouse pueden obtener automáticamente cierto grado de compatibilidad con el lápiz. Cuando el lápiz se utiliza de esta forma, la aplicación tiene la oportunidad de controlar el evento de lápiz adecuado y, a continuación, controla el evento de mouse correspondiente. Además, hay otros servicios de nivel superior, como la entrada manuscrita, que también están disponibles a través de la abstracción de dispositivo de lápiz. Para obtener más información sobre la entrada manuscrita, vea Introducción a las entradas manuscritas.

Enrutamiento de eventos

Un FrameworkElement puede incluir otros elementos como elementos secundarios en su modelo de contenido, formando así un árbol de elementos. En WPF, el elemento primario puede participar en la entrada dirigida a sus elementos secundarios o a otros descendientes controlando los eventos. Esto resulta especialmente útil para crear controles partiendo de otros más pequeños, un proceso que recibe el nombre de "composición de controles" o "composición". Para obtener más información sobre los árboles de elementos y la forma en que se relacionan con las rutas de eventos, vea Árboles en WPF.

El enrutamiento de eventos es un proceso que consiste en reenviar eventos a varios elementos para que un objeto o elemento determinado a lo largo de la ruta pueda decidir si ofrece una respuesta significativa (a través de un proceso de control) a un evento que podría tener su origen en otro elemento. Los eventos enrutados utilizan uno de estos tres mecanismos de enrutamiento: directo, propagación y tunelización. En el enrutamiento directo, el elemento de origen es el único elemento que recibe notificación y el evento no se enruta a ningún otro elemento. Sin embargo, el evento enrutado directo proporciona algunas funciones adicionales que sólo están presentes para los eventos enrutados por oposición a los eventos CLR estándar. La propagación asciende por el árbol de elementos notificando primero al elemento que originó el evento, a continuación al elemento primario, y así sucesivamente. La tunelización se inicia en la raíz del árbol de elementos y desciende por este hasta finalizar en el elemento de origen inicial. Para obtener más información acerca de los eventos enrutados, vea Información general sobre eventos enrutados.

Generalmente, los eventos de entrada de WPF se presentan en parejas que constan de un evento de túnel y de un evento de propagación. Los eventos de túnel se distinguen de los eventos de propagación mediante el prefijo "Preview". Por ejemplo, PreviewMouseMove es la versión de túnel de un evento de movimiento del mouse y MouseMove es la versión de propagación de ese mismo evento. Este emparejamiento de eventos es una convención que se implementa en el nivel de elemento y no es ninguna función inherente del sistema de eventos de WPF. Para obtener información detallada, consulte la sección Eventos de entrada de WPF en Información general sobre eventos enrutados.

Controlar los eventos de entrada

Para recibir la entrada en un elemento, debe existir un controlador de eventos asociado a ese evento concreto. En XAML este proceso es muy sencillo: se hace referencia al nombre del evento como un atributo del elemento que escuchará este evento. A continuación, se establece el valor del atributo en el nombre del controlador de eventos que se define, basado en un delegado. El controlador de eventos debe escribirse en código, como C#, y puede incluirse en un archivo de código subyacente.

Los eventos de teclado se producen cuando el sistema operativo notifica acciones de teclas que se producen mientras el foco del teclado se encuentra en un elemento. Los eventos de mouse y de lápiz se dividen en dos categorías: eventos que notifican cambios en la posición del puntero en relación con el elemento y eventos que notifican cambios de estado en los botones del dispositivo.

Ejemplo de evento de entrada de teclado

En el ejemplo siguiente se realizan escuchas para detectar cuándo se presiona la tecla de dirección izquierda. Se crea un elemento StackPanel con un control Button. La instancia de Button tiene asociado un controlador de eventos para escuchar cuándo se presiona la tecla de dirección izquierda.

En la primera sección del ejemplo se crean los controles StackPanel y Button, y el controlador de eventos se asocia al evento KeyDown.

<StackPanel>
  <Button Background="AliceBlue"
          KeyDown="OnButtonKeyDown"
          Content="Button1"/>
</StackPanel>
// Create the UI elements.
StackPanel keyboardStackPanel = new StackPanel();
Button keyboardButton1 = new Button();

// Set properties on Buttons.
keyboardButton1.Background = Brushes.AliceBlue;
keyboardButton1.Content = "Button 1";

// Attach Buttons to StackPanel.
keyboardStackPanel.Children.Add(keyboardButton1);

// Attach event handler.
keyboardButton1.KeyDown += new KeyEventHandler(OnButtonKeyDown);

La segunda sección del ejemplo está escrita en código y en ella se define el controlador de eventos. Cuando se presiona la tecla de dirección izquierda y el control Button tiene el foco de teclado, se ejecuta el controlador y cambia el color Background de Button. Si se presiona una tecla que no es tecla de dirección izquierda, el color Background de Button vuelve a establecerse en su color original.

private void OnButtonKeyDown(object sender, KeyEventArgs e)
{
    Button source = e.Source as Button;
    if (source != null)
    {
        if (e.Key == Key.Left)
        {
            source.Background = Brushes.LemonChiffon;
        }
        else
        {
            source.Background = Brushes.AliceBlue;
        }
    }
}

Ejemplo de evento de entrada del mouse

En el ejemplo siguiente, el color Background de Button cambia cuando el puntero del mouse entra en Button. El color Background se restaura cuando el mouse sale de Button.

En la primera sección del ejemplo se crean los controles StackPanel y Button, y se asocian controladores de eventos para los eventos MouseEnter y MouseLeave a Button.

<StackPanel>
  <Button Background="AliceBlue"
          MouseEnter="OnMouseExampleMouseEnter"
          MouseLeave="OnMosueExampleMouseLeave">Button

  </Button>
</StackPanel>
// Create the UI elements.
StackPanel mouseMoveStackPanel = new StackPanel();
Button mouseMoveButton = new Button();

// Set properties on Button.
mouseMoveButton.Background = Brushes.AliceBlue;
mouseMoveButton.Content = "Button";

// Attach Buttons to StackPanel.
mouseMoveStackPanel.Children.Add(mouseMoveButton);

// Attach event handler.
mouseMoveButton.MouseEnter += new MouseEventHandler(OnMouseExampleMouseEnter);
mouseMoveButton.MouseLeave += new MouseEventHandler(OnMosueExampleMouseLeave);

La segunda sección del ejemplo está escrita en código y en ella se definen los controladores de eventos. Cuando el mouse entra en Button, el color Background de Button cambia a SlateGray. Cuando el mouse sale de Button, el color Background de Button vuelve a establecerse en AliceBlue.

private void OnMouseExampleMouseEnter(object sender, MouseEventArgs e)
{
    // Cast the source of the event to a Button.
    Button source = e.Source as Button;

    // If source is a Button.
    if (source != null)
    {
        source.Background = Brushes.SlateGray;
    }
}
private void OnMosueExampleMouseLeave(object sender, MouseEventArgs e)
{
    // Cast the source of the event to a Button.
    Button source = e.Source as Button;

    // If source is a Button.
    if (source != null)
    {
        source.Background = Brushes.AliceBlue;
    }
}

Entrada de texto

El evento TextInput le permite escuchar la entrada de texto de forma independiente del dispositivo. El teclado constituye el medio principal de entrada de texto, pero los dispositivos de voz, escritura a mano y otros dispositivos de entrada también pueden generar la entrada de texto.

En el caso de las acciones del teclado, WPF envía primero los eventos KeyDown o KeyUp adecuados. Si no se controlan dichos eventos y la tecla es de texto (en lugar de ser una tecla de control como las teclas de dirección o de función), se desencadena un evento TextInput. No siempre existe una asignación unívoca simple entre los eventos KeyDown o KeyUp y TextInput, ya que varias presiones de tecla pueden generar un único carácter de entrada de texto y una sola presión de tecla pueden generar cadenas de varios caracteres. Esto suele suceder con idiomas como el chino, el japonés y el coreano, que utilizan Editores de métodos de entrada (IMEs) para generar los miles de caracteres posibles de sus alfabetos correspondientes.

Cuando WPF envía un evento KeyUp o KeyDown, Key se establece en Key.System si las presiones de tecla pueden formar parte de un evento TextInput (por ejemplo, si se presiona ALT+S). Esto permite al código de un controlador de eventos KeyDown comprobar si existe Key.System y, si se encuentra, dejar el procesamiento al controlador del evento TextInput que se desencadena a continuación. En estos casos, pueden utilizarse las distintas propiedades del argumento TextCompositionEventArgs para determinar las presiones de tecla originales. Del mismo modo, si hay un IME activo, Key presenta el valor Key.ImeProcessed y ImeProcessedKey proporciona la presión o las presiones de tecla originales.

En el ejemplo siguiente, se define un controlador para el evento Click y otro para el evento KeyDown.

El primer segmento de código o marcado crea la interfaz de usuario.

<StackPanel KeyDown="OnTextInputKeyDown">
  <Button Click="OnTextInputButtonClick"
          Content="Open" />
  <TextBox> . . . </TextBox>
</StackPanel>
// Create the UI elements.
StackPanel textInputStackPanel = new StackPanel();
Button textInputeButton = new Button();
TextBox textInputTextBox = new TextBox();
textInputeButton.Content = "Open";

// Attach elements to StackPanel.
textInputStackPanel.Children.Add(textInputeButton);
textInputStackPanel.Children.Add(textInputTextBox);

// Attach event handlers.
textInputStackPanel.KeyDown += new KeyEventHandler(OnTextInputKeyDown);
textInputeButton.Click += new RoutedEventHandler(OnTextInputButtonClick);

El segundo segmento de código contiene los controladores de eventos.

private void OnTextInputKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.O && Keyboard.Modifiers == ModifierKeys.Control)
    {
        handle();
        e.Handled = true;
    }
}

private void OnTextInputButtonClick(object sender, RoutedEventArgs e)
{
    handle();
    e.Handled = true;
} 

public void handle()
{
    MessageBox.Show("Pretend this opens a file");
}

Dado que los eventos de entrada se propagan a la ruta de nivel superior de eventos, StackPanel recibe la entrada independientemente del elemento que tenga el foco de teclado. Primero se notifica al control TextBox y sólo se llama al controlador OnTextInputKeyDown si TextBox no controló la entrada. Si se utiliza el evento PreviewKeyDown en lugar del evento KeyDown, se llama primero al controlador OnTextInputKeyDown.

En este ejemplo, la lógica de control se ha escrito dos veces, una para CTRL+O y otra para el evento de clic del botón. Esto puede simplificarse utilizando comandos en lugar de controlar directamente los eventos de entrada. Los comandos se explican en esta información general y en Información general sobre comandos.

Foco

Hay dos conceptos principales relacionados con el foco en WPF: el foco de teclado y el foco lógico.

Foco de teclado

El foco de teclado hace referencia al elemento que recibe las acciones del teclado. Sólo puede haber un elemento en todo el escritorio que tenga el foco de teclado. En WPF, el elemento que tenga el foco de teclado tendrá la propiedad IsKeyboardFocused establecida en true. El método Keyboard estático FocusedElement devuelve el elemento que tiene el foco de teclado actualmente.

El foco de teclado puede obtenerse usando la tecla de tabulación hasta llegar a un elemento o haciendo clic con el mouse en determinados elementos, como TextBox. El foco de teclado también puede obtenerse mediante programación utilizando el método Focus en la clase Keyboard. Focus intenta asignar el foco de teclado al elemento especificado. El elemento devuelto por Focus es el elemento que tiene el foco de teclado actualmente.

Para que un elemento obtenga el foco de teclado, las propiedades Focusable y IsVisible deben estar establecidas en true. Algunas clases, como Panel, tienen la propiedad Focusable establecida en false de forma predeterminada; por lo tanto, puede que tenga que establecer esta propiedad en true si desea que ese elemento pueda obtener el foco.

En el ejemplo siguiente se utiliza Focus para establecer el foco de teclado en un control Button. El lugar recomendado para establecer el foco inicial en una aplicación es el controlador de eventos Loaded.

private void OnLoaded(object sender, RoutedEventArgs e)
{
    // Sets keyboard focus on the first Button in the sample.
    Keyboard.Focus(firstButton);
}

Para obtener más información acerca del foco de teclado, vea Información general sobre el foco.

Foco lógico

El foco lógico hace referencia al FocusManager.FocusedElement de un ámbito de foco. Puede haber varios elementos con el foco lógico en una aplicación, pero sólo puede haber uno con el foco lógico en un ámbito de foco determinado.

Un ámbito de foco es un elemento contenedor que realiza un seguimiento de la propiedad FocusedElement dentro de su ámbito. Cuando el foco salga de un ámbito de foco, el elemento que tenga el foco perderá el foco de teclado, pero conservará el foco lógico. Cuando el foco regrese al ámbito de foco, el elemento que tenga el foco obtendrá el foco de teclado. Esto permite cambiar el foco de teclado entre varios ámbitos de foco, pero garantiza que el elemento que tiene el foco dentro del ámbito de foco siga siendo el elemento que tiene el foco cuando este regrese.

En Lenguaje de marcado de aplicaciones extensible (XAML) un elemento puede convertirse en un ámbito de foco estableciendo la propiedad asociada FocusManager IsFocusScope en true o mediante código, estableciendo la propiedad asociada con el método SetIsFocusScope.

En el ejemplo siguiente, un StackPanel se convierte en un ámbito de foco estableciendo la propiedad asociada IsFocusScope.

<StackPanel Name="focusScope1" 
            FocusManager.IsFocusScope="True"
            Height="200" Width="200">
  <Button Name="button1" Height="50" Width="50"/>
  <Button Name="button2" Height="50" Width="50"/>
</StackPanel>
StackPanel focuseScope2 = new StackPanel();
FocusManager.SetIsFocusScope(focuseScope2, true);

Las clases de WPF, que son ámbitos de foco de forma predeterminada, son Window, Menu, ToolBar y ContextMenu.

Un elemento que tenga el foco de teclado también tendrá el foco lógico para el ámbito de foco al que pertenezca; por lo tanto, al establecer el foco en un elemento con el método Focus de la clase Keyboard o las clases del elemento base, se intentará asignar al elemento el foco de teclado y el foco lógico.

Para determinar cuál es el elemento que tiene el foco en un ámbito de foco, utilice el método GetFocusedElement. Para cambiar el elemento que tiene el foco para un ámbito de foco, utilice el método SetFocusedElement.

Para obtener más información acerca del foco lógico, vea Información general sobre el foco.

Posición del mouse

La API de entrada de WPF proporciona información útil con respecto a los espacios de coordenadas. Por ejemplo, la coordenada (0,0) es la coordenada superior izquierda, pero, ¿de qué elemento del árbol? ¿Del elemento de destino de la entrada? ¿Del elemento que asoció al controlador de eventos? ¿O de algún otro? Para evitar confusiones, la API de entrada de WPF exige que se especifique el marco de referencia al trabajar con coordenadas obtenidas a través del mouse. El método GetPosition devuelve la coordenada del puntero del mouse en relación con el elemento especificado.

Captura del mouse

Los dispositivos de mouse concretamente presentan una característica modal que se denomina captura del mouse. La captura del mouse se utiliza para mantener un estado de entrada de transición cuando se inicia una operación de arrastrar y colocar, de forma que no necesariamente se produzcan otras operaciones relacionadas con la posición nominal en pantalla del puntero del mouse. Durante el arrastre, el usuario no puede hacer clic sin que se anule la operación de arrastrar y colocar, lo que hace que la mayoría de las indicaciones de mouseover sean inapropiadas mientras el origen del arrastre mantiene la captura del mouse. El sistema de entrada expone las API que pueden determinar el estado de captura del mouse, así como las API que pueden forzar la captura del mouse a un elemento concreto o borrar el estado de captura del mouse. Para obtener más información acerca de las operaciones de arrastrar y colocar, vea Información general sobre la función de arrastrar y colocar.

Comandos

Los comandos habilitan el control de entrada en un nivel más semántico que la entrada del dispositivo. Los comandos son directivas simples, como Cut, Copy, Paste u Open. Los comandos son útiles para centralizar la lógica de comandos. Se podría obtener acceso al mismo comando desde un control Menu de ToolBar, o a través de un método abreviado de teclado. Los comandos también proporcionan un mecanismo para deshabilitar los controles cuando el comando deja de estar disponible.

RoutedCommand es la implementación WPF de la interfaz ICommand. Cuando se ejecuta RoutedCommand, se desencadenan los eventos PreviewExecuted y Executed en el destino del comando, que se tunelizan y se propagan por el árbol de elementos al igual que cualquier otra entrada. Si no se establece ningún destino de comando, el elemento que tenga el foco de teclado será el destino del comando. La lógica que ejecuta el comando está asociada a un elemento CommandBinding. Cuando un evento Executed alcanza un elemento CommandBinding para ese comando en concreto, se llama a ExecutedRoutedEventHandler en CommandBinding. Este controlador ejecuta la acción del comando.

Para obtener más información sobre los comandos, vea Información general sobre comandos.

WPF proporciona una biblioteca de comandos comunes que consta de ApplicationCommands, MediaCommands, ComponentCommands, NavigationCommands y EditingCommands; o también puede definir la suya propia.

En el ejemplo siguiente se muestra cómo configurar un elemento MenuItem para que cuando se haga 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;

Para obtener más información acerca de los comandos de WPF, vea Información general sobre comandos.

El sistema de entrada y los elementos base

Los eventos de entrada, como los eventos asociados definidos por las clases Mouse, Keyboard y Stylus, los desencadena el sistema de entrada y se insertan en una posición determinada del modelo de objetos basada en una prueba de posicionamiento del árbol visual en tiempo de ejecución.

Cada uno de los eventos que Mouse, Keyboard y Stylus definen como un evento asociado también vuelven a exponerlo las clases de elementos base UIElement y ContentElement como un evento enrutado nuevo. Los eventos enrutados de los elementos base los generan las clases que controlan el evento asociado original y que reutilizan los datos de los eventos.

Cuando el evento de entrada se asocia a un elemento de origen determinado a través de su implementación de evento de entrada de elemento base, puede enrutarse a lo largo del resto de una ruta de eventos basada en una combinación de objetos del árbol lógico y visual, y puede controlarse mediante código de aplicación. En general, resulta más conveniente controlar estos eventos de entrada relacionados con dispositivos utilizando los eventos enrutados en UIElement y ContentElement, ya que puede utilizarse una sintaxis de controlador de eventos más intuitiva, tanto en XAML como en el código. También podría optar por controlar el evento asociado que inició el proceso, pero tendría que enfrentarse a varios problemas: el evento asociado podría estar marcado como controlado por el control de clases del elemento base y tendría que utilizar métodos de descriptor de acceso en lugar de una sintaxis de eventos auténticos para asociar controladores a los eventos asociados.

Pasos adicionales

Ya dispone de varias técnicas para controlar la entrada en WPF. También debería tener más conocimientos sobre los diversos tipos de eventos de entrada y los mecanismos de eventos enrutados utilizados por WPF.

Existen recursos adicionales en los que se explican con más detalle el enrutamiento de eventos y los elementos del marco de trabajo de WPF. Para obtener más información, vea los documentos de información general siguientes: Información general sobre comandos, Información general sobre el focoInformación general sobre elementos baseÁrboles en WPF y Información general sobre eventos enrutados.

Vea también

Conceptos

Información general sobre el foco

Información general sobre comandos

Información general sobre eventos enrutados

Información general sobre elementos base

Otros recursos

Propiedades