WPF-路由事件

逻辑树与可视树

逻辑树

描述WPF界面元素的实际结构,XAML中所有的UI元素组成

如:


    
        
            
        
    

其中Window,Grid,DockPanel就是逻辑树

可视树

  • 界面上可见的元素构成
  • Visual或者Visual3D类中派生出来的类

可视树与逻辑树的遍历

  • LogicalTreeHelper:遍历逻辑树
  • VisualTreeHelper:遍历可视树

示例

新建一个项目,在视图文件中定义两个TreeView控件来展示逻辑树和可视树:


    
        
        
            
                
                
            
            
                逻辑树
                
            
            
                可视树
                
            
        
    

然后我们新建一个类WPFTreeHelper来实现两个静态方法,获取可视树和逻辑树:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace RoutedEventDemo
{
    class WPFTreeHelper
    {
        static string GetTypeDescription(object obj)
        {
            return obj.GetType().FullName;
        }

        // 获取逻辑树
        public static TreeViewItem GetLogicTree(DependencyObject dobj)
        {
            if (dobj == null)
            {
                return null;
            }
            var treeItem = new TreeViewItem { Header = GetTypeDescription(dobj), IsExpanded = true };
            foreach (var child in LogicalTreeHelper.GetChildren(dobj))
            {
                var item = GetLogicTree(child as DependencyObject); //递归调用
                if (item != null)
                {
                    treeItem.Items.Add(item);
                }
            }
            return treeItem;
        }

        // 获取可视树
        public static TreeViewItem GetVisualTree(DependencyObject dobj)
        {
            if (dobj == null)
            {
                return null;
            }
            var treeItem = new TreeViewItem { Header = GetTypeDescription(dobj), IsExpanded = true };
            for(int i=0; i

有了这两个静态方法就可以获取可视树和逻辑树了。然后我们在Button的点击事件中将获取到的逻辑树与可视树传入两个TreeView控件中展示:

namespace RoutedEventDemo
{
    /// 
    /// MainWindow.xaml 的交互逻辑
    /// 
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this.tvLogicTree.Items.Add(WPFTreeHelper.GetLogicTree(this));
            this.tvVisualTree.Items.Add(WPFTreeHelper.GetVisualTree(this));
        }
    }
}

显示结果:

可以看到逻辑树就和我们在xaml文件中看到的一样,可视树则更丰富一些,把所有可见的元素都显示了出来。

路由事件

什么是路由事件

  • 对元素树中多个侦听器调用处理的事件
  • 路由事件是一个CLR事件

冒泡事件和隧道事件

  • 冒泡事件:从源向它的父级元素传播
  • 隧道事件:从源向它的子级元素传播

路由事件的定义

  • 声明路由事件变量并注册
  • 通过标准的.NET事件进行包装
  • 产生传递事件

示例

新建一个用户控件RoutedEventControl


    

在控件的cs文件自定义路由事件,并且在按钮点击时传递路由事件:

namespace RoutedEventDemo
{
    /// 
    /// RoutedEventControl.xaml 的交互逻辑
    /// 
    public partial class RoutedEventControl : UserControl
    {
        public RoutedEventControl()
        {
            InitializeComponent();
        }

        // 声明和注册路由事件
        // RoutingStrategy.Bubble即冒泡事件
        // 冒泡事件命名规则:在名称后面加上'Event'
        public static readonly RoutedEvent MyClickEvent = EventManager.RegisterRoutedEvent("MyClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(RoutedEventControl));

        // 包装路由事件
        public event RoutedEventHandler MyClick
        {
            add { AddHandler(MyClickEvent, value); }
            remove { RemoveHandler(MyClickEvent, value); }
        }

        // 产生传递事件
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            RoutedEventArgs arg = new RoutedEventArgs();
            arg.RoutedEvent = MyClickEvent;  // RoutedEvent的赋值要在Source的赋值前面,否则会报错
            arg.Source = this;
            RaiseEvent(arg);
        }
    }
}

MainWindow中引入用户控件RoutedEventControl

    
        
    

MyClick就是我们定义的路由事件,我们再去MainWindow.xaml.cs中实现这个事件的处理方法:

private void RoutedEventControl_MyClick(object sender, RoutedEventArgs e)
{
    var source = e.Source;
    MessageBox.Show("Hello MyClickEvent " + source);
}

完成后,点击按钮的显示结果:

同样的,我们也可以注册隧道事件:

// 注册隧道事件
// 隧道事件命名规则:在名称前面加上'Preview'
public static readonly RoutedEvent PreviewMyClickEvent = EventManager.RegisterRoutedEvent("PreviewMyClick", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(RoutedEventControl));

public event RoutedEventHandler PreviewMyClick
        {
            add { AddHandler(PreviewMyClickEvent, value); }
            remove { RemoveHandler(PreviewMyClickEvent, value); }
        }

附加事件

什么是附加事件

  • 特殊的WPF路由事件
  • 用于非定义该事件的类

附加事件的定义

  • 声明附加事件变量并注册
  • 通过静态方法添加或移除事件
  • 引发附加事件

示例

新建一个用户控件AttachedEventControl


    

cs代码中定义我们的附加事件:

namespace RoutedEventDemo
{
    /// 
    /// AttachedEventControl.xaml 的交互逻辑
    /// 
    public partial class AttachedEventControl : UserControl
    {
        public AttachedEventControl()
        {
            InitializeComponent();
        }

        // 附加事件的声明和注册
        public static readonly RoutedEvent MyAttachedEvent = EventManager.RegisterRoutedEvent("MyAttached", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AttachedEventControl));

        // 静态添加方法
        public static void AddMyAttachedEventHandler(DependencyObject dp, RoutedEventHandler handler)
        {
            UIElement element = dp as UIElement;
            if (element != null)
            {
                element.AddHandler(MyAttachedEvent, handler);
            }
        }

        // 静态移除方法
        public static void RemoveMyAttachedEventHandler(DependencyObject dp, RoutedEventHandler handler)
        {
            UIElement element = dp as UIElement;
            if (element != null)
            {
                element.RemoveHandler(MyAttachedEvent, handler);
            }
        }

        // 引发附加事件
        // 这里是重写了鼠标左键点击事件
        protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnPreviewMouseLeftButtonDown(e);
            RoutedEventArgs arg = new RoutedEventArgs(MyAttachedEvent, this);
            RaiseEvent(arg);
        }
    }
}

MainWindow中引入用户控件AttachedEventControl


    

MainWindow.xaml.cs文件中添加我们定义的附加事件:

namespace RoutedEventDemo
{
    /// 
    /// MainWindow.xaml 的交互逻辑
    /// 
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            // 添加自定义的附加事件
            AttachedEventControl.AddMyAttachedEventHandler(this, new RoutedEventHandler(AttachedEventHandler));
        }
        // 附加事件处理代码
        private void AttachedEventHandler(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Hello, MyAttachedEvent");
        }
    }
}

实现效果: