正确的做隧道事件的方式 [英] The correct way to do a tunneled event
问题描述
编辑:我想我问了一个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屋!