Problem
The MVVM Toolkit has a StrongReferenceMessenger and a WeakReferenceMessenger, used for relaying messages via the messenger pattern.
These APIs are extremely handy for passing messages around to decoupled parts of the app, but they require code-behind to send and receive messages.
In scenarios where the user may prefer or be forced to write only XAML (such as retemplating a control), it becomes cumbersome to send and receive messages with the default Messenger.
Solution
It would useful to developers if they had the ability to send and receive messages for WeakReferenceMessenger
and StrongReferenceMessenger
via Behavior actions and triggers in XAML.
tl;dr
XAML has Behaviors.
Behaviors are like bacon.
Messenger needs bacon.
Examples
Sending a message
The user specifies a SendMessageAction
, changes the messenger to Weak
or Strong
as needed, and provides the model to send.
<ItemsControl x:Name="UsersList" ItemsSource="{x:Bind AvailableUsers}" SelectionMode="Single">
<Interactivity:Interaction.Behaviors>
<Interactions:EventTriggerBehavior EventName="SelectionChanged">
<tk:SendMessageAction Messenger="Weak">
<tk:SendMessageAction.Message>
<messages:UserChangedMessage User="{Binding SelectedItem, ElementName=UsersList}" />
<tk:SendMessageAction.Message>
</tk:SendMessageAction>
</Interactions:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</ItemsControl>
Receiving a message
Unconditional triggering
It would be easy to just invoke an action when a message of a certain type is emitted.
<UserControl x:Name="Container">
<Interactivity:Interaction.Behaviors>
<tk:ReceiveMessageTrigger Messenger="Weak" Type="messages:UserChangedMessage">
<Interactions:ChangePropertyAction TargetObject="{Binding ElementName=Container}" PropertyName="Visibility" Value="Collapsed"/>
</tk:ReceiveMessageTrigger>
</Interactivity:Interaction.Behaviors>
</UserControl>
Conditional triggering
However, unconditional triggering doesn't fit most user scenarios. We need the ability to conditionally execute the behavior based on data in the model, since the action performed often depends on what data is in the message.
There's a few options here. Which approach is best is open to discussion. (I'm personally a fan of the filters approach.)
Using filters declared in XAML
This is similar to DataTriggerBehavior, with a few key differences
- A filter is always compared against received messages of the same type.
- Multiple filters can be declared.
- Multiple filters are treated as implicitly
&&
(and
).
- Can be changed to
||
(or
) by declaring Optional="True"
.
<UserControl x:Name="Container">
<Interactivity:Interaction.Behaviors>
<tk:ReceiveMessageTrigger Messenger="Weak" Type="messages:UserChangedMessage">
<tk:ReceiveMessageTrigger.Filters>
<tk:ReceivedMessageFilter Property="IsLoggedIn" ComparisonCondition="Equal" Value="False" Optional="True" />
<tk:ReceivedMessageFilter Property="CanLogIn" ComparisonCondition="Equal" Value="True" />
</tk:ReceiveMessageTrigger.Filters>
<Interactions:ChangePropertyAction TargetObject="{Binding ElementName=Container}" PropertyName="Visibility" Value="Collapsed"/>
</tk:ReceiveMessageTrigger>
</Interactivity:Interaction.Behaviors>
</UserControl>
Using a converter
This option has the most flexibility but requires C# code and a questionable usage of converters.
public sealed class CustomUserChangedMessageFilterConverter : IValueConverter
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Convert(UserChangedMessage value)
{
return !value.IsLoggedIn;
}
public object Convert(object value, Type targetType, object parameter, string language)
{
return value is UserChangedMessage message && Convert(message);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
<UserControl x:Name="Container">
<Interactivity:Interaction.Behaviors>
<tk:ReceiveMessageTrigger Messenger="Weak" Type="messages:UserChangedMessage" MessageFilterConverter="{StaticResource CustomUserChangedMessageFilterConverter}">
<Interactions:ChangePropertyAction TargetObject="{Binding ElementName=Container}" PropertyName="Visibility" Value="Collapsed"/>
</tk:ReceiveMessageTrigger>
</Interactivity:Interaction.Behaviors>
</UserControl>
Additional info
Addressing one big potential concern ahead of time:
"But it's an anti-pattern!"
From what I've read, it's standard practice to use the Messenger pattern in ViewModels almost exclusively. You'd be hard pressed to find an example of the messenger pattern used outside of a ViewModel.
At some point it became the default way to use the Messenger pattern, and after using it in combination with pure trickle-down MVVM for a few years, I'm really not sure why.
"Who decided that anyway?"
In my ventures developing apps, I've found this statement to be flawed for a few reasons.
About 90% of the time, controls do not / should not exclusively use a ViewModel for internal implementation.
Instead, almost all well designed Controls use DependencyProperties to consume and bind data.
Controls may consume an existing "backend/data" ViewModel via DependencyProperty.
Controls can add more DependencyProperties, event handlers, and more for custom behavior.
If needed, the control can wrap a given "backend/data" VM with a control-specific ViewModel if the data structure doesn't quite meet its needs.
And so on.
What is it, then?
The messenger pattern itself closely resembles a pub-sub pattern, and so all decoupling benefits of the pub-sub pattern come with it, no matter the context, and even outside of ViewModels.
For example
The messenger pattern is fantastic for general app navigation. But if a well-crafted control, which uses DependencyProperties to consume and bind data, doesn't need a ViewModel, then how do you send a navigation message?
You do it in the Control code-behind. Or, with this new Behavior, you could do it directly in XAML.
Help us help you
Yes, I'd like to be assigned to work on this item.