Input Overview
This article explains the architecture of the input systems in AlteNET UI.
Input API
The primary input API exposure is found on the base element classes: UIElement, FrameworkElement and Control. These classes provide functionality for input events related to key presses, mouse buttons, mouse-wheel, mouse movement, focus management, and mouse capture, to name a few. By placing the input API on the base elements, rather than treating all input events as a service, the input architecture enables the input events to be sourced by a particular object in the UI and to support an event routing scheme whereby more than one element has an opportunity to handle an input event.
Keyboard and Mouse Classes
In addition to the input API on the base element classes, the Keyboard class and Mouse classes provide additional API for working with keyboard and mouse input.
Examples of input API on the Keyboard class are the Modifiers property, which returns the ModifierKeys currently pressed, and the IsKeyDown method, which determines whether a specified key is pressed.
The following example uses the GetKeyStates method to determine if a Key is in the down state.
// 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.Enter) & KeyStates.Down) > 0)
{
btnNone.Background = Brushes.Red;
}
An example of input API on the Mouse class is MiddleButton, which obtains the state of the middle mouse button.
The following example determines whether the LeftButton on the mouse is in the Pressed state.
if (Mouse.LeftButton == MouseButtonState.Pressed)
{
UpdateSampleResults("Left Button Pressed");
}
The Mouse and Keyboard classes are covered in more detail throughout this overview.
Event Routing
A FrameworkElement can contain other elements as child elements in its content model, forming a tree of elements. In AlterNET UI, the parent element can participate in input directed to its child elements or other descendants by handing events. This is especially useful for building controls out of smaller controls, a process known as "control composition" or "compositing."
Event routing is the process of forwarding events to multiple elements so that a particular object or element along the route can choose to offer a significant response (through handling) to an event that might have been sourced by a different element.
Routed events use one of three routing mechanisms: direct, bubbling, and tunneling. In direct routing, the source element is the only element notified, and the event is not routed to any other elements. However, the direct routed event still offers some additional capabilities that are only present for routed events as opposed to standard CLR events. Bubbling works up the element tree by first notifying the element that sourced the event, then the parent element, and so on. Tunneling starts at the root of the element tree and works down, ending with the original source element.
Handling Input Events
To handle an element's input, an event handler must be associated with that particular event. In UIXML this is straightforward: you reference the name of the event as an attribute of the element that will be listening for this event. Then, you set the value of the attribute to the name of the event handler that you define, based on a delegate. The event handler must be written in code such as C# and can be included in a code-behind file.
Keyboard events occur when the operating system reports key actions that occur while the keyboard focus is on an element. Mouse and stylus events each fall into two categories: events that report changes in pointer position relative to the element and events that report changes in the state of device buttons.
Keyboard Input Event Example
The following example listens for a left arrow key press. A StackPanel is created that has a Button. An event handler to listen for the left arrow key press is attached to the Button instance.
The first section of the example creates the StackPanel and the Button and attaches the event handler for the KeyDown.
<StackPanel>
<Button Background="AliceBlue"
KeyDown="OnButtonKeyDown"
Text="Button1"/>
</StackPanel>
// Create the UI elements.
StackPanel keyboardStackPanel = new StackPanel();
Button keyboardButton1 = new Button();
// Set properties on Buttons.
keyboardButton1.Background = Brushes.AliceBlue;
keyboardButton1.Text = "Button 1";
// Attach Buttons to StackPanel.
keyboardStackPanel.Children.Add(keyboardButton1);
// Attach event handler.
keyboardButton1.KeyDown += new KeyEventHandler(OnButtonKeyDown);
The second section is written in code and defines the event handler. When the left arrow key is pressed, and the Button has keyboard focus, the handler runs and the Background color of the Button is changed. If the key is pressed, but it is not the left arrow key, the Background color of the Button is changed back to its starting color.
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;
}
}
}
Mouse Input Event Example
In the following example, the Background color of a Button is changed when the mouse pointer enters the Button. The Background color is restored when the mouse leaves the Button.
The first section of the example creates the StackPanel and the Button control and attaches the event handlers for the MouseEnter and MouseLeave events to the Button.
<StackPanel>
<Button Background="AliceBlue"
MouseEnter="OnMouseExampleMouseEnter"
MouseLeave="OnMosueExampleMouseLeave"
Text="Button">
</Button>
</StackPanel>
// Create the UI elements.
StackPanel mouseMoveStackPanel = new StackPanel();
Button mouseMoveButton = new Button();
// Set properties on Button.
mouseMoveButton.Background = Brushes.AliceBlue;
mouseMoveButton.Text = "Button";
// Attach Buttons to StackPanel.
mouseMoveStackPanel.Children.Add(mouseMoveButton);
// Attach event handler.
mouseMoveButton.MouseEnter += new MouseEventHandler(OnMouseExampleMouseEnter);
mouseMoveButton.MouseLeave += new MouseEventHandler(OnMosueExampleMouseLeave);
The second section of the example is written in code and defines the event handlers. When the mouse enters the Button, the Background color of the Button is changed to SlateGray. When the mouse leaves the Button, the Background color of the Button is changed back to 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;
}
}
Text Input
The KeyPress event enables you to listen for text input in a device-independent manner. The keyboard is the primary means of text input, but speech, handwriting, and other input devices can generate text input also.
For keyboard input, AlterNET UI first sends the appropriate KeyDown/KeyUp events. If those events are not handled, and the key is textual (rather than a control key such as directional arrows or function keys), then a KeyPress event is raised. There is not always a simple one-to-one mapping between KeyDown/KeyUp and KeyPress events because multiple keystrokes can generate a single character of text input, and single keystrokes can generate multi-character strings. This is especially true for languages such as Chinese, Japanese, and Korean, which use Input Method Editors (IMEs) to generate the thousands of possible characters in their corresponding alphabets.
The following example defines a handler for the Click event and a handler for the KeyDown event.
The first segment of code or markup creates the user interface.
<StackPanel KeyDown="OnTextInputKeyDown">
<Button Click="OnTextInputButtonClick" Text="Open" />
<TextBox />
</StackPanel>
// Create the UI elements.
StackPanel textInputStackPanel = new StackPanel();
Button textInputButton = new Button();
TextBox textInputTextBox = new TextBox();
textInputButton.Text = "Open";
// Attach elements to StackPanel.
textInputStackPanel.Children.Add(textInputeButton);
textInputStackPanel.Children.Add(textInputTextBox);
// Attach event handlers.
textInputStackPanel.KeyDown += new KeyEventHandler(OnTextInputKeyDown);
textInputButton.Click += new RoutedEventHandler(OnTextInputButtonClick);
The second segment of the code contains the event handlers.
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, EventArgs e)
{
Handle();
e.Handled = true;
}
public void Handle()
{
MessageBox.Show("Pretend this opens a file");
}
Because input events bubble up the event route, the
StackPanel receives the input regardless of
which element has keyboard focus. The TextBox
control is notified first, and the OnTextInputKeyDown
handler is called only
if the TextBox did not handle the input.
Mouse Position
The AlterNET UI input API provides helpful information with regard to coordinate
spaces. For example, coordinate (0,0)
is the upper-left coordinate, but the
upper-left of which element in the tree? The element that is the input target?
The element you attached your event handler to? Or something else? To avoid
confusion, the AlterNET UI input API requires that you specify your frame of reference
when you work with coordinates obtained through the mouse. The
GetPosition method returns the coordinate
of the mouse pointer relative to the specified element.
Mouse Capture
Mouse devices specifically hold a modal characteristic known as mouse capture. Mouse capture is used to maintain a transitional input state when a drag-and-drop operation is started so that other operations involving the nominal on-screen position of the mouse pointer do not necessarily occur. During the drag, the user cannot click without aborting the drag-and-drop, which makes most mouseover cues inappropriate while the mouse capture is held by the drag origin. The input system exposes APIs that can determine the mouse capture state, as well as APIs that can force mouse capture to a specific element or clear the mouse capture state.
The Input System and Base Elements
Input events such as the attached events defined by the Mouse and Keyboard classes are raised by the input system and injected into a particular position in the object model based on hit testing the visual tree at run time.
Each of the events that Mouse and Keyboard define as an attached event is also re-exposed by the base element class UIElement as a new routed event. The base element routed events are generated by classes handling the original attached event and reusing the event data.
When the input event becomes associated with a particular source element through its base element input event implementation, it can be routed through the remainder of an event route that is based on a combination of logical and visual tree objects, and be handled by application code. Generally, it is more convenient to handle these device-related input events using the routed events on UIElement, because you can use more intuitive event handler syntax both in UIXML and in code. You could choose to handle the attached event that initiated the process instead, but you would face several issues: the attached event may be marked handled by the base element class handling, and you need to use the accessor methods rather than true event syntax in order to attach handlers for attached events.