[This topic is pre-release documentation and is subject to change in future releases of Microsoft Silverlight.]
Writing Handlers for Silverlight Input Events
Introduction
You have several choices for how you handle events that originate from elements in your Microsoft Silverlight-based application:
-
You can write JavaScript handlers, which are interpreted by the Silverlight plug-in and passed to the hosting browser's script engine.
-
You can write handlers in a dynamic language that targets managed code, such as IronPython or Managed JScript. These handlers are not compiled until run time. Support for dynamic languages in managed code is provided by the dynamic language runtime (DLR). For more information, see Programming Silverlight with Dynamic Languages.
-
You can write handlers in a managed code language such as C# or Visual Basic. These handlers are written in a code-behind file that is compiled along with the XAML page that references the handlers.
This topic describes the third option. It explains how to write event handlers in C# or Visual Basic, reference the handlers from XAML, and then compile the XAML and its code-behind as part of an assembly.
Prerequisites (available from the Silverlight download site):
-
Silverlight version 2 Beta 1.
-
Microsoft Visual Studio 2008.
-
Silverlight Tools Beta 1 for Visual Studio 2008.
This topic also assumes that you have created a basic Silverlight project. (See Creating an Application for Silverlight for instructions.)
Creating the Project and the Basic XAML
A project that is based on the Visual Studio Silverlight templates will choose a default class and namespace. x:Class will already be declared with these values.
To create the project and edit the XAML file
-
Create a new application for Silverlight as described in Creating an Application for Silverlight. However, choose the Generate a test HTML page ... option for the hosting HTML page (do not create a Web site). You can choose either a C# or Visual Basic project (further procedures will account for either language choice).
-
Open Solution Explorer to view the project that you created from the default Silverlight template.
-
Open Page.xaml for editing.
-
If the root UserControl defines a Height and Width, delete those attributes.
-
Replace the Grid element with a StackPanel element, and give it a visible background. Paste the following over the existing Grid and it closing tag:
<StackPanel x:Name="LayoutRoot" Background="OldLace">
</StackPanel>
-
Add a UI element. For this example, add a new Canvas element as a child element of the StackPanel. Paste the following XAML as child elements of the initially empty StackPanel opening and closing tags. Note that you are not declaring the event or its handler yet.
<Canvas x:Name="Canvas1" Background="DarkKhaki" Width="100" Height="30">
<TextBlock>Canvas1</TextBlock>
</Canvas>
-
Place the text cursor just after the last attribute closing quote of Canvas, and type one space. Now type MouseLeftButtonDown, or better yet, type as much as you need to and use the IntelliSense dropdown choices to complete the typing for you. Notice that the attribute is now defined empty: MouseLeftButtonDown="" and also note that there is an additional UI dropdown visible from IntelliSense.
-
Press TAB, as instructed by the tooltip over the IntelliSense dropdown. This will both reference and define your handler. (The name will be Canvas1_MouseLeftButtonDown; you can change this name later.) Your XAML should resemble the following:
cs
<Canvas x:Name="Canvas1" Background="DarkKhaki" Width="100" Height="30" MouseLeftButtonDown="Canvas1_MouseLeftButtonDown">
<TextBlock>Canvas1</TextBlock>
</Canvas>
-
Do not compile yet. Your initial handler code is empty. You will write this code in the next procedure.
Writing the Handler
All event handlers for the XAML page must be defined in the class and assembly that is declared by x:Class in the XAML file. The code-behind and its XAML are joined by a partial class technique, and are eventually compiled together. By default, the templates generate XAML and its code-behind with matching names, and with the code-behind as a dependent file of its XAML in the project structure. For instance, you have been working with Page.xaml. Its code-behind file will be named either Page.xaml.cs or Page.xaml.vb depending on your language choice, and can be found "under" the XAML you just edited (click the + to the left of Page.xaml in Solution Explorer if you cannot see the code-behind file initially.)
To define the event handler in the managed code file
-
Open Page.xaml.cs or Page1.xaml.vb for editing.
For C#, the file will already have a namespace and class defined based on the project templates. For Visual Basic, the class is defined, and the namespace is set by the default namespace of the project.
-
Find the existing and empty Canvas1_MouseLeftButtonDown definition as a member of the Page class. This empty handler was placed here when you pressed TAB through IntelliSense in the previous procedure. Write a handler that uses the sender parameter to obtain a reference to the element where the handler was attached, and then sets a property value on it (using a new SolidColorBrush to replace the original value that was defined in XAML). Your code should resemble the following:
cs
private void Canvas1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Canvas c = sender as Canvas;
SolidColorBrush newColor = new SolidColorBrush(Color.FromArgb(255, 200, 77, 0));
c.Background = newColor;
}
vb
Private Sub Canvas1_MouseLeftButtonDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
Dim c As Canvas = sender
Dim newColor As SolidColorBrush = New SolidColorBrush(Color.FromArgb(255, 200, 77, 0))
c.Background = newColor
End Sub
-
Compile the application and run it (the quick way: press F5 to compile and run under debug). When you load the HTML page and its hosted Silverlight content and click the area under Canvas1, you should see it change color.
note
Managed events in Silverlight generally use either EventHandler or RoutedEventHandler, unless there is particular event data that motivates a different handler and a class derived from EventArgs/RoutedEventArgs. The MouseLeftButtonDown event does have mouse-specific event data available, so it uses the MouseEventHandler delegate.
Attaching Event Handlers in Code
Generally, it is convenient to attach the event handlers as part of the XAML markup. But you can also use the appropriate common language runtime (CLR) syntax to add event handlers as part of your CLR code. For example, for the C# language, you attach handlers to instance events with +=. The following procedure uses the class constructor to attach a Loaded event handler. The Loaded event handler in turn attaches the existing Canvas1_MouseLeftButtonDown handler to the MouseLeftButtonDown event on another Canvas.
To change the XAML and the managed code
-
Edit Page.xaml. After the first Canvas "button", insert another similar Canvas that does not have a XAML event handler in it.
cs
<Canvas x:Name="Canvas2" Background="Orchid" Width="100" Height="30">
<TextBlock>Canvas2</TextBlock>
</Canvas>
-
Edit Page.xaml.cs. In the existing Page class constructor, attach a handler for the Loaded event, after the InitializeComponent call. (You will define the new handler in the next step. Also, in C# at least, this is another case where IntelliSense will offer options as soon as you type the +=, and can also generate the skeleton of Page_Loaded for you to start the next step.)
cs
public Page()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Page_Loaded);
}
-
Define the Page_Loaded handler, which references your new Canvas2 and its MouseLeftButtonDown event, and attaches the Canvas1_MouseLeftButtonDown handler there also.
vb
Sub Page_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
AddHandler Canvas2.MouseLeftButtonDown, AddressOf Canvas1_MouseLeftButtonDown
End Sub
-
Save the files and compile and run the application. When you click the Canvas2 area, you should see that it also changes color.
Attaching Event Handlers in Visual Basic using Handles
Visual Basic has a different syntax for attaching handlers that relies on the keywords WithEvents and Handles. In the case of named elements that are created in XAML, you will not directly see the WithEvents keywords applied. In a Silverlight Visual Studio project, WithEvents keywords are added and field references (for accessing your named XAML elements in code) are created through generated code by default. Handles syntax will work only if you give the source element a unique x:Name; that name becomes your instance reference when you declare Handles.
To change the XAML and use a Handles event handler
-
Edit Page.xaml. After the first Canvas "button", insert another similar Canvas that does not have a XAML event handler in it.
cs
<Canvas x:Name="Canvas2" Background="Orchid" Width="100" Height="30">
<TextBlock>Canvas2</TextBlock>
</Canvas>
-
Edit Page.xaml.vb. Add another handler that does essentially the same thing as the first one (copy Canvas1_MouseLeftButtonDown and rename it Canvas2_MouseLeftButtonDown). This time, use Handles to assign the handler to the specific MouseLeftButtonDown event on the Canvas2 instance.
Private Sub Canvas2_MouseLeftButtonDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles Canvas2.MouseLeftButtonDown
Dim c As Canvas = sender
Dim newColor As SolidColorBrush = New SolidColorBrush(Color.FromArgb(255, 200, 77, 0))
c.Background = newColor
End Sub
note
If you give the first Canvas the x:Name Canvas1, and the handler implementations are identical in signature as well as in function, you can designate more than one instance to be handled by the same handler, for example: … Handles Canvas1.MouseLeftButtonDown, Canvas2.MouseLeftButtonDown. If you do this, you will also want to remove the MouseLeftButtonDown event attribute from Canvas1, because now you are declaring the handling in code.
Note that Handles syntax and using the XAML attributes for event handlers are ultimately mutually exclusive per event. You should generally choose one or the other approach throughout your application for consistency. If you use Handles, be aware that Silverlight supports the concept of routed events, which will be shown next. The ramification of routed events on Handles is that an event handler might handle routed events where the source was actually a different object in the object tree.
Input Events, Routed Events, and Input-Specific Event Data
So far, the aspects of event handling that have been shown are not that different from event handling in CLR managed code in general, with the exception of the XAML attribute technique for referencing the handler. Next, you will modify your handlers to illustrate two important concepts that are specific to Silverlight input events.
To add a handler on the StackPanel
-
Edit Page.xaml. Add a handler for the MouseLeftButtonDown event to the StackPanel parent of your two named Canvas elements. Use (or reproduce) the IntelliSense naming for the handler, LayoutRoot_MouseLeftButtonDown.
-
After the two named Canvas elements, but still within the StackPanel, add the following element:
<TextBlock Name="statusText"/>
-
Edit Page.xaml.cs or Page.xaml.vb. Write code for the IntelliSense-generated LayoutRoot_MouseLeftButtonDown handler. Paste the following:
cs
FrameworkElement fe = e.Source as FrameworkElement;
StringBuilder sb = new StringBuilder();
sb.Append("source: " + fe.Name + "\n");
sb.Append("relative x/y to source: " + e.GetPosition(fe) + "\n");
sb.Append("Silverlight content area x/y : " + e.GetPosition(null));
statusText.Text = sb.ToString();
vb
Dim fe As FrameworkElement = e.Source
Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder()
sb.Append("source: " + fe.Name + vbCrLf)
sb.Append("relative x/y to source: " + e.GetPosition(fe).ToString() + vbCrLf)
sb.Append("Silverlight content area x/y : " + e.GetPosition(Nothing).ToString())
statusText.Text = sb.ToString()
-
Compile and run the application.
Try clicking on your Canvas elements. The handler that changes their color executes as before. But now the event is also handled by the parent StackPanel, which updates the TextBlock with information that comes from the mouse event data. This illustrates the concept of a routed event; the event is raised (sourced) by the Canvas element, but then routes upwards in the object tree to also invoke handlers on the StackPanel parent. If you had put a handler on the root UserControl, the event would have routed there too.
Not all Silverlight events have this routing behavior. In fact only a few input events route in this way. These are each events defined by the UIElement base class, and are events that report input that comes from the mouse or keyboard. Check the SDK reference documentation for the events defined by UIElement and check the Remarks of each to see which events specifically have the routing behavior.
Notice that your LayoutRoot_MouseLeftButtonDown is reporting the value of Source from the event data. This is the element that actually raises the event, and would have the first opportunity to handle the event in its route. Occasionally you might click somewhere that does not report the name of its Source. This is probably because you happened to click the text of the TextBlock that has the text "Canvas1" or "Canvas2". That is the source in this case (it just isn't displaying a name in the status, because you did not give these TextBlock elements a name). So you actually had routing behavior occurring even before you introduced a handler on the StackPanel.
This also illustrates the value and purpose of routed input events. When you are compositing a UI, either as part of writing user code that puts together existing controls, or when you are defining a custom control's compositing, it is not always certain whether you want event handling to happen on the composite parts or on some common parent. Event routing provides the opportunity to handle either case, or both cases.
Your LayoutRoot_MouseLeftButtonDown also exercises the most useful mouse-event specific event data: the X and Y coordinates of where the mouse event occurs. This is reported by GetPosition. GetPosition is a method rather than a property (or pair of properties) so that you can easily evaluate the coordinates in your choice of relative frames of reference. To get coordinates within the overall Silverlight content area, you simply pass null to GetPosition. Otherwise, you can pass any element that is connected to the Silverlight object tree (it does not need to be an object that is directly involved in the event route). The most common object to pass is the event Source, so that you can preserve the relative frame of reference even if the event is handled on a parent element, but many other techniques are also possible. For example, you might pass reference frame objects that correct for relative transforms.
Using Handled in Event Handlers
Event routing is useful, but you do not always want every possible object on a route to invoke the handler it has for a particular input event. Fortunately, you can write event handlers that can report the fact that another handler has already been invoked at an earlier point in the route. To do this, you change the value of Handled in the event data, and then add checks for Handled to any other event handler that might be invoked along a route.
To add Handled to the Handler Logic
-
Edit Page.xaml.cs or Page.xaml.vb. Modify LayoutRoot_MouseLeftButtonDown handler, by adding a check for the value of Handled. if Handled is false previously, then the handler also sets Handled to true, so that any other handler along the route can use similar logic to determine if the event was already handled.
cs
if (!e.Handled)
{
e.Handled = true;
FrameworkElement fe = e.Source as FrameworkElement;
StringBuilder sb = new StringBuilder();
sb.Append("source: " + fe.Name + "\n");
sb.Append("relative x/y to source: " + e.GetPosition(fe) + "\n");
sb.Append("Silverlight content area x/y : " + e.GetPosition(null));
statusText.Text = sb.ToString();
}
vb
If (Not e.Handled) Then
e.Handled = True
Dim fe As FrameworkElement = e.Source
Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder()
sb.Append("source: " + fe.Name + vbCrLf)
sb.Append("relative x/y to source: " + e.GetPosition(fe).ToString() + vbCrLf)
sb.Append("Silverlight content area x/y : " + e.GetPosition(Nothing).ToString())
statusText.Text = sb.ToString()
End If
-
Modify Canvas1_MouseLeftButtonDown (and Canvas2_MouseLeftButtonDown if you have it for the Visual Basic example) to set Handled to true. Also, have your handler blank out the status text in this case.
cs
void Canvas1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
statusText.Text = "";
Canvas c = sender as Canvas;
SolidColorBrush newColor = new SolidColorBrush(Color.FromArgb(255, 200, 77, 0));
c.Background = newColor;
}
-
Compile and run the application.
Try clicking on your Canvas elements. The handler that changes their color executes as before. The event is NOT handled by the parent StackPanel, because your color-changing event handlers set Handled to true in the event data that is then passed further along the route, and your StackPanel handler checks for Handled.
If you are familiar with Windows Presentation Foundation (WPF) routed events, there is an important difference here in the routed event behavior. In WPF, setting an event's data to Handled=true would prevent most handlers from even invoking; only specially registered "handledEventsToo" handlers would still be invoked. In Silverlight, changing the Handled value does not influence the event routing behavior or handler invocation at all. It is up to your handler implementation to check for Handled. Basically you are using the routed event data's Handled value as a sentinel for application-specific event behavior, and you can choose your own "protocol" for what Handled values mean to your application logic, and for when you consider any given routed event as handled.
Keyboard Events
Keyboard events are also routed events. Rather than reporting the X/Y coordinate of the mouse pointer position at the time of the event, a keyboard event's data reports the specifics of the key that was pressed or released to initiate the event.
Keyboard events also introduce the concept of modifier keys. The most common modifier keys are the SHIFT or CTRL (control) keys. Usually you are not interested in the modifier keys in isolation, but are interested in whether a modifier key is pressed at the same time as another key's event is received.
So far, the elements you have added are not inherently focusable. You will need to add a true control to the UI so that it can be a stop in the tab sequence and thus gain focus. A control must be focused in order to raise a keyboard event, but the key events themselves are defined deeper than Control in the hierarchy so that events might be raised by a control but still can be routed to and handled by a non-control that cannot gain focus (such as a panel).
To add Handled to the Handler Logic
-
Edit Page.xaml.cs or Page.xaml.vb. Add a handler for the KeyDown event to the StackPanel named LayoutRoot. Use (or reproduce) the IntelliSense naming for the handler, LayoutRoot_KeyDown. Paste in the following code:
cs
private void LayoutRoot_KeyDown(object sender, KeyEventArgs e)
{
//check key value, we are looking for "G"
if (e.Key == Key.G)
{
//check modifiers for Ctrl
if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
statusText.Text = "Beep!";
}
}
}
vb
Sub LayoutRoot_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
'check key value, we are looking for "G"
If (e.Key = Key.G) Then
'check modifiers for Ctrl
If (Keyboard.Modifiers And ModifierKeys.Control) Then
statusText.Text = "Beep!"
End If
End If
End Sub
-
Edit Page.xaml. After the two Canvas elements, but before the TextBlock, paste the following:
<Button Content="Hello" />
-
Compile and run the application.
Tab into the application. Once tabbed focus is within Silverlight content, the only focusable element is the button. If you press the key combination Ctrl+G while the "Hello" button has focus, the event routes to the StackPanel parent, and the handler there changes the status text.
note
Key only reports portable key codes. Nonportable key codes are possible; these occur when a key is pressed that only exists on a particular platform. Nonportable key codes are not discussed in this Quickstart; see Keyboard Support.
The following runs and shows sources for the final code from this QuickStart.