Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Behaviors for WeakReferenceMessenger and StrongReferenceMessenger #238

Closed
Arlodotexe opened this issue Jan 22, 2022 · 5 comments
Closed

Comments

@Arlodotexe
Copy link
Member

Arlodotexe commented Jan 22, 2022

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.

@ghost
Copy link

ghost commented Jan 22, 2022

Hello, 'Arlodotexe! Thanks for submitting a new feature request. I've automatically added a vote 👍 reaction to help get things started. Other community members can vote to help us prioritize this feature in the future!

@Sergio0694
Copy link
Member

This is an interesting proposal. As I mentioned on Discord, I think before discussing this in detail, we should reason about where would this go. Given this requires whatever package contains it to have a reference to the MVVM Toolkit, this automatically means it's a complete non-starter for any of the existing packages in the Windows Community Toolkit. As such, we'd need to create a new package to contain this code, such as CommunityToolkit.Uwp.Mvvm or similar. Few questions:

  • Is there actually interest in creating a new package specifically to support the MVVM Toolkit on UWP?
  • Would there be other APIs that are needed that would go there? By that I mean, creating a whole new package just for this messenger behavior is a tough sell, and I don't really see it happening unless we reach some critical mass in proposed APIs.

@michael-hawker
Copy link
Member

Yeah, we wouldn't want the Behaviors package itself to also pull in the MVVM library, I think. I mean it does pull in the Animations package, and soon WinUI itself, but MVVM is generally isolated.

I really wish we could have dependencies only be pulled in if certain code is used... would make things so much easier.

I mean in the new CommunityToolkit root space, we do have the ability to ship new packages more easily. So it's not that big a deal to make a new package. Main thing is if we'd want to open the door to have more specific helpers for MVVM stuff. We'd also probably just want to call it CommunityToolkit.WinUI.Mvvm and have it work for both UWP and WinUI 3 vs. having two package roots...

I do like this idea though. Definitely seems like a good thing to at least experiment with in 🧪 Labs... 😉

@Arlodotexe
Copy link
Member Author

It may be worth considering scrapping the "Filters" idea and creating a separate, generic conditional trigger wrapper.

@michael-hawker
Copy link
Member

FYI @Sergio0694 this may also play into what you've been playing with lately too as a separate or coordinated lab idea.

@Arlodotexe Arlodotexe transferred this issue from CommunityToolkit/WindowsCommunityToolkit Jul 28, 2022
@CommunityToolkit CommunityToolkit locked and limited conversation to collaborators Jul 28, 2022
@Arlodotexe Arlodotexe converted this issue into discussion #239 Jul 28, 2022

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests

3 participants