7. WPF事件
  TEZNKK3IfmPf 2023年11月15日 19 0

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>

逻辑树结构

7. WPF事件

当单击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);
}

7. WPF事件

在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>

7. WPF事件

如果传递到某个节点不再继续向下传递事件可以将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;
    }
}

7. WPF事件

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">

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

上一篇: 5.3表达式树 下一篇: 4.3 数据库迁移
  1. 分享:
最后一次编辑于 2023年11月15日 0

暂无评论

推荐阅读
  TEZNKK3IfmPf   2023年11月14日   48   0   0 事件javascript
  TEZNKK3IfmPf   2023年11月13日   19   0   0 事件jQuery
  TEZNKK3IfmPf   2023年11月14日   18   0   0 事件jQueryjavascript
  AnyLlCIhvKpr   2023年11月12日   14   0   0 事件
  TEZNKK3IfmPf   2023年11月12日   52   0   0 html事件js
  TEZNKK3IfmPf   2023年11月12日   52   0   0 事件javascript
  TEZNKK3IfmPf   2023年11月15日   20   0   0 事件WPF
  TEZNKK3IfmPf   2023年11月12日   19   0   0 react事件
  TEZNKK3IfmPf   2023年11月14日   24   0   0 WPF
  AnyLlCIhvKpr   2023年11月12日   18   0   0 事件
  TEZNKK3IfmPf   2023年11月13日   32   0   0 监听器事件java
  TEZNKK3IfmPf   2023年11月14日   70   0   0 WPF
TEZNKK3IfmPf