WPF Simplified Part 7: Routed Events

In plain old .NET, an event is based on the idea of pub-sub model, the publisher publishes an “event” and the subscriber is notified of the “event”. In WPF the idea is still the same, but updated to take into account the rather complex visual trees that often occur in WPF.

In WPF, when an event is raised on an element in the visual tree, the event will either travel up the visual tree (from the element till it reaches the root) or down the tree (from the root to the element). Which direction it will take depends on the event’s predefined “routing strategy”. What is meant by travel is that the same event will fire on the element’s parent and the parent’s parent and so on, if the event is moving up. That is, if you have a Button inside a Grid, and if a MouseRightButonDown event is raised on the Button, a MouseRightButtonDown event is then raised by the WPF framework on the Grid, because the MouseRightButonDown event moves up (“bubbles up”).

The advantage here is that a high-level visual element need not explicitly hook the same event on all of its descendants, it can hook into the event on itself and wait for the framework to raise the event on itself (just like the Grid hooks into it’s own MouseRightButonDown event and waits for the framework to raise a MouseRightButonDown event on the Grid). Descendants don’t need to explicitly notify parents when an event occurs.

Take this simple XAML,

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">

    <Grid>
        <Button Height="100" Width="100">
            <StackPanel Height="80" Width="80">
                <RadioButton Content="Click Me"/>
            </StackPanel>
        </Button>
    </Grid>
</Window>
 

Adding routed event handlers to this code,

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    MouseRightButtonDown="WindowMouseLeftButtonDownHandler"
    PreviewMouseRightButtonDown="WindowPreviewMouseLeftButtonDownHandler"
    Title="Window1" Height="300" Width="300">

    <Grid MouseRightButtonDown="GridMouseLeftButtonDownHandler"
        PreviewMouseRightButtonDown="GridPreviewMouseLeftButtonDownHandler">
 
        <Button Height="100" Width="100" 
            MouseRightButtonDown="ButtonMouseLeftButtonDownHandler"
            PreviewMouseRightButtonDown="ButtonPreviewMouseLeftButtonDownHandler">
 
            <StackPanel Height="80" Width="80" 
                MouseRightButtonDown="StackPanelMouseLeftButtonDownHandler"
                PreviewMouseRightButtonDown="StackPanelPreviewMouseLeftButtonDownHandler">
 
                <RadioButton Content="Click Me" 
                  MouseRightButtonDown="RadioButtonMouseLeftButtonDownHandler"
                  PreviewMouseRightButtonDown="RadioButtonPreviewMouseLeftButtonDownHandler"/>
            </StackPanel>
        </Button>
    </Grid>
</Window>
 

And in the code behind class add handlers that look like this,

private void WindowMouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e){
    Debug.WriteLine("Window caught event from " + e.OriginalSource.ToString());
}

private void GridMouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e){
    Debug.WriteLine("Grid caught event from " + e.OriginalSource.ToString());
}
 
// Add similar handlers for Button, StackPanel, and RadioButton

 
private void WindowPreviewMouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e){
    Debug.WriteLine("Window caught preview event from " + e.OriginalSource.ToString());
}

private void GridPreviewMouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e){
    Debug.WriteLine("Grid caught preview event from " + e.OriginalSource.ToString());
}
 
// Add similar handlers for Button, StackPanel, and RadioButton
 

Now if you run this example, and click on the radio button’s circle, you’ll see the output look like this,

Window caught preview event from Microsoft.Windows.Themes.BulletChrome
Grid caught preview event from Microsoft.Windows.Themes.BulletChrome
Button caught preview event from Microsoft.Windows.Themes.BulletChrome
StackPanel caught preview event from Microsoft.Windows.Themes.BulletChrome
RadioButton caught preview event from Microsoft.Windows.Themes.BulletChrome
RadioButton caught event from Microsoft.Windows.Themes.BulletChrome
StackPanel caught event from Microsoft.Windows.Themes.BulletChrome
Button caught event from Microsoft.Windows.Themes.BulletChrome
Grid caught event from Microsoft.Windows.Themes.BulletChrome
Window caught event from Microsoft.Windows.Themes.BulletChrome
 

