2014-07-21 4 views
3

EDIT: Я предполагаю, что я задал проблему 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; 
    } 
} 

не решить проблему либо. Я много искал и нашел много примеров того, как сделать событие с пузырьками, идущее от ребенка к родительскому, но я не смог найти ни одного полного примера, показывающего, как сделать туннелированное событие от родителя к ребенку.

ответ

5

Если вы проверить MSDN article on routed events in WPF более внимательно, вы увидите, что он говорит:

Bubble является наиболее распространенным и означает, что событие будет пузырь (размножать) до визуального дерева из элемента источника до тех пор, пока он не был обработан или не достигнет корневого элемента . Это позволяет обрабатывать событие на объекте дальше по иерархии элементов из исходного элемента.

Tunnel события идут в другом направлении, начиная с корневого элемента и перемещения вниз по дереву элементов, пока они не будут обработаны или достигают исходного элемента для события. Это позволяет восходящим элементам перехватывать событие и обрабатывать его до того, как событие достигнет исходного элемента. Туннельные события имеют свои имена, предваряемые предварительным просмотром по соглашению (например, PreviewMouseDown).

Это действительно счетчик интуитивно, но туннелируется событие распространяется в сторону исходного элемента. В вашем случае корень элемент MainWindow, но источник элемент на самом деле ChildControl.Когда вы подняли событие внутри MainWindow, это было как источник, так и корень .

Элемент источника - это элемент, на который вызывается метод RaiseEvent, даже если RoutedEvent не является членом этого элемента. Кроме того, поскольку 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 внутри родительского обработчика, обработчик ребенок не будет вызван.

[Update]

Если вы хотите, чтобы поднять событие из родительского контроля, но сделать ребенок контролировать источник события, вы можете просто вызвать общественный 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"))); 
    } 
} 

В зависимости от вашего фактического прецедента почти похоже, что вы хотите, чтобы родительское окно уведомляло дочернее управление без фактического туннелирования. В этом случае я не уверен, что вам нужны события? То есть что не так:

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); 
    } 
} 
+0

Хорошо, поэтому я неправильно использую туннелированные события. Я действительно не забочусь о том, как правильно делать туннельное событие. Я хочу, чтобы событие **, поднятое в родительском окне **, было подобрано на дочернем элементе управления, без необходимости явно указывать ребенку, кто является его родителем. Возможно ли это в WPF? Я обновил свой вопрос, в то время как ваш ответ действительно хорош и объясняет, почему мой пример не работает, он не решает проблему, которую я пытаюсь решить. –

+1

И если вы обновите свой вопрос, показывая, как решить мою настоящую проблему, оставьте свой исходный ответ тоже. Ваш текущий ответ - лучший, который я видел в Интернете, объясняя туннелированные события. –

Смежные вопросы