WPF - 声明自定义路由事件并监听它

8
简而言之:我想声明一个自定义路由事件,并同时从声明它的同一用户控件中监听它。
我的目标是创建一个用户控件来处理某个任务的请求,所以我想象了以下场景:
- 用户控件类声明一个自定义路由事件 - 用户控件类通过AddHandler(...)监听其自定义路由事件
然后:
- 视觉树中的某个随机项使用RaiseEvent(...)来...嗯,引发该事件。 - 树中的用户控件实例处理该请求。
它似乎不起作用。这与用户控件声明和引发事件的通常情况有些不同,我知道,所以我进行了一些测试。
如何创建自定义路由事件似乎很清楚,这不是我第一次这样做。我创建了一个示例用户控件,以下是它的代码后台:
public partial class FuffaControl : UserControl
{
    public static readonly RoutedEvent FuffaEvent =  
        EventManager.RegisterRoutedEvent("Fuffa", RoutingStrategy.Bubble, 
           typeof(FuffaEventHandler), typeof(FuffaControl));

    // Provide CLR accessors for the event
    public event FuffaEventHandler Fuffa
    {
        add { AddHandler(FuffaEvent, value); }
        remove { RemoveHandler(FuffaEvent, value); }
    }

    public FuffaControl()
    {
        InitializeComponent();
    }
}

目前为止,一切都很好。然后,为了测试目的,我声明了一个带有自定义控件和按钮的窗口。以下是窗口内容:

<Grid>
    <local:FuffaControl>
        <Grid>
            <Button Content="Fuffa" Click="Button_Click"/>
         </Grid>
    </local:FuffaControl>
</Grid>

在代码后台,我使用AddHandler来监听事件,并在按钮点击时触发事件:
public MainWindow()
{
    InitializeComponent();
    this.AddHandler(FuffaControl.FuffaEvent, new FuffaEventHandler(OnFuffaEvent));
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    RoutedEventArgs newEventArgs = new RoutedEventArgs(FuffaControl.FuffaEvent);
    RaiseEvent(newEventArgs);
}

private void OnFuffaEvent(object sender, RoutedEventArgs e) { }

它可以工作。我是说,它没有太多的意义(不是很有用),但我只是用它来测试事件本身是否正常工作。除非C#做了一些奇怪的事情并且绕了几个弯子,但第一眼看起来似乎按钮正在触发一个自定义事件,并且事件通过树向上传递(毕竟它是冒泡事件),最后窗口接收到了它。

接下来,我把AddHandler(...)调用和处理函数移到用户控件中;现在不再是窗口监听Raisevent(...),而是用户控件本身。如果它可以工作,那么我就有子元素引发事件,父用户控制管理它:

public partial class FuffaControl : UserControl
{
    public static readonly RoutedEvent FuffaEvent = EventManager.RegisterRoutedEvent("Fuffa", RoutingStrategy.Bubble, typeof(FuffaEventHandler), typeof(FuffaControl));
    // Provide CLR accessors for the event
    public event FuffaEventHandler Fuffa
    {
        add { AddHandler(FuffaEvent, value); }
        remove { RemoveHandler(FuffaEvent, value); }
    }

    public FuffaControl()
    {
        InitializeComponent();

        this.AddHandler(FuffaControl.FuffaEvent, new FuffaEventHandler(OnFuffaEvent));
    }

    private void OnFuffaEvent(object sender, RoutedEventArgs e)
    {
    }
}

不行。它无法工作。为什么?这里有什么问题?用户控件为什么不能监听其自身事件?

2个回答

8
原因在于你如何触发该事件:
private void Button_Click(object sender, RoutedEventArgs e)
{
    RoutedEventArgs newEventArgs = new RoutedEventArgs(FuffaControl.FuffaEvent);
    RaiseEvent(newEventArgs);
}

路由事件(类似于常规的.NET事件)具有源(发送者)和参数。您仅指定参数,而发送者是调用RaiseEvent的控件。您可以从MainWindow类中执行此操作,因此事件的源将是MainWindow,而不是按钮(正如您可能已经注意到的那样,按钮根本不参与您的事件触发代码)。WPF会从发送者开始搜索路由事件的处理程序,然后根据事件类型向上或向下遍历层次结构。在您的情况下,事件是冒泡的,因此它将从MainWindow开始向上搜索树。您的控件是窗口的子级,因此其处理程序将无法找到。
相反,您应该在按钮上调用RaiseEvent。然后按钮将成为发送者并按您的期望工作:
private void Button_Click(object sender, RoutedEventArgs e) {
   ((FrameworkElement) sender).RaiseEvent(new RoutedEventArgs(FuffaControl.FuffaEvent));
}

请有人杀了我。 - motoDrizzt
2
从那个评论中不清楚我的回答是否有帮助 :) - Evk
3
是的,有帮助:现在我确定是时候放下键盘,换工作了。 - motoDrizzt

1
乍一看,似乎你正在将 UserControl 实现为 ContentControl。不过,你可以通过将所有逻辑移动到 UC 中,并从窗口的 Button_Click 处理程序调用公共的 RaiseFuffaEvent 来使其工作。

FuffaControl.xaml.cs

public partial class FuffaControl : UserControl
{
    public FuffaControl()
    {
        InitializeComponent();
        AddHandler(FuffaEvent, new RoutedEventHandler(OnFuffaEvent));
    }

    public static readonly RoutedEvent FuffaEvent = EventManager.RegisterRoutedEvent(
        "Fuffa", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(FuffaControl));

    public event RoutedEventHandler Fuffa
    {
        add { AddHandler(FuffaEvent, value); }
        remove { RemoveHandler(FuffaEvent, value); }
    }

    public void RaiseFuffaEvent()
    {
        RoutedEventArgs newEventArgs = new RoutedEventArgs(FuffaEvent);
        RaiseEvent(newEventArgs);
    }

    private void OnFuffaEvent(object sender, RoutedEventArgs e)
    {

    }
}

使用这种实现RaiseFuffaEvent的方式,即使从窗口的代码后面引发事件,其他控件仍然可以监听该事件。

Window.xaml

<Window 
    ...>
    <Grid>
        <local:FuffaControl x:Name="fuffa">
            <Button Content="Fuffa" HorizontalAlignment="Center" VerticalAlignment="Center" Click="Button_Click"/>
        </local:FuffaControl>
    </Grid>
</Window>

Window.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        fuffa.RaiseFuffaEvent();
    }
}

为了完整起见,在“正常”的UserControl中,您会将按钮放置在FuffaControl.xaml中,并在FuffaControl.xaml.cs中设置Button_Click处理程序。


1
但是在你的 RaiseFuffaEvent 中,你直接调用了 OnFuffaEvent,绕过了整个事件系统。 - Evk
@Evk 理解了,我更新了我的代码。不过,你的解决方案更好。 - Funk
@Funk,你的解决方案还可以,但它有些违背了我想要实现的目标。我基本上是在尝试构建一个“服务提供者”,它驻留在可视树中(甚至有多个实例),并且对每个人都是透明的:只需用户控件转发请求,最近的“服务提供者”将满足它,但没有人知道彼此的位置或甚至存在。不管怎样,还是谢谢你的帮助 :-) - motoDrizzt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接