WPF Routed Events – Bubbling Several Layers Up

Recently, the WPF question of the day for me was how to bubble up a toggle button event through several layers with less code.  In WPF we can easily add layers without worrying about wiring up delegates for each level.

Most of the blogs and MSDN help pages were detailed but not obvious.  This simple example shows how a registered event in the lowest level user control can bubble up for a top level parent to handle the event.  The example bubbles up an event from the QueueButton user control (Level 4) to View2 (Level 3) , then View1 (Level 2), then the top parent Window1 (Level 1) .  There is no code behind nor any delegates to maintain at any level except the firing control and any listeners.  We should be able to add any number of views in the visual tree and still avoid any extra work.  The event is a Bubble event and not Tunnel, but that is an example for another day.  Tunneling can be thought of (informally) as a falling bubble from the top level of the visible tree to the source.  This lets each level receive the event like bubble up does, but knowing that the parent control had an option of acting on the event.

I hope this simple example helps someone keep the WPF code clean and simple.

-Vince

Window1 - Level 1 - The Top Level Listener:

XAML:

<Window x:Class="Routed.Window1"
    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
    Title="ToggleButton Event Bubbling Example" 
    xmlns:local="clr-namespace:Routed" 
    local:QueueButton.EnableQueue="Window_EnableQueue">
    <Grid>
        <StackPanel>
            <TextBlock Text="Window1" HorizontalAlignment="Center" />
            <TextBlock x:Name="ToggleStateTextBlock" HorizontalAlignment="Center" Margin="10" FontSize="14" />
            <local:View1 />
        </StackPanel>
    </Grid>
</
Window>

 

The Single Code-Behind Method:

private void Window_EnableQueue(object sender, RoutedEventArgs e)
{
    QueueButton queueButton = e.OriginalSource as QueueButton;
    if (null != queueButton)
        ToggleStateTextBlock.Text =
"Toggle Button IsChecked = " + queueButton.IsChecked.Value.ToString();
}

 

View1 - Level 2:  (No Code Behind in View1)

<UserControl x:Class="Routed.View1"
    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
    Height="200" Width="200"
    Background="Peru"
    xmlns:local="clr-namespace:Routed">
    <Grid>
        <TextBlock Text="View1" HorizontalAlignment="Center" />
        <local:View2 />
        </Grid>
</
UserControl>

 

View2 - Level 3: (No Code Behind in View2)

<UserControl x:Class="Routed.View2"
    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
    Height="150" Width="150"
    Background="ForestGreen"
    xmlns:local="clr-namespace:Routed">
    <Grid>
        <TextBlock Text="View2" HorizontalAlignment="Center" />
        <local:QueueButton />
    </Grid>
</
UserControl>

 

QueueButton - Level 4 - The Event Source

QueueButton XAML:

<ToggleButton x:Class="Routed.QueueButton"
    xmlns
=http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
    Height="60" Width="100">
    <Grid
>
        <TextBlock Text
="Toggle This!!!" />
    </Grid
>
</
ToggleButton>

 

QueueButton Code Behind:

using System.Windows;
using System.Windows.Controls.Primitives;
namespace Routed
{
        public partial class QueueButton : ToggleButton
        {
            public static readonly RoutedEvent EnableQueueEvent;

            static QueueButton()
            {
                QueueButton.EnableQueueEvent = EventManager.RegisterRoutedEvent("EnableQueue"
                RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(QueueButton));
            }

            public event RoutedEventHandler EnableQueue
            {
                add { AddHandler(QueueButton.EnableQueueEvent, value); }
                remove { RemoveHandler(QueueButton.EnableQueueEvent, value); }
            }

            public QueueButton()
            {
                InitializeComponent();
            }
            protected override void OnChecked(RoutedEventArgs e)
            {
                RaiseEvent(
new RoutedEventArgs(QueueButton.EnableQueueEvent, this));
            }

            protected override void OnUnchecked(RoutedEventArgs e)
            {
               RaiseEvent(
new RoutedEventArgs(QueueButton.EnableQueueEvent, this));
            }
        }
}

 

No Comments