2014-07-21 25 views
3

編輯:我想我問了一下一個XY問題。我真的不關心怎樣隧道事件的工作,我關心的是得到一個事件從代碼中提出的父窗口的後面由那就是窗口的子控件被拾起和反應而不需要明確地告訴孩子他們的父母是誰並手動訂閱該事件。正確的方法做一個隧道事件


我試圖提高在父控件的事件,並具有子控件監聽該事件並做出反應的。從我的研究中,我認爲我只需要做一個RoutedEvent,但我做了一些不正確的事情。

這裏是在什麼我已經嘗試了MCVE,這是一個簡單的程序有一個窗口,它內部的一個用戶控件。

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

當我運行像我期望的父窗口反應方案,但孩子用戶控件從來沒有運行它代表的是我通過中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父處理程序中,孩子處理器將不會被調用。

[更新]

如果你想提高從父控件的事件,但讓孩子控制事件的源,你可以簡單地從外部調用子控件的公共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

如果您更新顯示如何解決我的真實問題的問題,請留下原始答案。你目前的答案是我在互聯網上看到的最好的解釋隧道事件的答案。 –