正确的做隧道事件的方式 [英] The correct way to do a tunneled event

查看:123
本文介绍了正确的做隧道事件的方式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

编辑:我想我问了一个XY问题。我不太在意隧道事件的工作,我关心的是从父窗口 中的代码中提取事件 ,以便被接受并响应通过一个控件,该窗口是一个孩子,而不需要明确地告诉孩子其父母是谁,并手动订阅该事件。

EDIT: I guess I asked a bit of a XY Problem. I don't really care about getting tunneled events working, what I care about is getting a event raised from the code behind of the parent window to be picked up and reacted to by a control that is a child of that window without explicitly needing to tell the child who its parent is and manually subscribing to the event.

我试图在父控件中引发一个事件,让孩子控件监听该事件并对其进行响应。从我的研究中我以为我只需要做一个 RoutedEvent ,但是我做错了。

I am trying to raise a event in a parent control and having the child controls listen for that event and react to it. From my research I thought I just needed to do a RoutedEvent but I am doing something incorrect.

这是一个MCVE显示了我试过的内容,它是一个简单的程序,其中包含一个窗口和一个UserControl。

Here is a MCVE showing what I have tried, it is a simple program with a window and a UserControl inside of it.

<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

When I run the program the parent window reacts like I expect, but the child UserControl never runs its delegate that I passed in to 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;
    }
}

也没有解决问题。我搜索了很多,并发现了许多例子,说明如何从一个孩子到父母进行冒泡事件,但是我找不到一个完整的例子,显示了如何从一个父母到一个孩子做一个隧道事件。

did not fix the issue either. I searched a lot and found many examples of how to do a bubbled event going from a child to the parent, but I could not find a single full example showing how to do a tunneled event from a parent to a child.

推荐答案

如果您查看 MSDN关于WPF中路由事件的文章更仔细,你会看到它说:

If you check out the MSDN article on routed events in WPF more closely, you will see that it says:


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

Bubble is the most common and means that an event will bubble (propagate) up the visual tree from the source element until either it has been handled or it reaches the root element. This allows you to handle an event on an object further up the element hierarchy from the source element.

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

Tunnel events go in the other direction, starting at the root element and traversing down the element tree until they are handled or reach the source element for the event. This allows upstream elements to intercept the event and handle it before the event reaches the source element. Tunnel events have their names prefixed with Preview by convention (such as PreviewMouseDown).

确实是直观的,但是一个隧道事件会传播朝向源元素。在你的情况下,元素是 MainWindow ,但是元素实际上是 ChildControl 。当您在 MainWindow 中提出事件时,恰好是

It's indeed counter intuitive, but a tunneled event propagates towards the source element. In your case, root element is MainWindow, but the source element is actually the ChildControl. When you raised the event inside the MainWindow, that happened to be both the source and the root.

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

Source element is the element on which the RaiseEvent method is invoked, even if the RoutedEvent is not a member of that element. Also, since RaiseEvent is a public method, other elements can make another element become the source element for a tunneled event.

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

In other words, you would need something like (added the Preview prefix cause that's the convention for tunneled events):

// 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 作为源,而不是根元素:

Things are simpler if you use existing tunneled events, but note that they are still defined on the Button as the source, not the root element:

// 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")));
    }
}

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

This would also output the following to the console (on mouse up):

Parent handler
Child handler

当然,如果您将处理属性设置为 true 在父处理程序中,子句柄不会被调用。

And of course, if you set the Handled property to true inside the parent handler, child handler will not be invoked.

[更新]

如果你想从父控件引发事件,但是让孩子控制事件的源头,你可以简单地调用子控件的public RaiseEvent 方法从外面:

If you want to raise the event from the parent control, yet make the child control the source of the event, you can simply invoke the child control's public RaiseEvent method from outside:

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")));
    }
}

根据实际使用情况,它几乎看起来像您希望父窗口通知子控件,而不需要实际的隧道。在这种情况下,我不知道你是否甚至需要事件?即这样做有什么问题:

Depending on your actual use case, it almost looks like you want the parent window to notify the child control without actual tunneling. In that case, I am not sure if you even need events? I.e. what's wrong with simply this:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        childCtrl.DoSomething(this, "MainWindow just sent you an event");
    }
}

public partial class ChildControl : UserControl
{
    public ChildControl()
    {
        InitializeComponent();
    }

    public void DoSomething(UIElement sender, string message)
    {
        Console.WriteLine(sender.ToString() + ": " + message);
    }
}

这篇关于正确的做隧道事件的方式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