Handling Input
There are three main kinds of user input for a Windows
application: mouse, keyboard, and ink.
There is also a higher-level concept of a command: a particular action
that might be accessible through several different inputs such as keyboard
shortcuts, toolbar buttons, and menu items.
Although controls typically act as the principal target for
input, all elements in a user interface can receive input. This is unsurprising
because controls rely entirely on the services of lower-level elements like Rectangle
and TextBlock in order to provide visuals. All of the input mechanisms
described in the following sections are therefore available on all
user-interface element types.
3.2.1. Routed Events
The .NET Framework defines a standard mechanism for exposing
events. A class may expose several events, and each event may have any number
of subscribers. Although WPF uses this standard mechanism, it augments it to
overcome a limitation: if a normal .NET event has no registered handlers, it is
effectively ignored.
Consider what this would mean for a typical WPF control. Most
controls are made up of multiple visual components. For example, even if you
give a button a very plain visual tree consisting of a single Rectangle
and provide a simple piece of text as the content, there are still two elements
present: the text and the rectangle. The button should respond to a mouse click
whether the mouse is over the text or the rectangle. In the standard .NET
event-handling model, this would mean registering a MouseLeftButtonUp event
handler for both elements.
This problem would get much worse when taking advantage of WPF's
content model. A Button is not restricted to having just simple text
as a captionit can contain any markup. The example in
Figure 3-2 is fairly unambitious, but even this has six elements: the
yellow outlined circle, the two dots for the eyes, the curve for the mouth, the
text, and the button background itself. Attaching event handlers for every
single element would be tedious and inefficient. Fortunately, it's not
necessary.
WPF uses routed events, which are more thorough than
normal events. Instead of just calling handlers attached to the element that
raised the event, a routed event will call all of the handlers attached to any
given node, from the originating element right up to the root of the
user-interface tree.
Example 3-1 shows
markup for the button shown in Figure
3-2. If one of the Ellipse elements inside the Canvas
were to receive input, event routing would enable the Button, Grid,
Canvas and Ellipse to receive the event, as
Figure 3-3 shows.
Example 3-1. Handling events in a user interface tree
<Button MouseLeftButtonDown="MouseButtonDownButton"
PreviewMouseLeftButtonDown="PreviewMouseButtonDownButton">
<Grid MouseLeftButtonDown="MouseButtonDownGrid"
PreviewMouseLeftButtonDown="PreviewMouseButtonDownGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Canvas MouseLeftButtonDown="MouseButtonDownCanvas"
PreviewMouseLeftButtonDown="PreviewMouseButtonDownCanvas"
Width="20" Height="18" VerticalAlignment="Center">
<Ellipse MouseLeftButtonDown="MouseButtonDownEllipse"
PreviewMouseLeftButtonDown="PreviewMouseButtonDownEllipse"
Canvas.Left="1" Canvas.Top="1" Width="16" Height="16"
Fill="Yellow" Stroke="Black" />
<Ellipse Canvas.Left="4.5" Canvas.Top="5" Width="2.5" Height="3"
Fill="Black" />
<Ellipse Canvas.Left="11" Canvas.Top="5" Width="2.5" Height="3"
Fill="Black" />
<Path Data="M 5,10 A 3,3 0 0 0 13,10" Stroke="Black" />
</Canvas>
<TextBlock Grid.Column="1">Foo</TextBlock>
</Grid>
</Button>
A routed event can be bubbling ,
tunneling , or direct
. A bubbling event starts by looking for
event handlers attached to the element that raised the event, and then looks at
its parent, and then its parent's parent, and so on, until it reaches the root
of the treethis order is indicated by the numbers on
Figure 3-3. A tunneling event works
in reverseit looks for handlers at the root of the tree first and works its way
down, finishing with the originating element.
Direct events are routed in the same way as normal .NET
event handlingonly handlers attached directly to the originating element are
notified. This is typically used for events that make sense only in the context
of their originating element. For example, it would be unhelpful if mouse enter
and leave events were bubbled or tunneledthe parent element is unlikely to care
when the mouse moves from one child element to another. At the parent element,
you would expect "mouse leave" to mean "the mouse has left the parent element,"
and because direct event routing is used, that's exactly what it does mean. If
bubbling were used, the event would effectively mean "the mouse has left an
element that is inside the parent, and is now inside another element that may
or may not be inside the parent."
With the exception of direct events
, WPF defines most routed events in pairsone bubbling and one tunneling. The
tunneling event name always begins with Preview and is raised first.
This gives parents of the originating element the chance to see the event
before it reaches the child. (Hence the "Preview" prefix.) The tunneling
preview event is followed directly by a bubbling event. In most cases, you will
only handle the bubbling eventthe preview would usually only be used if you
wanted to be able to block the event, or if you needed a parent to do something
in advance of normal handling of the event.
In Example 3-1,
most of the elements have event handlers that are specified for the MouseLeftButtonDown
and PreviewMouseLeftButtonDown eventsthe bubbling and tunneling events
respectively. Example 3-2 shows
the corresponding code-behind file.
Example 3-2. Handling events
using System;
using System.Windows;
using System.Diagnostics;
namespace EventRouting {
public partial class Window1 : Window {
public Window1( ) {
InitializeComponent( );
}
private void MouseButtonDownButton(object sender, RoutedEventArgs e)
{ Debug.WriteLine("MouseButtonDownButton"); }
private void PreviewMouseButtonDownButton(object sender, RoutedEventArgs e)
{ Debug.WriteLine("PreviewMouseButtonDownButton"); }
private void MouseButtonDownGrid(object sender, RoutedEventArgs e)
{ Debug.WriteLine("MouseButtonDownGrid"); }
private void PreviewMouseButtonDownGrid(object sender, RoutedEventArgs e)
{ Debug.WriteLine("PreviewMouseButtonDownGrid"); }
private void MouseButtonDownCanvas(object sender, RoutedEventArgs e)
{ Debug.WriteLine("MouseButtonDownCanvas"); }
private void PreviewMouseButtonDownCanvas(object sender, RoutedEventArgs e)
{ Debug.WriteLine("PreviewMouseButtonDownCanvas"); }
private void MouseButtonDownEllipse(object sender, RoutedEventArgs e)
{ Debug.WriteLine("MouseButtonDownEllipse"); }
private void PreviewMouseButtonDownTextBlock(object sender,
RoutedEventArgs e)
{ Debug.WriteLine("PreviewMouseButtonDownEllipse"); }
}
}
Each of the handlers prints out a debug message. Here is the
debug output we get when clicking on the TextBlock inside the Canvas:
PreviewButtonDownButton
PreviewButtonDownGrid
PreviewButtonDownCanvas
PreviewButtonDownEllipse
ButtonDownEllipse
ButtonDownCanvas
ButtonDownGrid
ButtonDownButton
This confirms that the Preview event is raised first. It also
shows that it starts from the Button element and works down, as we
would expect with a tunneling event. The bubbling event that follows starts
from the Ellipse element and works up.
This bubbling routing offered for most events means that you can
register a single event handler on a control, and it will receive events for
any of the elements nested inside of the control. You do not need any special
handling to deal with nested content or custom visual tree contentsevents
simply bubble up to the control and can all be handled there.
3.2.1.1. Halting event handling
There are some situations in which you might not want events to
bubble up. For example, you may wish to convert the event into something
elsethe Button element effectively converts MouseLeftButtonDown
and MouseLeftButtonUp events into Click events. It suppresses
the lower-level events so that only the Click event bubbles up out of
the control.
Any handler can prevent further processing of a routed event by
setting the Handled property of the RoutedEventArgs, as shown
in Example 3-3.
Example 3-3. Halting event routing with Handled
private void ButtonDownCanvas(object sender, RoutedEventArgs e) {
Debug.WriteLine("ButtonDownCanvas");
e.Handled = true;
}
Another reason for setting the Handled flag would be if
you wanted to prevent normal event handling. If you do this in a Preview
handler, not only will the tunneling of the Preview event stop, the
corresponding bubbling event that would normally follow will not be raised at
all, so it appears as though the event never occurred.
3.2.1.2. Determining the target
Although it is convenient to be able to handle events from a
group of elements in a single place, your handler might need to know which
element caused the event to be raised. You might think that this is the purpose
of the sender parameter of your handler. In fact, the sender always
refers to the object to which you attached the event handler. In the case of
bubbled and tunneled events, this often isn't the element that caused the event
to be raised. In Example 3-1,
the ButtonDownWindow handler's sender will always be the Window
itself.
Fortunately, it's easy to find out which element was the
underlying cause of the event. The RoutedEventArgs object passed as
the second parameter offers an OriginalSource property.
3.2.1.3. Routed events and normal events
Normal .NET events (or, as they are
sometime called, CLR events
) offer one advantage over routed-event syntax: many .NET languages have
built-in support for handling CLR events. Because of this, most routed events
are also exposed as CLR events. This provides the best of both worlds: you can
use your favorite language's event-handling syntax while taking advantage of
the extra functionality offered by routed events.
 |
