进行隧道事件的正确方法

时间:2014-07-21 22:42:11

标签: c# wpf event-handling routed-events

编辑:我想我问了一些XY问题。我并不真正关心让隧道事件有效,我关心的是从父窗口的代码中提取事件 以便接收并做出反应通过一个控件,该控件是该窗口的子控件,而无需明确告诉孩子其父母是谁并手动订阅该事件。


我正在尝试在父控件中引发事件并让子控件侦听该事件并对其做出反应。根据我的研究,我认为我只需要做RoutedEvent,但我做错了。

这是一个显示我尝试过的MCVE,它是一个带有窗口和UserControl的简单程序。

<Window x:Class="RoutedEventsTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:RoutedEventsTest"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Name="button" Click="ButtonBase_OnClick" HorizontalAlignment="Left" 
                VerticalAlignment="Top">Unhandled in parent</Button>
        <local:ChildControl Grid.Row="1"/>
    </Grid>
</Window>
using System.Windows;

namespace RoutedEventsTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            TestEventHandler += MainWindow_TestEventHandler;
        }

        void MainWindow_TestEventHandler(object sender, RoutedEventArgs e)
        {
            button.Content = "Handeled in parent";
            e.Handled = false;
        }

        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
            RaiseEvent(new RoutedEventArgs(TestEvent));
        }

        public static readonly RoutedEvent TestEvent = EventManager.RegisterRoutedEvent("TestEvent", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(MainWindow));

        public event RoutedEventHandler TestEventHandler
        {
            add { AddHandler(TestEvent, value); }
            remove { RemoveHandler(TestEvent, value); }
        }
    }
}
<UserControl x:Class="RoutedEventsTest.ChildControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
          <TextBlock Name="textBlock">Unhandeled in child</TextBlock>  
    </Grid>
</UserControl>
using System.Windows;
using System.Windows.Controls;

namespace RoutedEventsTest
{
    public partial class ChildControl : UserControl
    {
        public ChildControl()
        {
            InitializeComponent();
            AddHandler(MainWindow.TestEvent, new RoutedEventHandler(TestEventHandler));
        }

        private void TestEventHandler(object sender, RoutedEventArgs routedEventArgs)
        {
            textBlock.Text = "Handled in child";
            routedEventArgs.Handled = false;
        }
    }
}

当我运行程序时,父窗口会像我期望的那样做出反应,但是子UserControl永远不会运行我传递给AddHandler的委托。

将子控件更改为

public partial class ChildControl : UserControl
{
    public ChildControl()
    {
        InitializeComponent();
        AddHandler(TestEvent, new RoutedEventHandler(TestEventHandler));
    }

    public static readonly RoutedEvent TestEvent = EventManager.RegisterRoutedEvent("TestEvent", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(ChildControl));

    private void TestEventHandler(object sender, RoutedEventArgs routedEventArgs)
    {
        textBlock.Text = "Handled in child";
        routedEventArgs.Handled = false;
    }
}

也没有解决问题。我搜索了很多,发现了很多关于如何从孩子到父母的泡沫事件的例子,但我找不到一个完整的例子来说明如何从父母到孩子进行隧道事件。

1 个答案:

答案 0 :(得分:6)

如果您更仔细地查看MSDN article on routed events in WPFarchived),您会看到它说:

  

Bubble是最常见的,意味着事件将从源元素冒泡(传播)到可视树中,直到它被处理或到达根元素。这允许您在源元素的元素层次结构上进一步处理对象上的事件。

     

Tunnel事件从另一个方向开始,从根元素开始并遍历元素树,直到它们被处理或到达源元素为止事件。这允许上游元素拦截事件并在事件到达源元素之前处理它。隧道事件的名称以“按惯例预览”为前缀(例如PreviewMouseDown)。

确实反直觉,但是隧道事件将传播到源元素。在您的情况下, root 元素为MainWindow,但元素实际上是ChildControl。当您在MainWindow内引发事件时,恰好是 root

源元素是调用RaiseEvent方法的元素,即使RoutedEvent不是该元素的成员。此外,自{{1} }是一个公共方法,其他元素可以使另一个元素成为隧道事件的源元素。

换句话说,您需要一些类似的东西(添加RaiseEvent前缀因为这是隧道事件的惯例):

Preview

// ChildControl is the event source public partial class ChildControl : UserControl { public readonly static RoutedEvent PreviewEvent = EventManager.RegisterRoutedEvent( "PreviewEvent", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(ChildControl)); public ChildControl() { InitializeComponent(); AddHandler(PreviewEvent, new RoutedEventHandler((s, e) => Console.WriteLine("Child handler"))); } private void Button_Click(object sender, RoutedEventArgs e) { // make this control the source element for tunneling this.RaiseEvent(new RoutedEventArgs(PreviewEvent)); } }

MainWindow

如果您使用现有的隧道事件,事情会更简单,但请注意它们仍然在public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); AddHandler(ChildControl.PreviewEvent, new RoutedEventHandler((s, e) => Console.WriteLine("Parent handler"))); } } 上定义为源,而不是根元素:

Button

这也会将以下内容输出到控制台(鼠标向上):

// this uses the existing Button.PreviewMouseUpEvent tunneled event
public partial class ChildControl : UserControl
{
    public ChildControl()
    {
        InitializeComponent();
        AddHandler(Button.PreviewMouseUpEvent, 
          new RoutedEventHandler((s, e) => Console.WriteLine("Child handler")));
    }
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        AddHandler(Button.PreviewMouseUpEvent, 
          new RoutedEventHandler((s, e) => Console.WriteLine("Parent handler")));
    }
}

当然,如果在父处理程序中将Parent handler Child handler 属性设置为Handled,则不会调用子处理程序。

<强> [更新]

如果你想从父控件中引发事件,但让子控件成为事件的来源,你只需从外面调用子控件的公共true方法:

RaiseEvent

根据您的实际用例,您几乎看起来希望父窗口在没有实际隧道的情况下通知子控件。在那种情况下,我不确定你是否还需要活动?即这有什么不对的:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        AddHandler(ChildControl.PreviewEvent,
          new RoutedEventHandler((s, e) => Console.WriteLine("Parent handler")));
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        // raise the child event from the main window
        childCtrl.RaiseEvent(new RoutedEventArgs(ChildControl.PreviewEvent));
    }
}

// child control handles its routed event, but doesn't know who triggered it
public partial class ChildControl : UserControl
{
    public readonly static RoutedEvent PreviewEvent = 
        EventManager.RegisterRoutedEvent(
            "PreviewEvent",
            RoutingStrategy.Tunnel,
            typeof(RoutedEventHandler),
            typeof(ChildControl));

    public ChildControl()
    {
        InitializeComponent();
        AddHandler(PreviewEvent, 
          new RoutedEventHandler((s, e) => Console.WriteLine("Child handler")));
    }
}