WPF Concepts—Attached Events

Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio


Jump to: navigation, search
CSharp-Online.NET:Articles
.NET Articles

Important New WPF Concepts

© 2007 Sams Publishing

Attached Events

The tunneling and bubbling of a routed event is natural when every element in the tree exposes that event. But WPF supports tunneling and bubbling of routed events through elements that don't even define that event! This is possible thanks to the notion of attached events.

Attached events operate much like attached properties (and their use with tunneling or bubbling is very similar to using attached properties with property value inheritance). Listing 3.9 changes the About dialog again by handing the bubbling SelectionChanged event raised by its ListBox and the bubbling Click event raised by both of its Buttons directly on the root Window. Because Window doesn't define its own SelectionChanged or Click events, the event attribute names must be prefixed with the class name defining these events. Listing 3.10 contains the corresponding code-behind file that implements the two event handlers. Both event handlers simply show a MessageBox with information about what just happened.

Listing 3.9. The About Dialog with Two Attached Event Handlers on the Root Window

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="AboutDialog" ListBox.SelectionChanged="ListBox_SelectionChanged"
  Button.Click="Button_Click"
  Title="About WPF Unleashed" SizeToContent="WidthAndHeight"
  Background="OrangeRed">
  <StackPanel>
    <Label FontWeight="Bold" FontSize="20" Foreground="White">
      WPF Unleashed (Version 3.0)
    </Label>
    <Label>© 2006 SAMS Publishing</Label>
    <Label>Installed Chapters:</Label>
    <ListBox>
      <ListBoxItem>Chapter 1</ListBoxItem>
      <ListBoxItem>Chapter 2</ListBoxItem>
    </ListBox>
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
      <Button MinWidth="75" Margin="10">Help</Button>
      <Button MinWidth="75" Margin="10">OK</Button>
    </StackPanel>
    <StatusBar>You have successfully registered this product.</StatusBar>
  </StackPanel>
</Window>

Listing 3.10. The Code-Behind File for Listing 3.9

using System.Windows;
using System.Windows.Controls;
 
public partial class AboutDialog : Window
{
  public AboutDialog()
  {
    InitializeComponent();
  }
 
  void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
  {
    if (e.AddedItems.Count > 0)
      MessageBox.Show("You just selected " + e.AddedItems[0]);
  }
  void Button_Click(object sender, RoutedEventArgs e)
  {
    if (e.AddedItems.Count > 0)
      MessageBox.Show("You just clicked " + e.Source);
  }
}

Every routed event can be used as an attached event. The attached event syntax used in Listing 3.9 is valid because the XAML compiler sees the SelectionChanged .NET event defined on ListBox and the Click .NET event defined on Button. At run-time, however, AddHandler is directly called to attach these two events to the Window. Therefore, the two event attributes are equivalent to placing the following code inside the Window's constructor:

public AboutDialog()
{
  InitializeComponent();
  this.AddHandler(ListBox.SelectionChangedEvent,
    new SelectionChangedEventHandler(ListBox_SelectionChanged));
  this.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click));
}
Digging Deeper: Consolidating Routed Event Handlers
Because of the rich information passed to routed events, you could handle every event that tunnels or bubbles with one top-level "megahandler" if you really wanted to! This handler could examine the RoutedEvent object to determine which event got raised, cast the RoutedEventArgs parameter to an appropriate subclass (such as KeyEventArgs, MouseButtonEventArgs, and so on) and go from there.

For example, Listing 3.9 could be changed to assign both ListBox.SelectionChanged and Button.Click to the same GenericHandler method, defined as follows:

void GenericHandler(object sender, RoutedEventArgs e)
{
  if (e.RoutedEvent == Button.ClickEvent)
  {
    MessageBox.Show("You just clicked " + e.Source);
  }
  else if (e.RoutedEvent == ListBox.SelectionChangedEvent)
  {
    SelectionChangedEventArgs sce = (SelectionChangedEventArgs)e;
    if (sce.AddedItems.Count > 0)
      MessageBox.Show("You just selected" + sce.AddedItems[0]);
  }
}

This is also made possible by the delegate contravariance feature added in version 2.0 of the .NET Framework, enabling a delegate to be used with a method whose signature uses a base class of an expected parameter (e.g. RoutedEventArgs instead of SelectionChangedEventArgs). GenericHandler simply casts the RoutedEventArgs parameter when necessary to get the extra information specific to the SelectionChanged event.


Previous_Page_.gif Next_Page_.gif





Personal tools