在多 window UWP 应用程序中引发和处理事件

Raising and handling events in a multi window UWP app

我创建了一些测试代码,这样我就可以尝试弄清楚如何在 UWP 中正确使用多个 windows。我想看看我是否可以触发一个事件并让多个 windows 在事件处理程序中更新它们的 UI。我终于得到了一些有用的东西,但我不完全确定它为什么有用。

这是在我的页面中创建的 class

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 时,我为 window 注册了 Dispatcher 和 NumberCruncher 的实例。然后在触发事件时,我为特定的 Dispatcher 使用 NumberCruncher EventHandler。

这不会抛出封送处理异常。如果我尝试使用当前 class 的 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}");
        });
    }

这是怎么回事?

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

If I try to use the current class's EventHandler, I get a marshaling exception when trying to add the item to the listView.

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

However this does not work and throws a marshaling exception when trying to update listView.

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