Silverlight中的事件分为普通事件和冒泡路由事件,它并没有包括WPF中的隧道路由事件,在本章中将详细讲解冒泡路由事件和如何注册一个冒泡路由事件。

        一、细解冒泡路由事件

        冒泡路由事件可以比喻为:一个父对象X包含子对象A,在子对象A中没有事件处理程序,但是父对象X中有一个鼠标左击事件处理程序。当用户点击子孙对象A时,这个事件又鼠标左击冒泡传递到父对象X。父对象的事件处理程序就处理这次点击事件。

        总结出来就是:冒泡路由事件是从子孙的元素传递到父对象事件处理程序中进行处理的一种解决方案,直到这个事件传递到最上层根对象。

        如果子对象有这类路由事件(如:MouseLeftButtonDown)的处理程序,父对象也有这类路由事件 (如:MouseLeftButtonDown)的处理程序的时候,会出现什么情况呢?答案是既执行子对象的MouseLeftButtonDown处理 程序,又执行父对象的MouseLeftButtonDown处理程序。如果我们想在某个子对象触发MouseLeftButtonDown路由事件的时 候,只让该子对象执行MouseLeftButtonDown处理程序而父对象不执行它的MouseLeftButtonDown处理程序,我们应该如何 办呢?答案是设置事件的e.Handled=ture。中止事件的冒泡路由,表示这个事件已经处理完毕,不用继续冒泡往上传递。

        现在我们通过一个实例程序来看冒泡路由事件的处理,首先我们来看XAML代码:

 

  1. <Grid Width="200" Height="200" x:Name="GridA" HorizontalAlignment="Left" Background="AliceBlue" 
  2. MouseLeftButtonDown="LayoutRoot_MouseLeftButtonDown"
  3. <Ellipse Height="44" HorizontalAlignment="Left" Fill="DarkKhaki" Name="ellipseFirst" 
  4. Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Width="86" /> 
  5. <Ellipse Height="44" HorizontalAlignment="Left" Fill="BlanchedAlmond" Margin="0,71,0,0" 
  6. Name="ellipseSecond" Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" 
  7. Width="94" MouseLeftButtonDown="ellipse2_MouseLeftButtonDown" /> 
  8. </Grid> 

        我们为一个名为GridA的Grid对象下添加两个子对象圆,这两个圆的Name分别是ellipseFirst和 ellipseSecond,ellipseFirst没有任何的事件处理程序,而ellipseSecond有一个事件处理程序 ellipse2_MouseLeftButtonDown,父对象有一个事件处理程序LayoutRoot_MouseLeftButtonDown。

 

  1. #region 路由事件的原理 
  2. private void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
  3. //正在调用事件处理程序的对象 
  4. FrameworkElement grid = sender as FrameworkElement; 
  5. //获取到触发此次事件的对象 
  6. FrameworkElement ellipse = e.OriginalSource as FrameworkElement; 
  7. MessageBox.Show("引发事件的子对象名是:" + ellipse.Name + 
  8. "----子对象事件冒泡上来触发并且产生事件的父对象名是:" + grid.Name); 
  9. private void ellipse2_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
  10. //获取到触发此次事件的对象 
  11. FrameworkElement ellipse = e.OriginalSource as FrameworkElement; 
  12. //通过e.Handled设置为ture表示路由事件已处理拦截了事件, 
  13. //不必冒泡到父对象的LayoutRoot_MouseLeftButtonDown去处理。 
  14. MessageBox.Show("引发事件的对象名是:" + ellipse.Name + ",路由事件被拦截"); 
  15. e.Handled = true
  16. #endregion 

        在鼠标左键点击事件中的e.OriginalSource可以获取到触发事件的源对象。

        在此实例中,我们点击圆ellipseFirst的时候,它没有任何事件处理程序,于是冒泡路由策略发生作用,此次点击事件由父对象的LayoutRoot_MouseLeftButtonDown事件处理程序进行处理。于是弹出以下界面:

        当我们点击圆ellipseSecond的时候,它自身有一个事件处理程序,于是首先执行它自身的左键点击处理程序ellipse2_MouseLeftButtonDown,弹出以下界面:

        二、注册冒泡路由事件

        在Silverlight中我们使用UIElement.AddHandler 方法为对象注册冒泡路由事件处理程序。

 

  1. public void AddHandler( RoutedEvent routedEvent,Delegate handler,bool handledEventsToo) 
  2. //routedEvent 
  3. // 类型:System.Windows.RoutedEvent 
  4. // 要处理的路由事件的标识符。 
  5.  
  6. //handler 
  7. // 类型:System.Delegate 
  8. // 对处理程序实现的引用。 
  9.  
  10. //handledEventsToo 
  11. // 类型:System.Boolean 
  12. // 如果为 true,则将按以下方式注册处理程序:即使路由事件在其事件数据中标记为已处理, 
  13. // 也会调用该处理程序;如果为 false,则使用默认条件注册处理程序, 
  14. // 即当路由事件已标记为已处理时,将不调用该处理程序。 

        下面我们来看一下实例的源码XAML文件代码如下:

  1. <Grid Width="200" Height="200" x:Name="GridB" HorizontalAlignment="Right" Background="Aqua"
  2. <Ellipse Height="44" HorizontalAlignment="Left" Fill="Coral" Name="ellipseThird" 
  3. Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Width="86" /> 
  4. <Ellipse Height="44" HorizontalAlignment="Left" Fill="DarkSalmon" Margin="0,71,0,0" 
  5. Name="ellipseFourth" Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" 
  6. Width="94" MouseLeftButtonDown="ellipse2_MouseLeftButtonDown" /> 
  7. </Grid> 

        在这里我们创建了一个名为GridB的Grid对象,在GridB对象内部我们创建两个圆分别是ellipseThird和ellipseFourth。 其中ellipseFourth中我们添加了一个事件处理程序ellipse2_MouseLeftButtonDown。       

  1. #region 为GridB控件添加一个路由事件处理程序,并且设置一直都要处理这个路由事件 
  2. private void UserControl_Loaded(object sender, RoutedEventArgs e) 
  3. //为GridB控件添加一个路由事件处理程序,并且设置一直都要执行此路由事件 
  4. this.GridB.AddHandler(FrameworkElement.MouseLeftButtonDownEvent, 
  5. new MouseButtonEventHandler(GridB_MouseLeftButtonDown), 
  6. true); 
  7.  
  8. private void GridB_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
  9. //处理这次事件的对象 
  10. FrameworkElement grid = sender as FrameworkElement; 
  11. //获取到触发事件的对象 
  12. FrameworkElement ellipse = e.OriginalSource as FrameworkElement; 
  13. MessageBox.Show("引发事件的子对象名是:" + ellipse.Name + 
  14. "----子对象事件冒泡上来触发并且产生事件的父对象名是:" + grid.Name); 
  15. #endregion 

        在上一段代码中我们在控件的Loaded事件中为GridB注册了一个事件处理程序名为:GridB_MouseLeftButtonDown。注意我们在使用AddHandle注册这个程序的时候,设置了handledEventsToo 为Ture,意味着我们不管子控件(ellipseThird和ellipseFourth)的自身事件处理程序中是否设置e.Handled是否为 Ture,我们都要执行父控件的事件处理程序GridB_MouseLeftButtonDown。反之如果我们设置handledEventsToo为False,则根据子控件的e.Handled是否为Ture来决定是否冒泡路由到父控件处理。

        下面我们来看看点击ellipseThird的时候弹出以下界面:

        下面我们来看看点击ellipseFourth的时候,因为设置handledEventsToo 为Ture,不管子控件(ellipseThird和ellipseFourth)的自身事件处理程序中是否设置e.Handled是否为Ture,我们 都要执行父控件的事件处理程序GridB_MouseLeftButtonDown,所以先后弹出以下两个窗口界面:

        本实例采用VS2010+Silverlight 4.0编写,如需源码请点击 SLRoutedEvent.zip 下载。