7. WPF事件

7. WPF事件

路由事件

路由事件与直接事件(WinForm方式的事件)的区别在于:

  • 直接事件激发时,发送者直接将消息通过事件订阅交给事件响应者,事件响应者通过处理方法做出响应。
  • 路由事件的事件拥有者和响应者没有直接的订阅关系,事件拥有者只负责触发事件,事件的响应者则是安装事件监听器,针对某类事件进行侦听,当有此类事件传递到响应者就用事件处理方法来响应,并决定事件是否要继续向下传递。

WPF内置路由事件

案例:

<Grid x:Name="gridRoot">
    <Grid x:Name="gridA">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Canvas x:Name="canLeft" Grid.Column="0" >
            <Button x:Name="btnLeft" Content="Left"/>
        </Canvas>
        <Canvas x:Name="canRight" Grid.Column="1" >
            <Button x:Name="btnRight" Content="Right"/>
        </Canvas>
    </Grid>
</Grid>

逻辑树结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eGatSboO-1667661795429)(C:\Users\54302\Desktop\wpf教程#\7. WPF事件.assets\222.png)]

当单击btnLeft时,Button.Click事件会沿着btnLeft-canLeft-gridA-gridRoot-Window路线传送,单击btnRight原理相同。

public MainWindow()
{
    InitializeComponent();
    //为gridRoot安装针对Button.Click事件的监听器
    this.gridRoot.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.ButtonClicked));
}

//事件处理程序
private void ButtonClicked(object sender, RoutedEventArgs e)
{
    //路由事件是一层层传出的,最后到达gridRoot,并由gridRoot将事件消息交给事件处理程序
    //所以sender是gridRoot,而不是btnLeft或者btnRight,这点和传统的直接事件不同
    //e.OriginalSource可以查看事件的最初发起者
    MessageBox.Show((e.OriginalSource as FrameworkElement).Name);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kzrPMCcN-1667661795431)(C:\Users\54302\Desktop\wpf教程#\7. WPF事件.assets\image-20221105203659753.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gC42XVgx-1667661795432)(C:\Users\54302\Desktop\wpf教程#\7. WPF事件.assets\image-20221105203732320.png)]

在XAML实现,<Grid x:Name="gridRoot" Button.Click="ButtonClicked">

自定义路由事件

自定义路由事件大致分为3个步骤:

  1. 声明并注册路由事件,使用EventManager的RegisterRoutedEvent方法进行注册
  2. 为路由事件添加包装器,目的是把路由事件暴露的像一个传统直接事件,并仍然可以使用+=或者-=操作符。
  3. 创建可以激发事件的方法

案例:当点击按键时,报告事件发生的时间

//用于承载事件消息的时间参数
class ReportTimeEventArgs : RoutedEventArgs
{
    public ReportTimeEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source)
    {
    }
    public DateTime ClickTime { set; get; }
}
class TimeButton:Button
{
    //声明和注册事件
    //参数1:路由事件名称,和事件包装器的名称相同
    //参数2:路由事件的策略,wpf路由事件的策略有3种
    //       Bubble:冒泡式,由激发者向上级容器一层一层传递直到UI树的根部,路径唯一
    //       Tunnel:隧道式,与Bubble策略相反,路径不唯一
    //       Direct: 直达式,直接将事件消息发送到事件处理方法
    //参数3:事件类型
    //参数4:事件拥有者
    public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton));

    //路由事件的包装器,固定写法
    public event RoutedEventHandler ReportTime
    {
        add { this.AddHandler(ReportTimeEvent, value); }
        remove { this.RemoveHandler(ReportTimeEvent, value); }
    }

    //激发路由事件,使用Click激发
    protected override void OnClick()
    {
        base.OnClick();
        ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent, this);
        args.ClickTime = DateTime.Now;
        this.RaiseEvent(args);
    }
}

//ReportTimeEvent事件处理方法
private void ReportTimeHandle(object sender, ReportTimeEventArgs e)
{
    FrameworkElement element = sender as FrameworkElement;
    string timeStr = e.ClickTime.ToLongTimeString();
    this.listBox.Items.Add($"{timeStr} 到达 {element.Name}");
}
<Grid x:Name="grid_1" local:TimeButton.ReportTime="ReportTimeHandle">
    <Grid x:Name="grid_2" local:TimeButton.ReportTime="ReportTimeHandle">
        <Grid x:Name="grid_3" local:TimeButton.ReportTime="ReportTimeHandle">
            <StackPanel x:Name="stackPanel" local:TimeButton.ReportTime="ReportTimeHandle">
                <ListBox x:Name="listBox"/>
                <local:TimeButton x:Name="timeBtn" Content="报时" local:TimeButton.ReportTime="ReportTimeHandle"/>
            </StackPanel>
        </Grid>
    </Grid>
