当前位置: 首页 > news >正文

从 WPF 到 Avalonia 的迁移系列实战篇2:路由事件的异同点与迁移技巧

从 WPF 到 Avalonia 的迁移系列实战篇2:路由事件的异同点与迁移技巧

我的GitHub仓库Avalonia学习项目包含完整的Avalonia实践案例与代码对比。
我的gitcode仓库是Avalonia学习项目。
文中主要示例代码均可在仓库中查看,涵盖核心功能实现与优化方案。
点击链接即可直接访问,建议结合代码注释逐步调试。

在 WPF 开发中,路由事件(Routed Event)是 UI 交互和控件通信的重要机制。而在 Avalonia 中,也提供了类似的事件机制,但实现方式和使用习惯与 WPF 有一定差异。本文将从概念、分类、注册、处理以及迁移技巧几个方面详细对比 WPF 和 Avalonia 的路由事件,帮助开发者顺利迁移项目。


一、路由事件的基本概念

WPF:

  • 路由事件是 UIElementContentElement 提供的一种事件传播机制。

  • 支持三种路由策略:

    1. 冒泡事件(Bubbling):事件从源控件向父控件逐层传递。
    2. 隧道事件(Tunneling):事件从根控件向源控件逐层传递,通常以 Preview 开头,如 PreviewMouseDown
    3. 直接事件(Direct):只在源控件触发,不向父控件传播。

Avalonia:

  • Avalonia 的路由事件机制与 WPF 类似,但没有 Preview 前缀,直接使用 RoutingStrategies 来指定策略。

  • 支持三种路由策略:

    1. Bubble(冒泡)
    2. Tunnel(隧道)
    3. Direct(直接)

⚠️ 区别:Avalonia 没有 WPF 的 PreviewXXX 命名约定,需要通过 RoutingStrategies.Tunnel 显式注册隧道事件。


二、路由事件的注册方式

1. WPF 注册路由事件

public static readonly RoutedEvent MyClickEvent =EventManager.RegisterRoutedEvent("MyClick",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(MyButton));public event RoutedEventHandler MyClick
{add { AddHandler(MyClickEvent, value); }remove { RemoveHandler(MyClickEvent, value); }
}

2. Avalonia 注册路由事件

public static readonly RoutedEvent<RoutedEventArgs> MyClickEvent =RoutedEvent.Register<MyButton, RoutedEventArgs>("MyClick",RoutingStrategies.Bubble);public event EventHandler<RoutedEventArgs> MyClick
{add { AddHandler(MyClickEvent, value); }remove { RemoveHandler(MyClickEvent, value); }
}

⚠️ 差异点:

  • Avalonia 的事件注册通过泛型指定控件类型和事件参数类型。
  • WPF 使用 EventManager.RegisterRoutedEvent,Avalonia 使用 RoutedEvent.Register
  • Avalonia 的路由策略枚举是 RoutingStrategies,而 WPF 是 RoutingStrategy

三、事件触发与处理

1. WPF 触发事件

RaiseEvent(new RoutedEventArgs(MyClickEvent));

2. Avalonia 触发事件

RaiseEvent(new RoutedEventArgs(MyClickEvent));

🔹 相同点:触发事件都使用 RaiseEvent,参数都是对应事件对象。
🔹 不同点:Avalonia 的 RoutedEventArgs 泛型更灵活,可携带自定义事件参数。

3. 事件处理方式

WPF:
myButton.AddHandler(MyButton.MyClickEvent, new RoutedEventHandler(OnMyClick));
Avalonia:
myButton.AddHandler(MyButton.MyClickEvent, OnMyClick);

Avalonia 的语法更简洁,但本质相同。


四、路由事件的迁移技巧

在从 WPF 迁移到 Avalonia 的过程中,有几个注意点:

  1. Preview 事件需要替换

    • WPF 中 PreviewMouseDown → Avalonia 中 PointerPressed 或自定义隧道事件。

    • 例如:

      AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel);
      
  2. 自定义事件注册

    • WPF 的 EventManager.RegisterRoutedEvent → Avalonia 的 RoutedEvent.Register
    • 需要指定控件类型和事件参数类型。
  3. 事件处理顺序

    • Avalonia 冒泡和隧道事件顺序与 WPF 相同:Tunnel → 源控件 → Bubble。
    • 如果依赖 Preview* 的事件拦截逻辑,需要明确使用 RoutingStrategies.Tunnel
  4. 事件参数自定义

    • Avalonia 推荐自定义事件参数时继承 RoutedEventArgs 并泛型化。
  5. 绑定命令替代

    • 在 WPF 中,有些路由事件用于触发 Command,在 Avalonia 中可以使用 ReactiveCommandInteraction 实现类似功能。

