为了能到远方,脚下的每一步都不能少.|

石起起

园龄:1年11个月粉丝:1关注:0

2025-02-26 18:40阅读: 4评论: 0推荐: 0

第5章-路由事件

Handler: 处理器
Preview: 预览、这指隧道
Raise: 引发
Bubble: 冒泡
Handled: 已处理

理解路由事件

事件路由允许源自某个元素的事件由另一个元素引发。

定义、注册和封装路由事件

public partial class Window1 : Window
{
	// 定义路由事件,必须是 static readonly
	// 类型 RoutedEvent
	public static readonly RoutedEvent TestEvent;

	public Window1()
	{
		InitializeComponent();
	}

	// 也可以写在外面吧
	static Window1()
	{
		// 在静态构造函数中注册路由事件
		// 使用 EventManager.RegisterRoutedEvent 注册
		Window1.TestEvent = EventManager.RegisterRoutedEvent(
			"Click",  // 事件名称
			RoutingStrategy.Bubble,  // 路由类型 Bubble:冒泡
			typeof(RoutedEventHandler),  // 定义事件处理程序语法的委托
			typeof(Window1)  // 拥有事件的类
			);
	}

	// 路由事件通过普通的 .NET 事件进行封装
	// 两个封装器:AddHandler 和 RemoveHandler
	// 这两个方法都在 FrameworkElement 基类中定义,并被每个 WPF 元素继承
	public event RoutedEventHandler Click
	{
		add { base.AddHandler(Window1.TestEvent, value)}
		remove { base.RemoveHandler(Window1.TestEvent, value)}
	}
}

👀 RoutingStrategy.Bubble 是枚举值,指示是冒泡路由还是隧道路由

参考文章:如何创建自定义路由事件 - WPF .NET | Microsoft Learn

共享路由事件

UIElement.MouseUpEvent = Mouse.MouseUpEvent.AddOwer(typeof(UIElement));

UIElement 类是所有普通元素的起点,通过 AddOwer() 方法征用事件,可令路由事件在类之间共享。

引发路由事件

RaiseEvent() 方法负责为每个已经通过 AddHandler() 方法注册的调用程序引发事件。

protected void OnTestEvent()
{
	RoutedEventArgs e = new RoutedEventArgs(Window1.TestEvent, this);
	base.RaiseEvent(e);
}

RoutedEventArgs 是事件参数类的基类。

处理路由事件

即关联事件处理程序。引发事件以后需要做出相应的处理。

方式一:
XAM 标记添加事件特性:(以“元素名_事件名”命名事件处理方法,没有名字的话使用“元素类型_事件名”)

<Image Name="img" MouseUp="img_MouseUp"/>

方式二:

img.MouseUp += new MouseButtonEventHandloer(img_MouseUp);
// 等效于
img.MouseUp += img_MouseUp; // 隐藏委托

方式三:

img.AddHandler(Image.MouseUpEvent, new MouseButtonEventHandler(img_MouseUp));
// 等效于
img.AddHandler(UIElement.MouseUpEvent, new MouseButtonEventHandler(img_MouseUp));

断开路由事件的几种写法:

img.MouseUp -= img_MouseUp;
img.RemoveHandler(Image.MouseUpEvent, new MouseButtonEventHandler(img_MouseUp));

事件路由

路由事件分三种:

  • 直接路由事件
  • 向上传递的冒泡路由事件
  • 向下传递的隧道路由事件

可以在路由路径中任意一层完成对事件的处理

每个冒泡路由事件都对应有一个以Privew开关的隧道路由事件。

private void img_MouseUp(object sender, MouseButtonEventArgs e)
{}

sender 参数提供了对整个链条上最后那个链接的引用。(就是最后一个对象)

⭐RoutedEventArgs 类属性说明:

  • Source: 指示引发了事件的对象;
  • OriginalSource: 指示最初是什么对象引发了事件。💡 OriginalSource 属性值通常与 Source 属性值相同。
  • RoutedEvent: 通过事件处理程序为触发的事件提供 RoutedEvent 对象。
  • Handled: 该属性允许终止事件的冒泡或隧道过程。

冒泡路由事件

protected int eventCounter = 0;

private void SomethingClicked(object sender, MouseButtonEventArgs e)
{
	eventCounter++;
	string message = "#" + eventCounter.ToString() + "\r\n" +
		" Sender: " + sender.ToString() + "\r\n" +
		" Source: " + e.Source + "\r\n" +
		" Original Source: " + e.OriginalSource;
	lstMessages.Items.Add(message);
	e.Handled = (bool)chkHandle.IsChecked; // 当为 true 时,终止路由传递
}

💡 大多数 WPF 元素没有提供 Click 事件,而是提供了更直接的 MouseDown 和 MouseUp 事件, Click 事件专用于基于按钮的控件。

❓处理挂起的事件

当设置第三个参数为 true 时,即使设置了 Handled 标志,也将接收到事件。
❌ 这一般是一种错误的做法,系统默认会挂起 MouseUp 事件而优先去处理 Click 事件。

cmdClear.AddHandler(UIElement.MouseUpEvent, new MouseButtonEventHandler(cmdClear_MouseUp), true);

