事象
ダイアログを開くメッセージを送信したら同じダイアログが何回も表示された。
自作のメッセンジャー
public static class Messenger
{
private static readonly Dictionary<MessageType, List<Action<Message>>> _handlers
= new();
public static void Register(MessageType type, Action<Message> action)
{
if (!_handlers.ContainsKey(type))
_handlers[type] = new List<Action<Message>>();
_handlers[type].Add(action);
}
public static void Send(Message message)
{
if (_handlers.TryGetValue(message.Type, out var actions))
{
foreach (var action in actions)
action(message);
}
}
}送信側(ViewModel)ではWeakReferenceMessenger.Default.Sendで送信
※よくわからずWeakReferenceMessengerを使ってた
受信側(View)
public ViewBase()
{
//viewを呼び出したら受信の設定をする
Loaded += OnLoaded;
//閉じたら受信の設定を削除する
Unloaded += OnUnloaded;
}
// 受信の設定をする
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (DataContext == null)
{
// ViewModelのインスタンスを作成
DataContext = ViewModelLocator.Resolve(this);
}
RegisterMessages();
}
// 受信の設定を削除する
private void OnUnloaded(object sender, RoutedEventArgs e)
{
WeakReferenceMessenger.Default.UnregisterAll(this);
}
起動して最初にダイアログを出すときは1回表示されて、
別のダイアログを出そうとすると、そのダイアログが2回表示された。
同じ要領で3回4回って同じダイアログが繰り返し表示されるようになっていった
理由
2つあった
自作メッセンジャーの受信の設定が削除できていなかった
渡したいパラメータがいっぱいあったからメッセンジャーを自作していて、
それ自体は問題なく動いてたんだけど、
受信の設定を削除するためのWeakReferenceMessenger.Default.UnregisterAll(this);
↑この記述は自作のメッセンジャー相手には効いてなかった。
新しくviewが呼ばれる度にどんどん受信の設定が登録だけされていく状態
ターゲットを指定していなかった
Viewでメッセージを受信したときに、
「メッセージのパラメータに○○が含まれてたら△△の画面を表示する」
みたいな分岐をさせるべきだったのに、
させてなかった。
つまり全部のViewに対して受信の設定が登録されていて、
関係ないViewからもダイアログが呼び出されていた
送信も受信もWeakReferenceMessengerを使う
まず自作のメッセンジャーは削除した。
せっかくCommunityToolkit.MVVMを使ってるから
WeakReferenceMessengerをちゃんと使う方針にシフトしたよ。
送信側(ViewModel)
どのViewModelにも継承させる基底クラスで
各メッセージの種類ごとに専用のメッセージ送信メソッドを作った
↓これは警告ダイアログとか確認ダイアログ用の送信メソッド
/// <summary>
/// ポップアップを出すときのメッセージ送信
/// </summary>
/// <param name="target">対象のViewModel</param>
/// <param name="text">本文</param>
/// <param name="title">タイトル</param>
/// <param name="button">ボタン</param>
/// <param name="icon">アイコン</param>
/// <returns>どのボタンを押したか</returns>
protected MessageBoxResult SendPopupMessage(string target, string text, string title = "注意", MessageBoxButton button = MessageBoxButton.OK,
MessageBoxImage icon = MessageBoxImage.Information)
{
PopupMessage message = new()
{
Target = target,
Title = title,
Text = text,
Button = button,
Icon = icon,
};
WeakReferenceMessenger.Default.Send(message);
return message.Result;
}実際に処理の中でダイアログを出したいときにはこんな記述
var result = SendPopupMessage(nameof(xxxxxViewModel)
, $"本文"
, button: MessageBoxButton.YesNo
, icon: MessageBoxImage.Question);受信側(View)
どのxxxView.csにも継承させるクラスで
画面を開いたら受信の設定を登録して、
画面を閉じたら受信の設定を削除する処理を記述した。
public class ViewBase : Window
{
public ViewBase()
{
Loaded += OnLoaded;
Closed += OnClosed;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (DataContext == null)
{
// ViewModelのインスタンスを作成
DataContext = ViewModelLocator.Resolve(this);
}
RegisterMessages();
}
private void OnClosed(object? sender, EventArgs e)
{
WeakReferenceMessenger.Default.UnregisterAll(this);
}
// 継承先でオーバーロードして実装
protected virtual void RegisterMessages()
{
}
}これを継承してるxxxView.csでは
RegisterMessages()のオーバーロードをして受信した後の処理を記述した
それと、
if (msg.Target == nameof(xxxxxViewModel))で
View自身に関係あるViewModelからのメッセージなのかどうかを判定するようにした。
protected override void RegisterMessages()
{
// 閉じる
WeakReferenceMessenger.Default.Register<WindowCloseMessage>(
this,
(r, msg) =>
{
if (msg.Target == nameof(xxxxxViewModel))
{
Close();
}
});
// 確認、エラーのポップアップを表示
WeakReferenceMessenger.Default.Register<PopupMessage>(
this,
(r, msg) =>
{
if (msg.Target == nameof(xxxxxViewModel))
{
msg.Result = MessageBox.Show(
msg.Text,
msg.Title,
msg.Button,
msg.Icon
);
}
});
}まとめ
- Register()は受信処理を登録するメソッド
- 同じRegister()を何度も呼ぶと、その数だけ処理が実行される
- 画面を閉じる際にはUnregisterAll()で登録解除する
- WeakReferenceMessengerを使用すると管理しやすい
- 「Registerした回数=実行回数」になる