五、示例:WPF 与 Avalonia 对比

WPF

<Button Content="Click Me" PreviewMouseDown="Button_PreviewMouseDown"/>
private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{MessageBox.Show("WPF PreviewMouseDown");
}

Avalonia

<Button Content="Click Me" />
myButton.AddHandler(PointerPressedEvent, (s, e) =>
{Console.WriteLine("Avalonia PointerPressed (Tunnel equivalent)");
}, RoutingStrategies.Tunnel);

六、基于 BlinkingButton 控件的详细使用示例

在实例中,通过点击BlinkingButton控件,控制它的IsBlinking属性,触发定义的BlinkingStartedEvent 和BlinkingStoppedEvent 事件,控制闪烁,并且在Title上显示不同的文字,直观感受路由事件的用法。

WPF

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;namespace WpfDemo.control;public class BlinkingButton : Button
{public static readonly DependencyPropertyIsBlinkingProperty = DependencyProperty.Register(nameof(IsBlinking), typeof(bool), typeof(BlinkingButton), new PropertyMetadata(false, OnIsBlinkingChanged));// ================== 路由事件定义 ==================public static readonly RoutedEvent BlinkingStartedEvent =EventManager.RegisterRoutedEvent(nameof(BlinkingStarted),RoutingStrategy.Bubble, // 事件冒泡typeof(RoutedEventHandler),typeof(BlinkingButton));public static readonly RoutedEvent BlinkingStoppedEvent =EventManager.RegisterRoutedEvent(nameof(BlinkingStopped),RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(BlinkingButton));private Storyboard? _blinkStoryboard;static BlinkingButton(){DefaultStyleKeyProperty.OverrideMetadata(typeof(BlinkingButton),new FrameworkPropertyMetadata(typeof(BlinkingButton)));}public bool IsBlinking{get => (bool)GetValue(IsBlinkingProperty);set => SetValue(IsBlinkingProperty, value);}// CLR 封装public event RoutedEventHandler BlinkingStarted{add => AddHandler(BlinkingStartedEvent, value);remove => RemoveHandler(BlinkingStartedEvent, value);}public event RoutedEventHandler BlinkingStopped{add => AddHandler(BlinkingStoppedEvent, value);remove => RemoveHandler(BlinkingStoppedEvent, value);}private static void OnIsBlinkingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){var btn = (BlinkingButton)d;if ((bool)e.NewValue)btn.StartBlinking();elsebtn.StopBlinking();}private void StartBlinking(){if (_blinkStoryboard == null){var animation = new DoubleAnimation{From = 1.0,To = 0.3,Duration = new Duration(TimeSpan.FromSeconds(0.6)),AutoReverse = true,RepeatBehavior = RepeatBehavior.Forever};_blinkStoryboard = new Storyboard();_blinkStoryboard.Children.Add(animation);Storyboard.SetTarget(animation, this);Storyboard.SetTargetProperty(animation, new PropertyPath("Opacity"));}_blinkStoryboard.Begin();RaiseEvent(new RoutedEventArgs(BlinkingStartedEvent, this));}private void StopBlinking(){_blinkStoryboard?.Stop();Opacity = 1.0;// 触发路由事件RaiseEvent(new RoutedEventArgs(BlinkingStoppedEvent, this));}
}
<WindowHeight="300"Title="MainWindow"Width="600"mc:Ignorable="d"x:Class="WpfDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:control="clr-namespace:WpfDemo.control"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><Grid><Grid.RowDefinitions><RowDefinition Height="100" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><control:BlinkingButtonBlinkingStarted="BlinkingButton_OnBlinkingStarted"BlinkingStopped="BlinkingButton_OnBlinkingStopped"Click="ButtonBase_OnClick"Content="点击我"FontSize="20"Grid.Row="0"Height="80"HorizontalAlignment="Center"IsBlinking="True"VerticalAlignment="Center"Width="300"x:Name="MyBlinkButton" /></Grid>
</Window>
using System.Windows;namespace WpfDemo;/// <summary>
///     Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{public MainWindow(){InitializeComponent();}private void ButtonBase_OnClick(object sender, RoutedEventArgs e){MyBlinkButton.IsBlinking = !MyBlinkButton.IsBlinking;}private void BlinkingButton_OnBlinkingStarted(object sender, RoutedEventArgs e){Title = "警告:按钮正在闪烁!";}private void BlinkingButton_OnBlinkingStopped(object sender, RoutedEventArgs e){Title = "路由事件 Demo";}
}

