WPF路由事件:冒泡、隧道与直接全解析

路由事件的三类传播方式

  • 冒泡事件(Bubbling)

    事件从源元素向上传递到根元素(如Button.Click)。

    XML 复制代码
    <StackPanel Button.Click="StackPanel_Click">
        <Button Content="Click Me"/>
    </StackPanel>
    csharp 复制代码
    private void StackPanel_Click(object sender, RoutedEventArgs e) {
        // 处理按钮点击事件
    }
  • 隧道事件(Tunneling)

    事件从根元素向下传递到源元素(命名以Preview开头,如PreviewMouseDown)。

    XML 复制代码
    <StackPanel PreviewMouseDown="StackPanel_PreviewMouseDown">
        <Button Content="Click Me"/>
    </StackPanel>
    csharp 复制代码
    private void StackPanel_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
        // 在事件到达按钮前拦截
    }
  • 直接事件(Direct)

    仅触发在源元素上,不传播(如MouseEnter)。

  • 添加/移除事件处理器

    csharp 复制代码
    button.AddHandler(Button.ClickEvent, new RoutedEventHandler(HandleClick));
    button.RemoveHandler(Button.ClickEvent, new RoutedEventHandler(HandleClick));
  • 终止事件传播

    在事件处理器中设置e.Handled = true,阻止事件继续冒泡或隧道。

    csharp 复制代码
    private void HandleClick(object sender, RoutedEventArgs e) {
        e.Handled = true; // 停止传播
    }
  • 输入事件
    MouseDown(冒泡)、PreviewKeyDown(隧道)

  • 控件事件
    Button.Click(冒泡)、TextBox.TextChanged(直接)

  • 全局快捷键
    在窗口级处理PreviewKeyDown事件,拦截特定按键组合。

  • 控件组合交互
    父容器监听子控件的冒泡事件(如ListBox中按钮的点击)。

  • INotifyPropertyChanged实现:

    csharp 复制代码
    public class Model : INotifyPropertyChanged {
        private string _name;
        public string Name {
            get => _name;
            set { _name = value; OnPropertyChanged(nameof(Name)); }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName) => 
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

命令与MVVM

  • ICommand接口实现:

    csharp 复制代码
    public class RelayCommand : ICommand {
        private readonly Action _execute;
        public RelayCommand(Action execute) => _execute = execute;
        public bool CanExecute(object parameter) => true;
        public void Execute(object parameter) => _execute();
        public event EventHandler CanExecuteChanged;
    }
  • MVVM优势:解耦视图与逻辑,利于单元测试,支持设计时数据(d:DataContext)。

样式与模板

  • 控件模板示例:

    XML 复制代码
    <ControlTemplate TargetType="Button">
        <Border Background="{TemplateBinding Background}" CornerRadius="5">
            <ContentPresenter HorizontalAlignment="Center"/>
        </Border>
    </ControlTemplate>
  • 触发器类型:PropertyTriggerDataTriggerEventTrigger

性能优化

  • 虚拟化技术:VirtualizingStackPanel用于ListBox等控件,延迟加载可视项。

  • 依赖属性机制:静态注册节省内存,支持值继承、动画等。

    csharp 复制代码
    public static readonly DependencyProperty IsActiveProperty = 
        DependencyProperty.Register("IsActive", typeof(bool), typeof(MyControl));
    
    public bool IsActive {
        get => (bool)GetValue(IsActiveProperty);
        set => SetValue(IsActiveProperty, value);
    }

高级主题

  • 路由事件:Bubbling(冒泡)、Tunneling(隧道)、Direct(直接)。
  • 跨线程访问UI:Dispatcher.InvokeDispatcher.BeginInvoke
  • 自定义绘图:继承FrameworkElement,重写OnRender方法使用DrawingContext

调试技巧

  • 使用Snoop或WPF Inspector工具实时查看视觉树。
  • 绑定失败时查看Output窗口的绑定错误日志。
  • PresentationTraceSources.TraceLevel=High诊断绑定问题。
相关推荐
KmSH8umpK14 小时前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第三篇
redis·分布式·wpf
宝桥南山15 小时前
GitHub Models - 尝试一下使用GitHub Models
microsoft·ai·微软·c#·github·.netcore
读书札记202215 小时前
visual studio 调试技巧总结
ide·visual studio
hixiong12318 小时前
C# OpenvinoSharp部署INSID3
开发语言·人工智能·ai·c#·openvinosharp
星辰徐哥19 小时前
Unity C#入门:变量的定义与访问权限(public/private)
unity·c#·lucene
Shadow(⊙o⊙)19 小时前
智能指针、循环引用、锁、删除器
开发语言·c++·后端·visual studio
leoufung19 小时前
LeetCode 30:Substring with Concatenation of All Words 题解(含 C 语言 uthash 实现)
c语言·leetcode·c#
hacker70719 小时前
Visual Studio安装教程(C#开发版)
ide·c#·visual studio
SKY -dada19 小时前
Understand 使用教程
开发语言·c#·流程图·软件构建·敏捷流程·代码复审·源代码管理
KmSH8umpK20 小时前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案
redis·分布式·wpf