Consider the classic scenario when you need to show the currently logged in User Name in your main Window, once you have successfully logged in. The The login Window is supposed to be a Modal Dialog, and isn’t remotely aware of the Label displaying Username in the Main Window.
WPF handles Modal Dialogs and messaging service between Views using Window Managers and Event Aggregators.
Event Aggregators allows a loosely coupled message parsing mechanism between different View Models in the system. As the Caliburn Micro Documentation states, an Event Aggregator is a service that provides the ability to publish an object from one entity to another in a loosely based fashion.


<Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="50"></RowDefinition> </Grid.RowDefinitions> <StackPanel Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center"> <Label x:Name="UserName" FontWeight="Bold" FontSize="32"></Label> </StackPanel> <StackPanel Grid.Row="1" Margin="10,10,10,10"> <Button Margin="5,5,5,5" cal:Message.Attach="[Event Click]=[Action PromptForLogin]">Login</Button> </StackPanel> </Grid>
IShell Interface
public interface IShell { }
ShellViewModel
[Export(typeof(IShell))] public class ShellViewModel : PropertyChangedBase, IShell { private string _userName = default; public string UserName { get => _userName; set { _userName = value; NotifyOfPropertyChange(nameof(UserName)); } } public void PromptForLogin() {}; }
<Grid> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="20"></RowDefinition> <RowDefinition Height="30"></RowDefinition> <RowDefinition Height="30"></RowDefinition> <RowDefinition Height="30"></RowDefinition> <RowDefinition Height="20"></RowDefinition> </Grid.RowDefinitions> <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Background="Lavender"> <Label>User Login</Label> </StackPanel> <TextBox Margin="5,5,5,5" Grid.Row="1" Name="UserName">UserName</TextBox> <TextBox Margin="5,5,5,5" Grid.Row="2" Name="Password">Password</TextBox> <Button Grid.Row="3" Margin="5,5,5,5" cal:Message.Attach="[Event Click]=[Action Validate(UserName,Password)]">Login</Button> </Grid>
ILogin Interface
public interface ILogin { bool Validate(string userName, string passWord); }
LoginViewModel
[Export(typeof(ILogin))] public class LoginViewModel : ILogin { // Since this is a demo, we will always return true public bool Validate(string userName, string passWord) => true; }
Alright, so we have our barebone code ready. Time to link up the chain. The first task is to ensure we have ShellView as our primary start up Window, which we set by altering the code in our Bootstrapper.
protected override void OnStartup(object sender, StartupEventArgs e) { DisplayRootViewFor(); }
The next task is to ensure the Login Window User Clicks Login Button. We do this by utilizing the Window Manager, which is injected (via Dependency Injection) into ShellViewModel. We will add a constructor for ShellViewModel decorated with the ImportingConstructor Attribute. We will also inject LoginViewModel as well in to the class. We will then use the WindowManager for invoking the LoginViewModel as a Modal Dialog. Our constructor and PromptForLogin Method looks like following now.
[ImportingConstructor] public ShellViewModel(IWindowManager windowManager,ILogin loginWindow) { } public void PromptForLogin() => _windowManager.ShowDialog(_loginWindow);
This opens up the Login Window for you. All good till now, the Login Window is displayed, and you type in the UserName & Password, and Click the Login button to invoke the Validate Method. But how do you pass the UserName information to the ShellViewModel ? This is where Event Aggregator comes into place.
Event Aggregators allows ViewModels to pass (broadcast) information (in a model) to any other View Models who has subscribed to the event. We will begin by creating the model which would contain the information that will passed along Event Aggregators.
public class UserInfoModel { public string UserName { get; set; } }
We will then inject an instance of EventAggregator in the LoginViewModel. We will also modify our Validate Method a bit. The LoginViewModel class now looks like following.
[Export(typeof(ILogin))] public class LoginViewModel : ILogin { private IEventAggregator _eventAggregator; [ImportingConstructor] public LoginViewModel(IEventAggregator eventAggregator) { _eventAggregator = eventAggregator; _eventAggregator.Subscribe(this); } // Since this is a demo, we will always return true public bool Validate(string userName, string passWord) { _eventAggregator.PublishOnUIThread(new UserInfoModel(){UserName = userName}); return true; } }
As you can see, we have used the PublishOnUIThread Method of Event Aggregator to publish the UserInfoModel object to anyone listening. We will now move to our ShellViewModel and ensure the class is listening. We would also see how we access the message send by the Login View Model.
Let’s begin by modifying the ShellViewModel to inject the EventAggregator to ShellViewModel, Our constructor now looks as following.
[ImportingConstructor] public ShellViewModel(IWindowManager windowManager,ILogin loginWindow, IEventAggregator eventAggregator) { eventAggregator.Subscribe(this); windowManager.ShowDialog(loginWindow); }
Now comes the most important part, we need to implement the IHandle interface, where T is the model of the event object, which in our case is UserInfoModel. The interface contains a single method Handle, which we implement soon. Prior to that, this is how our class declaration looks now.
public class ShellViewModel : PropertyChangedBase, IShell, IHandle
The Handle Method of IHandle Interface looks like following.
public void Handle(UserInfoModel message) { this.UserName = message.UserName; }
As you can see, we have assigned the UserName property of ViewModel from the message passed by the EventAggregator. That’s all we need to do. The EventAggregator has passed the message to all subscribers in the message bus for that particular event model. We then capture the message by implementing the Handle Method in IHandle interface.
One thought on “Caliburn.Micro #006 : Event Aggregators & Window Managers”