As you can see, the PreviewMouseRightButtonDown routed event is a tunneling event, which means it starts from the root (Window) and goes to the source (RadioButon), whereas the MouseRightButtonDown routed event is a bubbling event, which means that it starts at the source and goes to the root of the tree. By convention tunneling events have a prefix of Preview. There is also a third “routing strategy”, called “direct”, which means that the event is only raised on the element and does not travel either up or down the visual tree.

Input events are usually in pairs, like the MouseRightButtonDown and the PreviewMouseRightButtonDown both occur on a single user input. First the tunneling event is raised and travels its route (to the element from the root) and then the bubbling event is raised and it travels its route.

If you want to stop the tunneling or bubbling at any point (say at the Button level), simply set the Handled to true.

private void ButtonMouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e){
    Debug.WriteLine("Button caught " + e.OriginalSource.ToString());
    e.Handled = true;
}
 

Notice in the output that Grid and Window don’t receive the bubbling MouseLeftButtonDown routed event.

To get all the routed events raised by a class, use the EventManager class,

var events = EventManager.GetRoutedEvents();
foreach (var routedEvent in events)
{
    EventManager.RegisterClassHandler(typeof(Window1), routedEvent, new RoutedEventHandler(handler));
}

internal static void handler(object sender, RoutedEventArgs e)
{
    Debug.WriteLine(e.OriginalSource + "=>" + e.RoutedEvent);
}
 

Writing a custom routed event is similar to writing a dependency property,

public static readonly RoutedEvent MyRoutedEvent = EventManager.RegisterRoutedEvent(
                               "MyRoutedEvent",            // Name of the custom routed event
                               RoutingStrategy.Bubble,     // The routing strategy
                               typeof(RoutedEventHandler), // Type of the event handler
                               typeof(MyControl));         // The type of the owner of this routed event 

// Provide CLR property wrapper for the routed event
public event RoutedEventHandler MyEvent
{
    add { AddHandler(MyRoutedEvent, value); }
    remove { RemoveHandler(MyRoutedEvent, value); }
}
 

The tunneling and bubbling of a routed event occurs when every element in the route exposes that event. But WPF supports tunneling and bubbling of routed events through elements that don’t even define that event – this is possible via attached properties. 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), elements can handle events that are declared in a different element.

<Grid Button.Click="ButtonClickHandler">
    <Button Height="100" Width="100" Content="Some Text"/>
</Grid>
 

Grid doesn’t have a Click event, but WPF allows the Button.Click event to be raised on the Grid. The Grid can then handle this event in it’s handler “ButtonClickHandler”. Every routed event can be used as an attached event. We can also hook up the attached event in code,

<Grid x:Name="MyGrid">
    <Button Height="100" Width="100" Content="Some Text"/>
</Grid>
 
// In code...
this.MyGrid.AddHandler(Button.ClickEvent, new RoutedEventHandler(ButtonClickHandler));

Digg This
Advertisements

WPF Simplified Part 6: Attached Properties

Attached properties is a new construct in WPF that allows you to attach a property defined for an object to another  object when the second object has no definition or declaration of the property.

For example, consider the simple XAML,

    <Grid>
        <!-- A simple button -->
        <Button Height="100" Width="100" Content="Some Text" />
    </Grid>

 

Now say, we’d like to set the FontSize on the Button, this is simple,

    <Grid>
        <!-- Setting FontSize on the button -->
        <Button Height="100" Width="100" FontSize="20" Content="Some Text" />
    </Grid>
 

This was easy because Button has a dependency property called FontSize. Now say we have many buttons inside the Grid and we’d like to set all their FontSizes. This could be done with Styles, but for the purpose of this explanation we’ll do it via attached properties. The following gives a compile time error, because Grid does not have any Font related properties.

    <!-- ERROR: The property 'FontSize' was not found in type 'Grid' -->
    <Grid FontSize="20">
        <Button Height="100" Width="100" Content="Some Text" />
    </Grid>

 

But this works,

    <Grid TextBlock.FontSize="20">    
        <Button Height="100" Width="100" Content="Some Text" />
    </Grid>

 

How? FontSize is defined in WPF as an attached (read attachable) property of the TextBlock class. This attachable property defined in TextBlock (attached property provider) is attached to a completely different class (Grid) here. The end result is that Grid now effectively has a new property (FontSize) that the Button inherits. The code behind is a little bit more explanatory,

Grid grid = new Grid();
TextBlock.SetFontSize(grid, 20);
Button button = new Button() { Height = 100, Width = 100, Content = "Some Text" };
grid.Children.Add(button);

 