Avalonia

using System;
using System.Threading;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Styling;namespace AvaloniaDemo.Controls;public class BlinkingButton : Button
{public static readonly StyledProperty<bool> IsBlinkingProperty =AvaloniaProperty.Register<BlinkingButton, bool>(nameof(IsBlinking));// 路由事件定义public static readonly RoutedEvent<RoutedEventArgs> BlinkingStartedEvent =RoutedEvent.Register<BlinkingButton, RoutedEventArgs>(nameof(BlinkingStarted),RoutingStrategies.Bubble);public static readonly RoutedEvent<RoutedEventArgs> BlinkingStoppedEvent =RoutedEvent.Register<BlinkingButton, RoutedEventArgs>(nameof(BlinkingStopped),RoutingStrategies.Bubble);private Animation? _blinkAnimation;private CancellationTokenSource? _cts;public BlinkingButton(){// 监听属性变化this.GetObservable(IsBlinkingProperty).Subscribe(OnIsBlinkingChanged);}public bool IsBlinking{get => GetValue(IsBlinkingProperty);set => SetValue(IsBlinkingProperty, value);}// CLR 包装public event EventHandler<RoutedEventArgs>? BlinkingStarted{add => AddHandler(BlinkingStartedEvent, value);remove => RemoveHandler(BlinkingStartedEvent, value);}public event EventHandler<RoutedEventArgs>? BlinkingStopped{add => AddHandler(BlinkingStoppedEvent, value);remove => RemoveHandler(BlinkingStoppedEvent, value);}private void OnIsBlinkingChanged(bool isBlinking){if (isBlinking)StartBlinking();elseStopBlinking();}private void StartBlinking(){_blinkAnimation ??= new Animation{Duration = TimeSpan.FromSeconds(1.2),IterationCount = IterationCount.Infinite,Children ={new KeyFrame{Cue = new Cue(0d),Setters = { new Setter(OpacityProperty, 1.0) }},new KeyFrame{Cue = new Cue(0.5d),Setters = { new Setter(OpacityProperty, 0.3) }},new KeyFrame{Cue = new Cue(1d),Setters = { new Setter(OpacityProperty, 1.0) }}}};// 取消上一次动画_cts?.Cancel();_cts = new CancellationTokenSource();_blinkAnimation.RunAsync(this, _cts.Token);// 触发路由事件RaiseEvent(new RoutedEventArgs(BlinkingStartedEvent));}private void StopBlinking(){if (_blinkAnimation != null){_cts?.Cancel(); // 立即停止动画_cts = null;Opacity = 1.0;}// 触发路由事件RaiseEvent(new RoutedEventArgs(BlinkingStoppedEvent));}
}
<WindowHeight="300"Icon="/Assets/avalonia-logo.ico"Title="AvaloniaDemo"Width="600"d:DesignHeight="300"d:DesignWidth="600"mc:Ignorable="d"x:Class="AvaloniaDemo.Views.MainWindow"x:DataType="vm:MainWindowViewModel"xmlns="https://github.com/avaloniaui"xmlns:controls="clr-namespace:AvaloniaDemo.Controls"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:vm="using:AvaloniaDemo.ViewModels"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><Design.DataContext><!--This only sets the DataContext for the previewer in an IDE,to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs)--><vm:MainWindowViewModel /></Design.DataContext><Grid><Grid.RowDefinitions><RowDefinition Height="100" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><controls:BlinkingButtonBlinkingStarted="BlinkingButton_OnBlinkingStarted"BlinkingStopped="BlinkingButton_OnBlinkingStopped"Click="Button_OnClick"Content="点击我"Grid.Row="0"Height="80"HorizontalAlignment="Center"IsBlinking="False"VerticalAlignment="Center"Width="300"x:Name="BlinkBtn" /></Grid>
</Window>
using Avalonia.Controls;
using Avalonia.Interactivity;namespace AvaloniaDemo.Views;public partial class MainWindow : Window
{public MainWindow(){InitializeComponent();}private void Button_OnClick(object? sender, RoutedEventArgs e){BlinkBtn.IsBlinking = !BlinkBtn.IsBlinking;}private void BlinkingButton_OnBlinkingStarted(object? sender, RoutedEventArgs e){Title = "⚠ 警告:按钮正在闪烁!";}private void BlinkingButton_OnBlinkingStopped(object? sender, RoutedEventArgs e){Title = "路由事件 Demo";}
}

