Widget to Widget Communication in UWP Dashboard app
In our last tutorial we created an UWP widget dashboard app (and platform) using Caliburn.Micro. The platform in its first version is very basic and we ended up listing many improvements to it:
- Instead of always having all the widgets, user should be able to add and remove widgets.
- Common widget elements, like title and action buttons.
- Different sized widgets instead of a single fixed size.
- Ability to send notifications from a widget to a widget using Event aggregator.
- Ability to send notifications from a widget to the shell using Event aggregator.
In this tutorial we’re going to show how it’s possible to do widget to widget communication.
Background
We want our widgets to be self contained and isolated, meaning we don’t want them to interfere with the other widgets and we don’t want our shell to have to care about which widgets it is hosting.
Widgets can communicate with each directly and indirectly. Direct way (calling a method from another widget) introduces coupling and makes it harder to keep widgets isolated. Preferred way is to use indirect messaging.
Direct communication
It’s possible to communicate directly from a widget to an another but this isn’t recommended.If you really need to do this, a widget can reference other widgets through its Parent-property (which is provided by Caliburn.Micro). The following code shows how you can directly call methods from other hosted widgets:
var widgets = ((ShellViewModel) this.Parent).Items; foreach (var widget in widgets) { if (widget is CustomerListViewModel customerList) { customerList.AddNewCustomer(FirstName, LastName); } }
But as mentioned, you shouldn’t do this in most cases.
Indirect communication using the Event Aggregator
Caliburn.Micro contains an event aggregator. Event aggregator is a “middle man” which you can use to handle widget-to-widget, widget-to-shell and shell-to-widget communication.
With event aggregator your widgets (and shell) can raise messages. Any other widget can subscribe to the messages they are interested in. For example “NewCustomerWidget” can raise “NewCustomerAdded” message. “CustomerListWidget” can listen (to subscribe) to this message and react to it, by for example refreshing its view.
Given that we have NewCustomerWidget, it can can raise desired message using IEventAggregator.Publish(). First we need to define the message:
public class NewCustomerCreated { public string FirstName { get; } public string LastName { get; } public NewCustomerCreated(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } }
Then we can raise (publish) the message from our widget:
public class NewCustomerViewModel : Screen, IWidget { private string _lastName; private string _firstName; private readonly IEventAggregator _eventAggregator; public string FirstName { get { return _firstName; } set { _firstName = value; NotifyOfPropertyChange(() => FirstName); NotifyOfPropertyChange(() => CanSave); } } public string LastName { get { return _lastName; } set { _lastName = value; NotifyOfPropertyChange(() => LastName); NotifyOfPropertyChange(() => CanSave); } } public bool CanSave { get { if (string.IsNullOrWhiteSpace(FirstName)) return false; if (string.IsNullOrWhiteSpace(LastName)) return false; return true; } } public NewCustomerViewModel(IEventAggregator eventAggregator) { this._eventAggregator = eventAggregator; } public void Save() { this._eventAggregator.PublishOnUIThread(new NewCustomerCreated(FirstName, LastName)); } }
Main things to notice:
- IEventAggregator is passed through constructor.
- Message is raised using PublishOnUiThread.
Now that our widget can publish messages, we can add subscribers who can react to these messages. Here’s how we can make our “CustomerListWidget” to update its view anytime new customer is added:
public class CustomerListViewModel : Conductor<Customer>.Collection.AllActive, IWidget, IHandle<NewCustomerCreated> { public CustomerListViewModel(IEventAggregator eventAggregator) { eventAggregator.Subscribe(this); this.Items.Add(new Customer("Test", "Customer")); this.Items.Add(new Customer("Another", "One")); this.Items.Add(new Customer("Mikael", "Koskinen")); } public void Handle(NewCustomerCreated message) { this.Items.Add(new Customer(message.FirstName, message.LastName)); } }
Main things to notice:
- IEventAggregator is passed through constructor and we subscribe our view model to it.
- Our view model implements IHandle<NewCustomerCreated>
When we now run the app, we can see our two customer related widgets:
If we now create a new customer, we can see that the customer list is updated:
Conclusion
You can use direct widget-to-widget communication but instead you should prefer indirect messaging. Event aggregator makes it possible for widgets to publish and subscribe to messages.
The source code for this tutorial is available through GitHub.