This is possible thanks to the flexible design of
the CLR event mechanism. Although there is a standard simple behavior
associated with CLR events, the CLR designers had the foresight to realize that
some applications would require more sophisticated behavior. Classes are
therefore free to implement events however they like. WPF reaps the benefits of
this design by defining CLR events that are implemented internally as routed
events.
|
|
Example 3-1 and
Example 3-2 arranged for the event handlers to be connected by using
attributes in the markup. But we could have used the normal
C# event-handling syntax to attach handlers in the constructor instead. For
example, we could remove the MouseLeftButtonDown and PreviewMouseLeftButtonDown
attributes from Example 3-1 and
then modify the constructor from Example
3-2 as follows in Example 3-4.
Example 3-4. Attaching event handlers in code
...
public Window1( ) {
InitializeComponent( );
this.MouseLeftButtonDown += MouseButtonDownWindow;
this.PreviewMouseLeftButtonDown += PreviewMouseButtonDownWindow;
}
...
We could also do the same for the events from the nested
elements. We would just have to apply x:Name attributes in order to be
able to access those elements from C#.
The code-behind is usually the best place to attach event
handlers. If your user interface has unusual and creative visuals, there's a
good chance that the XAML file will effectively be owned by a graphic designer.
A designer shouldn't have to know what events a developer needs to handle, or
what the handler functions are called. So you will normally get the designer to
give elements names in the XAML, and the developer will attach handlers in the
code-behind.
3.2.2. Mouse Input
Mouse input is directed to whichever element is directly under
the mouse cursor. All user-interface elements derive from the UIElement
base class, which defines a number of mouse input events
. These are listed in Table 3-1.
Table 3-1. Mouse input events
|
Event
|
Routing
|
Meaning
|
GotMouseCapture
|
Bubble
|
Element captured the mouse.
|
LostMouseCapture
|
Bubble
|
Element lost mouse capture.
|
MouseEnter
|
Direct
|
Mouse pointer moved into
element.
|
MouseLeave
|
Direct
|
Mouse pointer moved out of
element.
|
PreviewMouseLeftButtonDown,
MouseLeftButtonDown
|
Tunnel, Bubble
|
Left mouse button pressed
while cursor inside element.
|
PreviewMouseLeftButtonUp,
MouseLeftButtonUp
|
Tunnel, Bubble
|
Left mouse button released
while cursor inside element.
|
PreviewMouseRightButtonDown,
MouseRightButtonDown
|
Tunnel, Bubble
|
Right mouse button pressed
while cursor inside element.
|
PreviewMouseRightButtonUp,
MouseRightButtonUp
|
Tunnel, Bubble
|
Right mouse button released
while cursor inside element.
|
PreviewMouseMove, MouseMove
|
Tunnel, Bubble
|
Mouse cursor moved while
cursor inside element.
|
PreviewMouseWheel, MouseWheel
|
Tunnel, Bubble
|
Mouse wheel moved while cursor
inside element.
|
QueryCursor
|
Bubble
|
Mouse cursor shape to be
determined while cursor inside element.
|
UIElement also defines a pair of properties that
indicate whether the mouse cursor is currently over the element: IsMouseOver
and IsMouseDirectlyOver. The distinction between these two properties
is that the former will be true if the cursor is over the element in question
or over any of its child elements, but the latter will be true only if the
cursor is over the element in question but not one of its children.
Note that the basic set of mouse events shown above does not
include a click event. This is because clicks are a higher-level concept than
basic mouse inputa button can be "clicked" with either the mouse or the
keyboard. Moreover, clicking doesn't necessarily correspond directly to a
single mouse eventusually, the user has to press and release the mouse button
while the mouse is over the control to register as a click. Accordingly, these
higher-level events are provided by more specialized element types. The Control
class adds a MouseDoubleClick and PreviewMouseDoubleClick event
pair. ButtonBase, the base class of Button, CheckBox,
and RadioButton, goes on to add a Click event.
 |
