在多窗口 UWP 应用中引发和处理事件

Posted

技术标签:

【中文标题】在多窗口 UWP 应用中引发和处理事件【英文标题】:Raising and handling events in a multi window UWP app 【发布时间】:2018-06-16 01:09:08 【问题描述】:

我创建了一些测试代码,以便尝试找出如何在 UWP 中正确使用多个窗口。我想看看我是否可以触发一个事件并让多个窗口在事件处理程序中更新它们的 UI。我终于得到了一些工作,但我不完全确定它为什么工作。

这是在我的页面中创建的课程

public class NumberCruncher

    private static Dictionary<int, Tuple<CoreDispatcher, NumberCruncher>> StaticDispatchers  get; set; 

    static NumberCruncher()
    
        StaticDispatchers = new Dictionary<int, Tuple<CoreDispatcher, NumberCruncher>>();
    

    public NumberCruncher()
    
    

    public event EventHandler<NumberEventArgs> NumberEvent;

    public static void Register(int id, CoreDispatcher dispatcher, NumberCruncher numberCruncher)
    
        StaticDispatchers.Add(id, new Tuple<CoreDispatcher, NumberCruncher>(dispatcher, numberCruncher));
    

    public async Task SendInNumber(int id, int value)
    
        foreach (var dispatcher in StaticDispatchers)
        
            await dispatcher.Value.Item1.RunAsync(CoreDispatcherPriority.Normal, () =>
            
                Debug.WriteLine($"invoking dispatcher.Key");
                dispatcher.Value.Item2.NumberEvent?.Invoke(null, new NumberEventArgs(id, value));
            );
        
    

这是我的 MainPage 代码的相关部分

    NumberCruncher numberCruncher;

    public MainPage()
    
        this.InitializeComponent();
        numberCruncher = new NumberCruncher();
        numberCruncher.NumberEvent += NumberCruncher_NumberEvent;
    

    private async void NumberCruncher_NumberEvent(object sender, NumberEventArgs e)
    
        listView.Items.Add($"e.Id sent e.Number");
    

    protected override void OnNavigatedTo(NavigationEventArgs e)
    
        NumberCruncher.Register(ApplicationView.GetForCurrentView().Id, Window.Current.Dispatcher, numberCruncher);
    

我有一个按钮可以创建 MainPage 的新视图。然后我有另一个按钮调用SendInNumber() 方法。

当我导航到 MainPage 时,我为窗口和 NumberCruncher 实例注册了 Dispatcher。然后在触发事件时,我使用 NumberCruncher EventHandler 来处理特定的 Dispatcher。

这不会引发封送处理异常。如果我尝试使用当前类的 EventHandler

await dispatcher.Value.Item1.RunAsync(CoreDispatcherPriority.Normal, () =>
        
            Debug.WriteLine($"invoking dispatcher.Key");
            NumberEvent?.Invoke(null, new NumberEventArgs(id, value));
        );

尝试将项目添加到 listView 时,我收到了封送处理异常。但是,如果我在 MainPage 中维护 SynchronizationContext,然后使用 SynchronizationContext.Post 更新 listView。效果很好

    SynchronizationContext synchronizationContext;

    public MainPage()
    
        this.InitializeComponent();
        numberCruncher = new NumberCruncher();
        numberCruncher.NumberEvent += NumberCruncher_NumberEvent;
        synchronizationContext = SynchronizationContext.Current;
    

    private async void NumberCruncher_NumberEvent(object sender, NumberEventArgs e)
    
        synchronizationContext.Post(_ =>
        
            listView.Items.Add($"e.Id sent e.Number");
        , null);
    

但是,这不起作用,并在尝试更新 listView 时引发编组异常。

private async void NumberCruncher_NumberEvent(object sender, NumberEventArgs e)
    
        await CoreApplication.GetCurrentView().CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        
            listView.Items.Add($"e.Id sent e.Number");
        );
    

这是怎么回事?

【问题讨论】:

GetCurrentView 返回指向活动窗口的指针,不一定是包含您尝试更新的 listView 实例的窗口。所以我认为你最终会使用错误的 UI 线程(因为每个窗口都有自己的 UI 线程)。您是否尝试过使用“listView.Dispatcher”?这应该将调用编组到此 listView 实例的正确 UI 线程上。 文档说 GetCurrentView 返回活动视图,但我设置了一个线程,将 ApplicationView.GetForCurrentView().Id 和 CoreApplication.GetCurrentView().IsMain 的状态打印到调试控制台。当我有 2 个窗口时,每个窗口的输出都不同。这让我相信 GetCurrentView() 返回该 UI 线程的视图,而不是活动窗口。 是的,但是您的事件是否在 UI 线程上触发?如果是这样,那么您首先不需要调度程序。您似乎需要它的事实表明该事件不会在与您尝试更新的 ListView 对应的 UI 线程上触发。如果您只执行“listView.Dispatcher.RunAsync”会发生什么? (而不是 CoreApp.GetCurrentView().CoreWin.Dispatcher.RunAsync) 如果我使用 listView Dispatcher,我会在我打开的每个窗口的 listView 中收到一条消息,因此我会在窗口 -23473 上看到 -23473 sent 123 两次打开两个窗口。 【参考方案1】:

要记住的重要一点是,当触发事件时,订阅的方法会在与Invoke 方法相同的线程上被调用。

如果我尝试使用当前类的 EventHandler,则在尝试将项目添加到 listView 时会收到封送处理异常。

发生第一个错误是因为您试图在另一个 Window 的调度程序 (dispatcher.Value.Item1) 上触发当前类的事件。假设事件在 Window 1 上,dispatcher.Value.Item1 属于 Window 2。一旦进入 Dispatcher 块,您就是 Window 2 的 UI 线程并触发 Window 1'sNumberEvent NumberCruncher 将运行 Window Window 2 的 UI 线程上的 1 的 处理程序,这会导致异常。

但是,这不起作用并在尝试更新 listView 时引发编组异常。

GetCurrentView() 方法返回当前活动的视图。因此,当时处于活动状态的任何应用程序视图都将被返回。在您的情况下,它将是您单击按钮的那个。如果您在目标 Window 的 UI 线程上调用 Invoke 方法,则不需要在 NumberEvent 处理程序中添加任何代码。

【讨论】:

这就是我认为正在发生的事情。我也猜测存储 SynchronizationContext 然后使用它来更新 UI 是可行的,因为我正在使用 UI 线程来更新 UI。 SynchronizationContext 起作用的原因是您将它存储在必须在 UI 线程上运行的页面构造函数中,因此上下文与正确的 UI 线程相关联:-)

以上是关于在多窗口 UWP 应用中引发和处理事件的主要内容,如果未能解决你的问题,请参考以下文章

切换到其他应用时不会触发 UWP Flyout 的关闭事件

在 C 中引发和处理事件

处理和引发事件

AppDomain.AssemblyLoad 事件捕获事件处理程序中引发的所有异常

MSDN搬运 之 [事件]

如何编写 C++ 代码来处理 C# 代码中引发的事件