⭐附加事件

<Grid Width="300"
          Height="300"
          Background="AliceBlue" Button.Click="DoSomething">
          
        <Grid Width="200"
              Height="200"
              Background="Yellow">
              
            <StackPanel Width="100"
                        Height="100"
                        Background="LightGreen">
                        
                <Button Content="Btn1" />
                
                <Button Content="Btn2" />
                
                <Button Content="Btn3" />
            </StackPanel>
        </Grid>
    </Grid>

Button.Click="DoSomething" 就是附加事件,可以在 Grid 层处理按钮的路由事件。

💡 Button.Click="DoSomething" 也可以写成 ButtonBase.Click="DoSomething",但是,因为 RadioButton、CheckBox 也继承自 ButtonBase,因此如果只想处理 Button 对象的事件,则需要明确使用第一种写法。

在代码中关联附加事件

在代码中不能使用 += 关联附加事件,需要使用AddHandler()

pnlButtons.AddHandler(Button.Click, new RoutedEventHandler(DoSomething));

隧道路由事件

隧道路由事件的工作方式和冒泡路由事件相同,但方向相反。
隧道路由事件以单词 Preview 开头。
WPF 通常成对地定义冒泡路由事件和隧道路由事件。
💡 隧道路由事件总在冒泡路由事件之前被触发。
💡 如果将隧道路由事件标记为已处理过,那就不会发生冒泡路由事件。这是因为两个事件共享 RoutedEventArges 类的同一个实例。
❓如果需要执行一些预处理(根据键盘上特定的键执行动作或过滤特定的鼠标动作),隧道路由事件是非常有用的。
有时也称预览事件

WPF 事件

WPF 事件的分类:

  • 生命周期事件
  • 鼠标事件
  • 键盘事件
  • 手写笔事件
  • 多点触控事件
    鼠标、键盘、手写笔及多点触控事件都是输入事件。

生命周期事件

所有元素的生命周期事件:
  • Initialized
  • Loaded
  • Unloaded

窗口初始化时会自下而上初始化每个元素,也就是说根元素总是最后才会被初始化。

Window 类的生命周期事件
  • SourceInitialized: 当取得窗口句柄前发生;
  • ContentRendered: 在窗口首次呈现后立即发生;
  • Activated: 当用户切换到窗口时发生,相当于控件的 GotFocus 事件;
  • Deactivated: 当用户从该窗口切换到其他窗口时发生,相当于控件的 LostFocus 事件;
  • Closing: 当关闭窗口时发生;
  • Closed: 当窗口已经关闭后发生,此时仍可访问元素对象。

输入事件

输入事件是当用户使用某些各类的外设硬件进行交互时发生的事件,例如鼠标、键盘、手写笔或多点触控屏。输入事件可通过继承自 InputEventArgs 的自定义事件参数类传递额外的信息。

InputEventArgs 增加了两个属性:

  • Timestamp: 指示事件是何时发生的;
  • Device: 指示事件发生的相关设备对象。

键盘输入

所有元素的键盘事件(按顺序)
  • PreviewKeyDown
  • KeyDown
  • PreviewTextInput
  • TextInput
  • PreviewKeyUp
  • KeyUp
private void KeyEvent(object sender, KeyEventArgs e)
{
	string message = "Event: " + e.RoutedEvent + " " +
		" Key: " + e.Key;
}

KeyEventArgs 枚举可获得按钮属性。

当连续按住一个键时会引发连接的事件,可以通过判断是否是连续按下避免重复触发事件:

if ((bool) chkIgnoreRepeat.IsChecked && e.IsRepeat) return;
private void TextInput(object sender, TextCompositionEventArgs e)
{
	string message = "Event: " + e.RoutedEvent + " " +
			" Text: " + e.Text;
}

🔥 ❓
TextInput 事件中,可通过 TextCompositionEventArgs 获取输入文本值。

关于键盘数字:
在主键区的数字和数字区的数字,返回的Key值是不一样的,分别是:Key.D9 和 Key.NumPad9
可以通过转换将得到相同的数字:(但这个仅限于数字才能转换成一样的)

KeyConverter converter = new KeyConverter();
string ke = converter.ConvedrToString(e.Key);

💡 如果在文本框中按下了空格,将直接绕过 PreviewTextInput 事件,这意味着还处理 PreviewKeyDown 事件。

💡 只有当字符可以“输入”到元素中 时,都会触发 TextInput 事件。比如空格键等。

💡 当发生 PreviewTextInput 事件时,一般会挂起 TextInput 事件。

焦点

WPF 中用户一次只能使用一个控件。即具有焦点的控件。
Focussable="true" 属性可设置控件是否具有焦点。
Tab 键可以切换控件焦点。
TabIndex 可以设置控件的焦点顺序。0是第一个。
IsTabStop = “fasle” 属性将阻止控件被包含进 Tab 焦点顺序。
Visibility 设置是否显示。
IsEnabled 设置是否可用。

获取键盘状态

使用 KeyboardDevice 属性获取键盘状态:(速度慢,有时候状态跟不上输入速度)