If you use a Shape (such as a Rectangle
or Path) with a Fill that uses transparency, the shape will
act as the target of the input if the mouse is over the shape. This can be
slightly surprising if you use a completely transparent brushthe shape will not
be visible, but it will still be the input target, regardless of what the mouse
may appear to be over. If you want a shape with a transparent fill that does
not capture mouse input, simply supply no Fill at allif the Fill
is null, (as opposed to being a completely transparent brush), the shape will
not act as an input target.!
|
|
Remember that if the reason you are considering handling a mouse
event is simply to provide some visible feedback to the user, writing an event
handler may be overkill. It is often possible to achieve the visible effects
you require entirely within the markup for a style by using declarative
property triggers and event triggers. (Triggers are discussed in
Chapter 5.)
3.2.3. Keyboard Input
Keyboard input introduces the idea of focus. Unlike the
mouse, there is no way for the user to move the keyboard over an element in
order to indicate the target for input. In Windows, a particular element is
designated as having the focus, meaning that it acts as the target for
keyboard input. The user sets the focus by clicking the mouse or tapping the
stylus on the control in question, or by using navigation keys such as the Tab
and cursor keys.
 |
In principle, any user interface element can
receive the focusthe IsFocused property is defined on UIElement,
the base class of FrameworkElement. However, the Focusable property
determines whether this feature is enabled on any particular element. By
default, this is true for controls and false for other elements.
|
|
Table 3-2 shows
the keyboard input events offered on
user-interface elements. All of these items use tunnel and bubble routing for
the Preview and main events, respectively.
Table 3-2. Keyboard input events
|
Event
|
Routing
|
Meaning
|
PreviewGotFocus, GotFocus
|
Tunnel, Bubble
|
Element received the focus.
|
PreviewLostFocus, LostFocus
|
Tunnel, Bubble
|
Element lost the focus.
|
PreviewKeyDown, KeyDown
|
Tunnel, Bubble
|
Key pressed.
|
PreviewKeyUp, KeyUp
|
Tunnel, Bubble
|
Key released.
|
PreviewTextInput, TextInput
|
Tunnel, Bubble
|
Element received text input.
|
Note that TextInput is not necessarily keyboard input.
It represents textual input in a device-independent way, so this event can also
be raised as a result of ink input.
3.2.4. Ink Input
The stylus used on tablet PCs and other ink-enabled systems has
its own set of events. Table 3-3
shows the ink input events offered on user-interface elements.
Table 3-3. Stylus and ink events
|
Event
|
Routing
|
Meaning
|
GotStylusCapture
|
Bubble
|
Element captured stylus.
|
LostStylusCapture
|
Bubble
|
Element lost stylus capture.
|
PreviewStylusDown, StylusDown
|
Tunnel, Bubble
|
Stylus touched screen over
element.
|
PreviewStylusUp, StylusUp
|
Tunnel, Bubble
|
Stylus left screen while over
element.
|
PreviewStylusEnter, StylusEnter
|
Tunnel, Bubble
|
Stylus moved into element.
|
PreviewStylusLeave, StylusLeave
|
Tunnel, Bubble
|
Stylus left element.
|
PreviewStylusInRange,
StylusInRange
|
Tunnel, Bubble
|
Stylus moved close enough to
screen to be detected.
|
PreviewStylusOutOfRange,
StylusOutOfRange
|
Tunnel, Bubble
|
Stylus moved out of detection
range.
|
PreviewStylusMove, StylusMove
|
Tunnel, Bubble
|
Stylus moved while over
element.
|
PreviewStylusInAirMove,
StylusInAirMove
|
Tunnel, Bubble
|
Stylus moved while over
element but not in contact with screen.
|
PreviewStylusSystemGesture,
StylusSystemGesture
|
Tunnel, Bubble
|
Stylus performed a gesture.
|
PreviewTextInput, TextInput
|
Tunnel, Bubble
|
Element received text input.
|
3.2.5. Commands
Many applications provide more than one way of performing
certain actions. For example, consider the act of creating a new file. You
might choose the File New menu item, or
you could click the corresponding toolbar button. Alternatively, you might use
a keyboard shortcut such as Ctrl+N. If the application provides a scripting
system, a script could provide yet another way to perform the action. The
outcome is the same whichever mechanism you use, because these are all just
different ways of invoking the same underlying command.
WPF has built-in support for this idea. The RoutedCommand
class represents a logical action that could be invoked in several ways. In a
typical WPF application, each menu item and toolbar button is associated with
an underlying RoutedCommand object.
RoutedCommand works in a very similar way to
lower-level forms of input. When a command is invoked, it raises two events: PreviewExecuteEvent
and ExecuteEvent. These tunnel and bubble through the element tree in
the same way as input events. The target of the command is determined by the
way in which the command was invoked. Typically, the target will be whichever
element currently has the focus, but RoutedCommand also provides an
overloaded Execute method, which may be passed a specific target
element.
There are several places from which you can get hold of a RoutedCommand.
A few controls offer commands. For example, the ScrollBar control
defines commands for each of its actions and makes these available in static
fields, such as LineUpCommand and PageDownCommand
. However, most commands are not unique to a particular control. Some
correspond to application-level actions such as "new file" or "open." Others
represent actions that would be invoked on a control, but could be implemented
by several different controls. For example, both TextBox and RichTextBox
can handle clipboard operations.
There is a set of classes that provide standard commands. These
classes are shown in Table 3-4.
This means you don't need to create your own RoutedCommand objects to
represent the most common operations. Moreover, many of these commands are
understood by built-in controls. For example, TextBox and RichTextBox
support many of these standard operations, including clipboard, undo, and redo
commands.
Table 3-4. Standard command classes
|
Class
|
Command types
|
ApplicationCommands
|
Commands common to almost all
applications. Includes clipboard commands, undo and redo, and document-level
operations (open, close, print, etc.).
|
ComponentCommands
|
Operations for moving through
information such as scroll up and down, move to end, and text selection.
|
EditCommands
|
Text-editing commands such as
bold, italic, and alignment.
|
MediaCommands
|
Media-playing operations such
as transport (play, pause, etc.), volume control, and track selection.
|
3.2.5.1. Handling commands
For a command to be of any use, something must respond to it.
This works slightly differently from handling normal
input events, because most commands are not defined by the controls that will
handle them. The classes in Table
3-4 define 95 commands, so if Control defined CLR events for
each distinct command, that would require 190 events once you include previews.
Not only would this be extremely unwieldy, it wouldn't even be a complete
solutionmost applications define their own custom commands as well as using
standard ones. The obvious alternative would be for the RoutedCommand itself
to raise events. However, each command is a singletonthere is only one ApplicationCommands.New
object, for example. If you were able to add a handler to a command object
directly, that handler would run any time the command was invoked anywhere in
your application. What if you just want to handle the command when it is
executed in a particular window?
The CommandBinding class solves these problems. A CommandBinding
object maps a specific RoutedCommand onto a handler function in the
scope of a particular user-interface element. It is this CommandBinding
that raises the PreviewExecute and Execute events, rather
than the UI element. These bindings are held in the CommandBindings property
defined by UIElement. Example
3-5 shows how to handle the ApplicationCommands.New command in
the code-behind file for a window.
Example 3-5. Handling a command
public partial class Window1 : Window {
public Window1( ) {
InitializeComponent( );
CommandBinding cmdBindingNew = new CommandBinding(ApplicationCommands.New);
cmdBindingNew.Execute += NewCommandHandler;
CommandBindings.Add(cmdBindingNew);
}
private void NewCommandHandler(object sender, ExecuteEventArgs e) {
if (unsavedChanges) {
MessageBoxResult result = MessageBox.Show(this,
"Save changes to existing document?", "New",
MessageBoxButton.YesNoCancel);
if (result == MessageBoxResult.Cancel) {
return;
}
if (result == MessageBoxResult.Yes) {
SaveChanges( );
}
}
// Reset text box contents
inputBox.Clear( );
}
}
This code relies on the bubbling nature of command routingthe
top-level Window element is unlikely to be the target of the command, as the
focus will usually belong to some child element inside the window. However, the
command will bubble up to the top. This routing makes it easy to put the
handling for commands in just one place.
The command that Example
3-5 handles is ApplicationCommands.New. If the set of standard
commands does not meet all of your application's needs, you can define custom
commands for specialized operations.
3.2.5.2. Defining commands
Example 3-6 shows
how to define a command. WPF uses object instances to establish the identity of
commands. If you were to create a second command of the same name, it would not
be treated as the same command. For this reason, commands are usually put in
static fields or properties.
Example 3-6. Creating a custom command
public partial class Window1 : Window {
public static RoutedCommand FooCommand;
static Window1( ) {
InputGestureCollection fooInputs = new InputGestureCollection( );
fooInputs.Add(new KeyGesture
(Key.F,
ModifierKeys.Control|ModifierKeys.Shift));
FooCommand = new RoutedCommand("Foo", typeof(Window1), fooInputs);
}
...
}
The Foo command created in
Example 3-6 would be handled using a CommandBinding just like
any other command. Of course, the user needs some way of invoking
the command.
3.2.5.3. Invoking commands
As well as defining a custom
command, Example 3-6 also
shows one way in which a command can be associated with user input. This
particular command has been configured to be invoked by a specific input gesture
. Two input-gesture types are currently supported: a MouseGesture
is a particular shape made with the mouse or stylus; a KeyGesture, as
used in Example 3-6, is a
particular keyboard shortcut. Many of the built-in commands are associated with
standard gestures. For example, ApplicationCommands.Copy is associated
with the standard keyboard shortcut for copying. (Ctrl+C in most locales.)
Although a command can be associated with a set of gestures when
it is created, you may wish to assign additional shortcuts for the command in
the context of a particular window. To allow this, user-interface elements have
an InputBindings property. This collection contains InputBinding
objects, which associate input gestures with commands. These augment the
default gestures associated with the command.
Input gestures such as keyboard shortcuts are not the only way
in which commands can be invoked. You can call the Execute method on
the command in order to invoke it from code. As
Example 3-7 shows, Execute is overloaded. If you pass no
parameters, the command target will be whichever element has the focus, just as
it would be if the command had been invoked using an input gesture. But you can
pass in a target element if you want.
Example 3-7. Invoking a command from code
ApplicationCommands.New.Execute( );
...or...
ApplicationCommands.New.Execute(targetElement);
You might think that you would write code like this in click
handlers for menu items or toolbar buttons. However, since commands are so
often associated with menu items or buttons on a toolbar, Button and MenuItem
both offer a Command property. This identifies the command to invoke
when the element is clicked. This provides a declarative way of connecting
user-interface elements to commands. You only need to write an event handler
for the command itself, rather than a handler for each UI element bound to the
command. Example 3-8 shows a Button
associated with the standard Copy command.
Example 3-8. Invoking a command with a Button
<Button Command="Copy">Copy</Button>
Because this example uses a standard command from the ApplicationCommands
class, we can use this short-form syntax, specifying nothing but the command
name. For commands not defined by the classes in
Table 3-4, a little more information is required. The full syntax for a
command attribute in XAML is:
[[xmlNamespacePrefix:]ClassName.]EventName
If only the event name is present, the event is presumed to be
one of the standard ones. For example, Undo is shorthand for ApplicationCommands.Undo.
Otherwise, you must also supply a class name, and possibly a namespace prefix.
The namespace prefix is required if you are using either custom commands or
commands defined by some third-party component. This is used in conjunction
with the Mapping XML processing instruction used to make external
types available in a XAML file. (See
Appendix A for more information on the Mapping processing
instruction.)
Example 3-9 shows
the use of the command-name syntax with all the parts present. The value of m:MyCommands.Foo
means that the command in question is defined in the MyLib.Commands.MyCommands
class in the mylib component and is stored in either a field or a
property called Foo.
Example 3-9. Using a custom command in XAML
<?Mapping ClrNamespace="MyLib.Commands" Assembly="mylib"
XmlNamespace="urn:mylib" ?>
<Window xmlns:m="urn:mylib" ...>
...
<Button Command="m:MyCommands.Foo">Custom Command</Button>
...
3.2.5.4. Enabling commands
As well as being executable, commands also offer a QueryEnabled
method. This returns a Boolean indicating whether the command can be invoked
right now; certain commands are only valid in particular contexts. This feature
can be used to determine whether items in a menu or toolbar should be grayed
out. Calls to the QueryEnabled method are handled in much the same way
as calls to Execute; CommandBinding objects are used to
handle the query. The binding raises a PreviewQueryEnabled and QueryEnabled
pair of events, which tunnel and bubble in the same way as the PreviewExecute
and Execute events. Example
3-10 shows how to handle this event for the system-defined Redo
command.
Example 3-10. Handling QueryEnabled
public Window1( ) {
InitializeComponent( );
CommandBinding redoCommandBinding =
new CommandBinding(ApplicationCommands.Redo);
redoCommandBinding.QueryEnabled += RedoCommandQueryEnabled;
CommandBindings.Add(redoCommandBinding);
}
void RedoCommandQueryEnabled(object sender, QueryEnabledEventArgs e) {
if (!CanRedo( )) {
e.IsEnabled = false;
}
}
 |
Unfortunately, the current build of WPF at the time
of writing does not gray out menu or toolbar items. It raises the QueryEnabled
event when a menu item is invoked and prevents the command from executing if it
is disabled, but does not currently provide any visual indication that a menu
item is disabled. We hope this will be addressed in a future release.
|
|
We've seen all of the various ways in which controls handle
input in WPF. Now let's look at the set of controls built into WPF.
|