UWP Multi-View Communication
UWP apps support multiple views/windows. Compared to Windows Forms and WPF apps there’s one big difference in UWP: All application views use different threads. This makes it harder to build applications where different views communicate with each other.
In this post we explore couple different ways of multi-window communication.
Creating a new View (Window) in UWP
To create a new view in UWP app one can use CoreApplication.CreateNewView. Here’s the basic code for opening an another view:
CoreApplicationView newView = CoreApplication.CreateNewView(); int newViewId = 0; await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { Frame frame = new Frame(); frame.Navigate(typeof(Secondary), null); Window.Current.Content = frame; // You have to activate the window in order to show it later. Window.Current.Activate(); newViewId = ApplicationView.GetForCurrentView().Id; }); bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
Here’s an example output:
There’s a good tutorial about multi view app development at Dev Center: Show multiple views for an app.
Next, let’s look how we can make the views to communicate with each other.
Example app
In our example app we have two views: The main one and a secondary. Secondary view contains a button and when clicked, we want to update the main view.
Main view has method which updates the TextBlock:
public void UpdateMessage(string newMessage) { this.Message.Text = newMessage; }
The problem
As mentioned, all the views have different threads in UWP. As with Windows Forms and WPF, there’s only one thread which can access UI controls. Trying to access them from other threads will cause exceptions.
The first approach to multi view communication in UWP is the direct one: We pass the main view to secondary view and try to update the main view’s TextBox directly using MainPage.UpdateMessage:
Main view is passed to Secondary View:
frame.Navigate(typeof(Secondary), this);
Secondary view receives the Main view:
protected override void OnNavigatedTo(NavigationEventArgs e) { this.MainPage = (MainPage) e.Parameter; }
Main view’s UpdateMessage is called directly:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { this.MainPage.UpdateMessage("Hello from second view"); }
Here we hit the problem: This throws an exception:
First solution to this problem is using CoreDispatcher directly.
First option: Directly using CoreDispatcher
We can use Main View’s CoreDispatcher to get around this problem. The change is done on the UpdateMessage-method:
public void UpdateMessage(string newMessage) { this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => this.Message.Text = newMessage); }
With this, we get the desired result:
Second option: EventAggregator
The first option works but if there is much communication between the views, it can be tedious to manually call CoreDispatcher at every point. Another option is to change the pattern:
Insted of views communicating directly with each other, you add a middle man which handles the communication between views. EventAggregator is a familiar pattern and it fits into this problem nicely: You raise messages from your views and if some other view is interested, it acts on that message.
I’ve posted a gist which contains a source code of a multi-view UWP EventAggregator. You can examine it to get the idea but in production use it’s good to use something like WeakReferences so that EventAggregator knows when to let go of views.
The idea in this pattern is that you create one EventAggregator for each of your views but the EventAggregator contains a static (shared) list of subscribers which are common to all the views. Here’s what we change in our example app:
Main view:
public sealed partial class MainPage : Page, MainPage.ISubscriber{ public MainPage() { this.InitializeComponent(); var eventAggregator = new MyEventAggregator(); eventAggregator.Subscribe(this); }
Note that MainPage now implements ISubscriber.
Secondary view:
public Secondary() { this.InitializeComponent(); this.EventAggregator = new MyEventAggregator(); }
Note that there’s no need to pass Main view to Secondary view: Secondary view doesn’t have to know that Main view exists.
Secondary view raises a message:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { this.EventAggregator.Publish(new Message("hello from second view")); }
Main view handles the message:
public void Handle(Message message) { this.Message.Text = message.Text; }
Conclusion
Different view threads in UWP apps can bite you. You can get around the problem using CoreDispatcher. If there’s much communication happening between the views, it can be better to use a middle man (mediator) to handle the cross thread communication. EventAggregator is one example of this kind of a pattern.