if((e.KeyboardDevice.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
	lblInfo.Text = "You held the Control key.";
}

KeyboardDevice 方法:

  • IskeyDown()
  • IskeyUp()
  • IsKeyToggled()
  • GetKeyState()

使用 KeyBoard 类

if(Keyboard.IsKeyDown(Key.LeftShift))
{
	lblInfo.Text = "The left Shift is held down.";
}

鼠标输入

MouseEnter: 当指针移动到元素上时发生事件
MouseLeave: 当指针离开元素时发生事件
💡 这两个事件是直接事件

PreviewMouseMove 和 MouseMove 是路由事件

MouseEventArgs 包含鼠标状态,其中该属性的 GetPosition() 方法可获得鼠标相对于元素的位置

Point pt = e.GetPosition(this);

IsMouseOver 或 IsMouseDirectlyOver 可确定当前鼠标是否位于某元素上

鼠标单击事件

PreviewMoseWheel 和 MoseWheel 鼠标滚动事件

所有元素的鼠标单击事件:

  • PreviewMoseLeftButtonDown/PreviewMoseRightButtonDown
  • MouseLeftButtonDown/MouseRightButtonDown
  • PreviewMouseLeftButtonUp/PreviewMoseRightButtonUp
  • MouseLeftButtonUp/MouseLeftButtonUp

MouseButtonEventArgs 继承自 MouseEventArgs 对象

  • MouseButton: 指示哪个鼠标按键
  • ButtonState: 指示鼠标按钮状态
  • ClickCount: 指示是单击还是双击事件
捕获鼠标

💡 如果单击一个元素,保持按下鼠标键,然后移动鼠标指针离开该元素,这里该元素就不会接收到鼠标键释放事件。

Mouse.Capture() 方法可捕获鼠标事件
LostMouseCapture 事件可响应鼠标丢失问题

鼠标拖放

💡 需要指定拖放的源和目标,拖放操作可交换任意类型的对象

👉 拖动

private void lblSource_MouseDown(object sender, MouseButtonEventArgs e)
{
	Label lbl = (Label)sender;
	DragDrop.DoDragDrop(lbl, lbl.Content, DragDropEffects.Copy);
	// DragDrop.DoDragDrop 调用此方法进行拖动
	// 参数:源,被移动的内容,移到方式上复制
}

👉 接收

<Label AllowDrop="True">To Here</Label>
<--!AllowDrop="True" 允许接收被拖动的内容-->

👉 如果需要对接收的内容进行处理: DragEnter 事件

private void lblTarget_DragEnter(object sender, DragEventArgs e)
{
	if(e.Data.GetDataPresent(DataFormats.Text))
		e.Effects = DragDropEffects.Copy;  // 将鼠标变成复制外形
	else
		e.Effects = DragDropEffects.None; // 将鼠标变成一条线
}

👉 接收拖放数据事件

private void lblTarget_Drop(object sender, DrogEventArgs e)
{
	((Lable)sender).Content = e.Data.GetData(DataFormats.Text);
}

也可以实现两个应用程序间的数据拖放操作。需要使用 System.Windows.Clipboard类。👀 我的理解:就是实现复制、粘贴操作。

多点触控输入

💡 单击和文本输入属于高层次输入;鼠标事件及按键事件属于低层次输入。

触控输入的三个层次:

  • 原始触控:例如绘图操作。
  • 操作:移动、绽放、旋转以及轻按。
  • 内置的元素支持:有些元素已经对多点触控事件提供了内置支持,如可滚动的支持触控移动:ListBox、ListView、DataGrid、TextBox以及ScrollViewer。
原始触控事件
  • PreviewTouchDown
  • TouchDown
  • PreviewTouchMove
  • TouchMove
  • PreviewTouchUp
  • TouchUp
  • TouchEnter
  • TouchLeave

TouchEventArgs 对象提供的两个重要成员:

  • GetTouchPoint()
  • TouchDevice: 指的是每个触点,并通过 TouchDeviceId 进行区分

可在按下事件、移动事件、抬起事件中,通过获取当前触控点,画一个圆点来显示用户的手指位置,也可以移动这些圆点,并且在抬起后将圆点元素删除掉。
同样支持类似于鼠标的事件丢失处理。略。

操作(移动、转运、缩小或放大)

💡 将元素的 IsManipulationEnabled 属性设置为 True,将元素配置为接受触控操作。

💡 这样,元素将响应4个事件:(它们是冒泡事件)

  • ManipulationStarting:当触摸对象时触发此事件
  • ManipulationStarted
  • ManipulationDelta:在操作过程中持续触发此事件
  • ManipulationCompleted
惯性

当用户结束手势并抬起手指释放元素时,会触发 ManipulationInertiaStarting 事件(冒泡事件),可使用 ManipulationInertiaStartingEventsArgs 对象确定当前速度,然后通过代码设置希望的减速度。


路由传播机制参考文章:
走进WPF之路由事件 - 老码识途呀 - 博客园

本文作者:石起起

本文链接:https://www.cnblogs.com/myshiqiqi/p/18739346

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   石起起  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起