As you can see, the attached property is just a method call that associates an object with an otherwise-unrelated property. But why get the attached property FontSize from TextBlock? Why not from Control or Button or TextElement? We could have used TextElement in place of TextBlock in the examples above, the reason is that TextBlock and TextElement register FontSize as an attached property, while Control and Button do not.

Attached properties can be used as an extensibility mechanism,

// Define a class that derives from DependencyObject
public class MyClass : DependencyObject { }
 
// The attach the 'Tag' property to it
MyClass myClass = new MyClass();
myClass.SetValue(FrameworkElement.TagProperty, "My Data");

// Retrieve the 'Tag' property from it
string tagValue = (string)myClass.GetValue(FrameworkElement.TagProperty);
 

Declaring a custom attached property is similar to declaring a dependency property,

public static void SetMyAttachedProperty(DependencyObject target, int value)
{
    // Calls DependencyObject.SetValue on the passed in DependencyObject
    target.SetValue(MyAttachedProperty, value);
}

public static int GetMyAttachedProperty(DependencyObject target)
{
    // Calls the DependencyObject.GetValue
    return (int)target.GetValue(MyAttachedProperty);
}

public static readonly DependencyProperty MyAttachedProperty = DependencyProperty.RegisterAttached(
        "MyAttached",           // Name of the attached property
        typeof(int),            // Type of the attached property
        typeof(MyControl),      // Type of the owner
        new PropertyMetadata(0,
            new PropertyChangedCallback(OnValueChanged),  // Callback when the property changes
            new CoerceValueCallback(CoerceValue)),        // Callback when value coercion is required
        new ValidateValueCallback(IsValidValue));     // Callback for custom validation
 
// The validation callback
private static bool IsValidValue(object value)
{ /* Validate the set value */ } 
        
// The coercion callback
private static object CoerceValue(DependencyObject d, object value)
{ /* Adjust the value without throwing an exception */ } 
        
// The value changed callback 
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ /* Called every time the dependency property value changes */ }

 

Unlike dependency properties, attached properties are not wrapped in CLR properties. And in the case of attached properties the property values are stored on the element the consumes the property instead of the element that declares the property (which is where the value is stored for dependency properties).

Although setting attached properties via XAML require the presence of the static Set[AttachedProperty] method, in code we can call DependencyObject.SetValue directly. This means that we can use any dependency property as an attached property in procedural code, as we did above for the Tag dependency property.

WPF Simplified Part 5: Control Templates

Recall from my previous post, that elements in the WPF object tree which derive from Visual constitute the visual tree. The visual tree is thus all the WPF elements that come together to render a control’s (or a set of controls’) UI to the screen. Control templates are XAML declarations of the visual tree of a control.

Consider the simple case of a Button control,

<Grid>
        <!-- A simple button -->
        <Button Height="100" Width="100" Content="Some Text" />
    </Grid>

The Button has a default visual template, ie, a visual tree supplied by WPF. We want to override this implementation and provide our own custom UI for the button. To do this we’ll customize the Button control’s UI template (and thus it’s visual tree).

This is simple enough,

<Grid>    
    <Button Height="100" Width="100" Content="Some Text">

        <!-- Override the Button's UI template -->
        <Button.Template>
 
            <!-- Create a new ControlTemplate that targets a Button -->
            <ControlTemplate TargetType="{x:Type Button}">
 
                <!-- Override the default UI of the control with this custom UI -->
                <Grid>
                    <Ellipse Height="60" Width="60" Stroke="Red" Fill="LightGray" />
                    <!-- Take the control's Content (which is “Some Text”) and place here -->
                    <ContentPresenter Content="{TemplateBinding Content}" 
                           VerticalAlignment="Center" HorizontalAlignment="Center" />
                </Grid>
            </ControlTemplate>
        </Button.Template>
 
    </Button>
</Grid>
 

As you can see from the XAML above, we’ve overridden the button’s UI template and provided our own implementation which consists of a Grid, with an Ellipse inside it and the Button’s Content inside the Ellipse. Even the height and width of the button get overridden to the new values of 60.

image image
           Default WPF Button       After providing a custom UI 
      template for the button

 

Another way to do the same thing is similar to the way we define styles in WPF, like below,

