I found a good article about basic event aggregation on Stack Overflow and decided to create a demonstration that runs on WPF using MVVM. The original article was written by Mike Hankey.
https://www.codeproject.com/Articles/5376132/EventAggregator-My-Take
I recommend you read his article first. I think it needs an unsubscribe method otherwise references to the handlers will prevent objects being removed from memory. Here's my fully working WPF version.
Use Visual Studio to create a C# WPF Core project called EventAggregator. We will start by creating some base classes.
Add a class called NotifyWindow. It inherits Window and adds NotifyPropertyChanged functionality.
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace EventAggregator
{
public class NotifyWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void SetProperty<T>(ref T storage, T value, [CallerMemberName] string name = "")
{
if (!Object.Equals(storage, value))
{
storage = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
}
namespace EventAggregator
{
public class EventAggregator<T>
{
private readonly Dictionary<Type, List<Delegate>> subscribers = new Dictionary<Type, List<Delegate>>();
public void Subscribe<TEvent>(Action<TEvent> handler)
{
Type eventType = typeof(TEvent);
if (!subscribers.TryGetValue(eventType, out var handlers))
{
handlers = new List<Delegate>();
subscribers[eventType] = handlers;
}
handlers.Add(handler);
}
public bool Unsubscribe<TEvent>(Action<TEvent> handler)
{
if (subscribers.TryGetValue(typeof(TEvent), out var handlers))
{
return handlers.Remove(handler);
}
return false;
}
public void Publish<TEvent>(TEvent message)
{
Type eventType = typeof(TEvent);
if (subscribers.TryGetValue(eventType, out var handlers))
{
foreach (Action<TEvent> handler in handlers)
{
handler.Invoke(message);
}
}
}
}
}
namespace EventAggregator
{
public interface IEventArgs<T>
{
T GetData();
}
public class Test
{
public class TestMessage : IEventArgs<String>
{
String _Message = "";
public TestMessage(string s)
{
_Message = s;
}
public String GetData()
{
return _Message;
}
}
}
}
using System.Windows;
namespace EventAggregator
{
public partial class App : Application
{
public static EventAggregator<IEventArgs<Object>> g_Aggregator = new EventAggregator<IEventArgs<Object>>();
}
}
The application will display a main window and a dialog window. The two windows will communicate through a publish/subscribe model. Add a Window called DialogWindow. The xaml and C# look like this. Changing the value of DialogMessage causes all subscribers to be notified.
<local:NotifyWindow x:Class="EventAggregator.DialogWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:EventAggregator"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="DialogWindow" Height="450" Width="800">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Please enter a message for the main window"/>
<TextBox Text="{Binding DialogMessage, UpdateSourceTrigger=PropertyChanged}" Width="200" BorderBrush="Gray" BorderThickness="1"/>
</StackPanel>
</local:NotifyWindow>
namespace EventAggregator
{
public partial class DialogWindow : NotifyWindow
{
private string _DialogMessage = "";
public string DialogMessage
{
get => _DialogMessage;
set
{
SetProperty(ref _DialogMessage, value);
App.g_Aggregator.Publish(new Test.TestMessage(DialogMessage));
}
}
public DialogWindow()
{
InitializeComponent();
}
}
}
<local:NotifyWindow x:Class="EventAggregator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:EventAggregator"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800">
<StackPanel Orientation="Vertical">
<TextBlock Text="A message from the dialog window"/>
<TextBlock Text="{Binding DialogMessage}"/>
</StackPanel>
</local:NotifyWindow>
namespace EventAggregator
{
public partial class MainWindow : NotifyWindow
{
private string _DialogMessage = "";
public string DialogMessage
{
get => _DialogMessage;
set => SetProperty(ref _DialogMessage, value);
}
public MainWindow()
{
this.Unloaded += MainWindow_Unloaded;
InitializeComponent();
DialogWindow dw = new DialogWindow();
App.g_Aggregator.Subscribe<Test.TestMessage>(HandleTestMessage);
dw.Show();
}
private void MainWindow_Unloaded(object sender, System.Windows.RoutedEventArgs e)
{
App.g_Aggregator.Unsubscribe<Test.TestMessage>(HandleTestMessage);
}
private void HandleTestMessage(Test.TestMessage message)
{
DialogMessage = message.GetData();
}
}
}
No comments:
Post a Comment