</Grid>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gptwQtsj-1667661795432)(C:\Users\54302\Desktop\wpf教程#\7. WPF事件.assets\image-20221105211951349.png)]

如果传递到某个节点不再继续向下传递事件可以将RoutedEventArgs中的Handled属性设置为true,意思为“已经处理完成”。

//ReportTimeEvent事件处理方法
private void ReportTimeHandle(object sender, ReportTimeEventArgs e)
{
    FrameworkElement element = sender as FrameworkElement;
    string timeStr = e.ClickTime.ToLongTimeString();
    this.listBox.Items.Add($"{timeStr} 到达 {element.Name}");

    if (element == this.grid_2)
    {
        e.Handled = true;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dx8wuB4i-1667661795433)(C:\Users\54302\Desktop\wpf教程#\7. WPF事件.assets\image-20221105212447158.png)]

Source和OriginalSource

我们常说的WPF树形结构通常指的是LogicalTree,而事件则是沿着VisualTree传递的,他俩的区别在于:LogicalTree的叶子结点构成了用户界面,而VisualTree要连控件中的细微结构也算上。如一个ListBox控件的细微结构由Border、ScrollViewer、Grid等等组成。

  • Source代表着LogicalTree的事件起点
  • OriginalSource代表着VisualTree上的事件起点

附加事件

常见的附加事件

  • Binding类:SourceUpdated事件,TargetUpdated事件
  • Mouse类:MouseEnter事件、MouseLeave事件等
  • Keyboard类:KeyDown事件、KeyUp事件等

可以看出,路由事件的宿主都是拥有可视化实体的界面元素,而附加事件不具备显示在用户界面上的能力。

案例:设计一个Student类,如果其中的Name属性发生变化则激发一个路由事件,并用界面元素来捕捉。

<Grid x:Name="gird">
    <Button x:Name="btn1" Content="OK" Click="Btn1_Click"/>
</Grid>
class Student
{
    //声明并定义路由事件
    public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRoutedEvent("NamgeChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Student));

    public int Id { set; get; }
    public string Name { set; get; }
}
public MainWindow()
{
    InitializeComponent();

    //为gird添加路由事件监听器
    this.gird.AddHandler(Student.NameChangedEvent, new RoutedEventHandler(this.StudentNameChangedHandler));
}

//Grid的事件处理方法
private void StudentNameChangedHandler(object sender, RoutedEventArgs e)
{
    MessageBox.Show((e.OriginalSource as Student).Id.ToString());
}

//非UIElement类没有RaiseEvent方法,所以要借用一个Button
private void Btn1_Click(object sender, RoutedEventArgs e)
{
    Student student = new Student() { Id = 100, Name = "Tim" };
    student.Name = "Tom";
    //准备事件消息并发送路由事件
    RoutedEventArgs arg = new RoutedEventArgs(Student.NameChangedEvent, student);
    this.btn1.RaiseEvent(arg);
}

为附加事件增加包装器

//在Student类中增加
//为目标UI元素增加事件监听器的包装器
//参数1:事件监听者
//参数2:事件处理函数
public static void AddNameChangedHandler(DependencyObject d, RoutedEventHandler h)
{
    UIElement e = d as UIElement;
    if (e != null)
    {
        e.AddHandler(Student.NameChangedEvent, h);
    }
}
//为目标UI元素移除事件监听器的包装器
public static void RemoveNameChangedHandler(DependencyObject d, RoutedEventHandler h)
{
    UIElement e = d as UIElement;
    if (e != null)
    {
        e.RemoveHandler(Student.NameChangedEvent, h);
    }
}

这样可以将上面
this.gird.AddHandler(Student.NameChangedEvent, new RoutedEventHandler(this.StudentNameChangedHandler));
改为
Student.AddNameChangedHandler(this.gird, new RoutedEventHandler(this.StudentNameChangedHandler));
或者删除上句将XMAL改为
<Grid x:Name="gird" local:Student.NameChanged="StudentNameChangedHandler">

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

步、步、为营

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值