七、小结

特性WPFAvalonia迁移技巧
冒泡事件RoutingStrategy.BubbleRoutingStrategies.Bubble保持原有逻辑即可
隧道事件Preview* / RoutingStrategy.TunnelRoutingStrategies.Tunnel用 Tunnel 代替 Preview* 前缀
直接事件RoutingStrategy.DirectRoutingStrategies.Direct基本一致
自定义事件注册EventManager.RegisterRoutedEventRoutedEvent.Register<T, Args>泛型指定控件类型和事件参数
添加处理器AddHandlerAddHandler语法稍有不同,Avalonia 可省略委托类型
事件参数RoutedEventArgs / MouseEventArgsRoutedEventArgs / PointerEventArgs可自定义泛型事件参数

迁移过程中核心是理解 Avalonia 没有 Preview 前缀,而是通过 RoutingStrategies 指定策略,其他大部分逻辑与 WPF 类似,代码调整量不会太大。


通过掌握上述技巧,WPF 路由事件的迁移到 Avalonia 将变得顺畅,也为后续控件交互、命令绑定和自定义控件开发打下基础。


我的GitHub仓库Avalonia学习项目包含完整的Avalonia实践案例与代码对比。
我的gitcode仓库是Avalonia学习项目。
文中主要示例代码均可在仓库中查看,涵盖核心功能实现与优化方案。
点击链接即可直接访问,建议结合代码注释逐步调试。

http://www.dtcms.com/a/354655.html

相关文章:

  • Linux下OpenRadioss源码编译安装及使用
  • Shell 字符串操作与运算符
  • 利用ChatGPT打造行业LLM大模型应用
  • 外部请求至k8s集群内部对应节点全流程介绍
  • 使用docker搭建嵌入式Linux开发环境
  • HTML5七夕节网站源码
  • Java:TCP/UDP网络编程
  • DevOps篇之利用Jenkins实现多K8S集群的版本发布
  • Docker-compose常用命令
  • Helm 在 K8s 中的常见应用场景
  • 【K8s】整体认识K8s之K8s的控制器
  • Node.js + MongoDB 搭建 RESTful API 实战教程
  • 从入门到入土之——奇异值分解(SVD)
  • 重塑可观测性成本:解析Coralogix的智能成本优化之道
  • 深入浅出:贴片式eMMC存储与国产芯(君正/瑞芯微)的协同设计指南
  • GitHub 宕机自救指南:确保开发工作不间断
  • 学习做动画6.瞄准偏移
  • 5.2 I/O软件
  • STL库——list(类函数学习)
  • 搭建私有云3步法:cpolar简化Puter本地云端配置
  • leetcode238:除自身以外的数组的乘积(前缀和思想)
  • Fair Federated Learning with Biased Vision-Language Models
  • 一文读懂:自然语言处理中的语义理解技术
  • C# Deconstruct | 简化元组与对象的数据提取
  • 秋招笔记-8.28
  • 如何获取文件的MD5码
  • 读大语言模型09超级智能
  • 完整代码注释:实现 Qt 的 TCP 客户端,实现和服务器通信
  • 从集线器到路由器:计算机网络演进之路
  • 实现微信小程序的UniApp相机组件:拍照、录像与双指缩放