Back to Devexpress

Messenger

wpf-17474-mvvm-framework-messenger.md

latest13.6 KB
Original Source

Messenger

  • Sep 19, 2022
  • 9 minutes to read

The DevExpress.Mvvm.Messenger class allows you to implement a message exchange between modules.

The Messenger class implements the IMessenger interface.

Run Demo: Messenger

Getting Started With Messenger

The following step-by-step tutorial illustrates how to use the DevExpress.Mvvm.Messenger to send notifications from one module (ViewModel) to another:

  1. Create a Sender and Receiver View and ViewModel.

  2. Implement a method that sends messages (SendMessage in a code sample below). Bind the Send Message button within the Sender View to the method.

  3. Enable the Receiver View Model to get messages. The ViewModel’s ReceivedMessage property value is set to the message text.

The animation below illustrates the result.

Default Messenger

The static Messenger.Default property returns a default Messenger instance. The default messenger is not multi-thread safe and stores weak references.

Change the Default Messenger

To change the Default messenger, create a new Messenger instance and pass is to the static Messenger.Default property. For example, the following code replaces the Default Messenger with a multi-thread safe Messenger :

csharp
public partial class App : Application {
    public App() {
        Messenger.Default = new Messenger(isMultiThreadSafe: true, actionReferenceType: ActionReferenceType.WeakReference);
    }
}
vb
Public Partial Class App
    Inherits Application
    Public Sub New()
        Messenger.[Default] = New Messenger(isMultiThreadSafe := True, actionReferenceType := ActionReferenceType.WeakReference)
    End Sub
End Class

Send and Receive Messages

Use the following methods to send and receive messages:

csharp
public static void Register<TMessage>(this IMessenger messenger, object recipient, Action<TMessage> action);
public static void Send<TMessage>(this IMessenger messenger, TMessage message);
vb
 _
Public Shared Sub Register(Of TMessage)(messenger As IMessenger, recipient As Object, action As Action(Of TMessage))
 _
Public Shared Sub Send(Of TMessage)(messenger As IMessenger, message As TMessage)

The code sample below demonstrates how to send and receive messages of different types.

csharp
public class MyMessage {
    //...
}
public class Recipient {
    public Recipient() {
        // Receives messages of the `string` type.
        Messenger.Default.Register<string>(this, OnMessage1);
        // Receives messages of a custom `MyMessage` type.
        Messenger.Default.Register<MyMessage>(this, OnMessage2);
    }
    void SendMessages() {
        // Sends messages of the `string` type.
        Messenger.Default.Send("test");
        // Sends messages of a custom `MyMessage` type.
        Messenger.Default.Send(new MyMessage());
    }
    void OnMessage1(string message) {
        //...
    }
    void OnMessage2(MyMessage message) {
        //...
    }
}
vb
Public Class MyMessage
    '...
End Class
Public Class Recipient
    Public Sub New()
        ' Receives messages of the `string` type.
        Messenger.[Default].Register(Of String)(Me, AddressOf OnMessage1)
        ' Receives messages of a custom `MyMessage` type.
        Messenger.[Default].Register(Of MyMessage)(Me, AddressOf OnMessage2)
    End Sub
    Private Sub SendMessages()
        ' Sends messages of the `string` type.
        Messenger.[Default].Send("test")
        ' Sends messages of a custom `MyMessage` type.
        Messenger.[Default].Send(New MyMessage())
    End Sub
    Private Sub OnMessage1(message As String)
        '...
    End Sub
    Private Sub OnMessage2(message As MyMessage)
        '...
    End Sub
End Class

Send and Receive Message Descendants

When you subscribe to a message of a custom type, you can invoke your handler if a message descendant is received.

Use the following methods to work with the messages of custom inherited types.

csharp
public static void Register<TMessage>(this IMessenger messenger, object recipient, bool receiveInheritedMessagesToo, Action<TMessage> action);
public static void Send<TMessage>(this IMessenger messenger, TMessage message);
vb
 _
Public Shared Sub Register(Of TMessage)(messenger As IMessenger, recipient As Object, receiveInheritedMessagesToo As Boolean, action As Action(Of TMessage))
 _
Public Shared Sub Send(Of TMessage)(messenger As IMessenger, message As TMessage)

The code sample below demonstrates how to invoke a handler when a message descendant is received.

csharp
public class InheritedMessage : MyMessage {
    // ...
}
public class Recipient {
    public Recipient() {
        // Inherited messages are not processed with this subscription
        Messenger.Default.Register<MyMessage>(
            recipient: this, 
            action: OnMessage);
        // Inherited messages are processed with this subscription
        Messenger.Default.Register<MyMessage>(
            recipient: this, 
            receiveInheritedMessagesToo: true,
            action: OnMessage);
    }
    void SendMessages() {
        Messenger.Default.Send(new MyMessage());
        Messenger.Default.Send(new InheritedMessage());
    }
    void OnMessage(MyMessage message) {
        // ...
    }
}
vb
Public Class InheritedMessage
    Inherits MyMessage
    ' ...
End Class
Public Class Recipient
    Public Sub New()
        ' Inherited messages are not processed with this subscription
        Messenger.[Default].Register(Of MyMessage)(recipient := Me, action := AddressOf OnMessage)
        ' Inherited messages are processed with this subscription
        Messenger.[Default].Register(Of MyMessage)(recipient := Me, receiveInheritedMessagesToo := True, action := AddressOf OnMessage)
    End Sub
    Private Sub SendMessages()
        Messenger.[Default].Send(New MyMessage())
        Messenger.[Default].Send(New InheritedMessage())
    End Sub
    Private Sub OnMessage(message As MyMessage)
        ' ...
    End Sub