<Grid>
    <Grid.Resources>
        <!-- Define a named template, with a target type -->
        <ControlTemplate x:Key="RoundButtonTemplate" TargetType="{x:Type Button}">
            <!-- Override the default UI of the control with this custom UI -->
            <Grid>
                <Ellipse Height="60" Width="60" Stroke="Red" Fill="LightGray" />
                <!-- Take the control's Content and place here -->
                <ContentPresenter Content="{TemplateBinding Content}" 
                        VerticalAlignment="Center" HorizontalAlignment="Center" />
            </Grid>
        </ControlTemplate>
    </Grid.Resources>

    <!—- Apply the 'RoundButtonTemplate' template to this button -->
    <Button Height="100" Width="100" Content="Some Text"
            Template="{DynamicResource RoundButtonTemplate}" />

    </Grid>

Note that although we have overridden the UI, the button’s functionality remains the same, all the same events are raised and all the same properties are still available.

You should use TemplateBinding to allow the control to specify property values as much as possible. For example,

<Grid>
    <Grid.Resources>
        <ControlTemplate x:Key="RoundButtonTemplate" TargetType="{x:Type Button}">
            <Grid>
                <!-- The Stroke and Fill properties are bound to the button's BorderBrush and Background -->
                <Ellipse Height="60" Width="60"
                         Stroke="{TemplateBinding BorderBrush}"
                         Fill="{TemplateBinding Background}" />

                  <ContentPresenter Content="{TemplateBinding Content}" 
                        VerticalAlignment="Center" HorizontalAlignment="Center" />
            </Grid>
        </ControlTemplate>
    </Grid.Resources>

    <!-- Apply the 'RoundButtonTemplate' template to this button -->
    <Button Height="100" Width="100" Content="Some Text"
            Template="{DynamicResource RoundButtonTemplate}"
            BorderBrush="Red" Background="LightGray" />

</Grid>

 

Control templates also work with styles and triggers. The syntax is straightforward,

<Grid>
    <Grid.Resources>

        <!-- First ControlTemplate definition for normal button state UI -->

        <ControlTemplate x:Key="RoundButtonTemplate" TargetType="{x:Type Button}">
            <Grid>
                <Ellipse Height="60" Width="60" x:Name="MyEllipse"
                         Stroke="{TemplateBinding BorderBrush}"
                         Fill="{TemplateBinding Background}" />
                <ContentPresenter Content="{TemplateBinding Content}" VerticalAlignment="Center" HorizontalAlignment="Center" />
            </Grid>
            <!-- Set a trigger to activate on mouseover -->
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="MyEllipse" Property="Fill" Value="Blue" />
                </Trigger>
            </ControlTemplate.Triggers>
         </ControlTemplate>

        <!-- Second ControlTemplate definition for clicked button state UI -–> 
        <ControlTemplate x:Key="RoundButtonPressedTemplate" TargetType="{x:Type Button}">
            <Grid>
                <Ellipse Height="60" Width="60"
                     Stroke="{TemplateBinding BorderBrush}"
                     Fill="Red" />
                <ContentPresenter Content="{TemplateBinding Content}" VerticalAlignment="Center" HorizontalAlignment="Center" />
            </Grid>
        </ControlTemplate>

        <!-- Create a style definition for the button -->

        <Style x:Key="RoundButtonStyle" TargetType="{x:Type Button}">
            <Setter Property="Template" Value="{DynamicResource RoundButtonTemplate}"/>
            <Style.Triggers>
                <Trigger Property="IsPressed" Value="True">
                    <Setter Property="Template" Value="{DynamicResource RoundButtonPressedTemplate}"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Grid.Resources>

    <!-- Apply the 'RoundButtonStyle' to this button -->
    <Button Height="100" Width="100" Content="Text"
            Style="{DynamicResource RoundButtonStyle}"
            BorderBrush="Red" Background="LightGray" />

</Grid>

The WPF  content model allows the Button’s content to be a tree of elements. In our examples, we have set the button’s content to “Some Text” like this,

<Button Height="100" Width="100" Content="Some Text" />

But the button’s Content could be set to a tree, like this,

<Button Height="100" Width="100">
    <Button.Content>
        <Grid>
            <StackPanel>
                <TextBlock Text="Some Text"/>
            </StackPanel>
        </Grid>
    </Button.Content>
</Button>