End Class

Messages With Tokens

You can invoke message handlers when a message with a particular token is received. Use the following methods to work with messages with tokens:

csharp
public static void Register<TMessage>(this IMessenger messenger, object recipient, object token, Action<TMessage> action);
public static void Send<TMessage>(this IMessenger messenger, TMessage message, object token);
vb
 _
Public Shared Sub Register(Of TMessage)(messenger As IMessenger, recipient As Object, token As Object, action As Action(Of TMessage))
 _
Public Shared Sub Send(Of TMessage)(messenger As IMessenger, message As TMessage, token As Object)

The code sample below demonstrates how to invoke handlers when a message with an appropriate token is received.

csharp
public enum MessageToken { Type1, Type2 }
public class Recipient {
    public Recipient() {
        Messenger.Default.Register<MyMessage>(
            recipient: this, 
            token: MessageToken.Type1,
            action: OnMessage1);
        Messenger.Default.Register<MyMessage>(
            recipient: this, 
            token: MessageToken.Type2,
            action: OnMessage2);
    }
    void SendMessages() {
        Messenger.Default.Send(message: new MyMessage(), token: MessageToken.Type1);
        Messenger.Default.Send(message: new MyMessage(), token: MessageToken.Type2);
    }
    void OnMessage1(MyMessage message) {
        //...
    }
    void OnMessage2(MyMessage message) {
        //...
    }
}
vb
Public Enum MessageToken
    Type1
    Type2
End Enum
Public Class Recipient
    Public Sub New()
        Messenger.[Default].Register(Of MyMessage)(recipient := Me, token := MessageToken.Type1, action := AddressOf OnMessage1)
        Messenger.[Default].Register(Of MyMessage)(recipient := Me, token := MessageToken.Type2, action := AddressOf OnMessage2)
    End Sub
    Private Sub SendMessages()
        Messenger.[Default].Send(message := New MyMessage(), token := MessageToken.Type1)
        Messenger.[Default].Send(message := New MyMessage(), token := MessageToken.Type2)
    End Sub
    Private Sub OnMessage1(message As MyMessage)
        '...
    End Sub
    Private Sub OnMessage2(message As MyMessage)
        '...
    End Sub
End Class

Unregister Message Handlers

Messenger With Weak References

You do not need to unregister message handlers if you use a Messenger with weak references (for example, the Default Messenger). Subscribing to messengers with weak references does not cause memory leaks.

Messenger With Strong References

You can use a Messenger instance that works with strong references:

csharp
Messenger messenger = new Messenger(isMultiThreadSafe: false, actionReferenceType: ActionReferenceType.StrongReference);
vb
Dim messenger As New Messenger(isMultiThreadSafe := False, actionReferenceType := ActionReferenceType.StrongReference)

To avoid memory leaks, unregister message handlers with any of the following methods:

csharp
// Unsubscribe from a specific message
void IMessenger.Unregister<TMessage>(object recipient, object token, Action<TMessage> action);
// Unsubscribe a recipient from all messages
void IMessenger.Unregister(object recipient);
vb
' Unsubscribe from a specific message
Sub Unregister(recipient As Object, token As Object, action As Action(Of TMessage)) Implements IMessenger(Of TMessage).Unregister
' Unsubscribe a recipient from all messages
Sub Unregister(recipient As Object) Implements IMessenger(Of TMessage).Unregister

Closures in Message Handlers

The weak reference messenger (like the Default messenger) imposes a limitation for lambda expressions with outer variables (closures).

The weak reference messenger refers to a lambda expression with a weak reference. If the garbage collector collects the lambda expression object, the message handler is not invoked.

In the code below, the lambda method refers to the text variable that is defined outside the lambda. In this case, this lambda can be collected and never called.

csharp
public class Recipient {
    public Recipient(string text) {
        // WARNING!
        // The lambda may be collected and never called.
        Messenger.Default.Register<Message>(this, x => {
            var str = text;
            //...
        });
    }
}
vb
Public Class Recipient
    Public Sub New(text As String)
        ' WARNING!
        ' The lambda may be collected and never called.
        Messenger.[Default].Register(Of Message)(Me, 
            Function(x) 
                Dim str = text
                '...
            End Function)
    End Sub
End Class

To protect a lambda expression object from being collected, declare the text variable as a property at the subscriber object level, for example:

csharp
public class Recipient {
    string MyProperty {get; set;}
    public Recipient(string text) {
        MyProperty = text;
        Messenger.Default.Register<Message>(this, x => {
            var str = MyProperty;
            //...
        });
    }
}
vb
Public Class Recipient
    Private MyProperty As String
    Public Sub New(text As String)
        MyProperty = text
        Messenger.[Default].Register(Of Message)(Me, 
            Function(x) 
                Dim str = MyProperty
                '...
            End Function)
    End Sub
End Class

You can also store the message handler as follows:

csharp
public class Recipient {
    Action<Message> messageHandler;
    public Recipient(string text) {
        messageHandler = x => {
            var str = text;
            //...
        };
        Messenger.Default.Register<Message>(this, messageHandler);
    }
}
vb
Public Class Recipient
    Private messageHandler As Action(Of Message)
    Public Sub New(text As String)
        messageHandler = Function(x) 
            Dim str = text
            '...
        End Function
        Messenger.[Default].Register(Of Message)(Me, messageHandler)
    End Sub
End Class

If your lambda expression does not use outer variables, your message handler is invoked without any limitations.