Search code examples
c#.netwpfmvvmcommunity-toolkit-mvvm

CommunityToolkit.MVVM send message identified by token to specific view model


I need a help with CommunityToolkit.MVVM to utilize messenger for communication between view models. The issue I have is that that at the same time user can open the same view twice (because it's a desktop app) so simple implementation of messenger doesn't work for me - I need to register my view model with some token to so it only listens for specific view model (I have trouble finding any example in documentation).

Thank you for any help in advance.

I tried to simplify the code to include only needed parts of it.

UserListView.xaml

<UserControl x:Class="ProjectIdeas.Core.Users.View.UsersListView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:syncfusion="http://schemas.syncfusion.com/wpf"
             xmlns:viewmodel="clr-namespace:ProjectIdeas.Core.Users.ViewModel"
             xmlns:local="clr-namespace:ProjectIdeas.Core.Users.View"
             mc:Ignorable="d">

    <UserControl.DataContext>
        <viewmodel:UsersListViewModel />
    </UserControl.DataContext>

    <Grid>

        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <syncfusion:SfDataGrid
            ItemsSource="{Binding Users}"
            SelectedItem="{Binding SelectedUser}" />

        <local:UserDetailView Grid.Column="1" />

    </Grid>

</UserControl>

UsersListViewModel.cs

public sealed partial class UsersListViewModel : ObservableObject
{

    [ObservableProperty]
    private UserModel? _selectedUser;

    partial void OnSelectedUserChanged(UserModel? value)
    {
        WeakReferenceMessenger.Default.Send(new UserDetailViewSelectedUserChangedMessage(value));
    }

}

UserDetailViewModel.cs

public sealed partial class UserDetailViewModel : ObservableObject, IRecipient<UserDetailViewSelectedUserChangedMessage>
{

    [ObservableProperty]
    private UserModel? _user;

    public UserDetailViewModel()
    {
        WeakReferenceMessenger.Default.Register<UserDetailViewSelectedUserChangedMessage>(this);
    }

    public void Receive(UserDetailViewSelectedUserChangedMessage message)
    {
        User = message.Value;
    }

}

UserDetailViewSelectedUserChangedMessage.cs

public class UserDetailViewSelectedUserChangedMessage : ValueChangedMessage<UserModel?>
{
    public UserDetailViewSelectedUserChangedMessage(UserModel? value) : base(value) {}
}

Solution

  • IMHO, a UserControl shouldn't have a ViewModel, especially assigned directly like this, and, IMHO again, you should implement this without a ViewModel and do this in code-behind instead.

    Something like this.

    UsersListView.xaml.cs

    public sealed partial class UsersListView: UserControl
    {
        public static readonly DependencyProperty UsersProperty =
            DependencyProperty.Register(
                nameof(Users),
                typeof(object),
                typeof(UsersListView),
                new PropertyMetadata(default));
    
        public static readonly DependencyProperty SelectedUserProperty =
            DependencyProperty.Register(
                nameof(SelectedUser),
                typeof(UserModel),
                typeof(UsersListView),
                new PropertyMetadata(default));
    
        public UsersListView()
        {
            this.InitializeComponent();
        }
    
    
        public object Users
        {
            get => (object)GetValue(UsersProperty);
            set => SetValue(UsersProperty, value);
        }
    
        public UserModel SelectedUser
        {
            get => (UserModel)GetValue(SelectedUserProperty);
            set => SetValue(SelectedUserProperty, value);
        }
    }
    

    UsersListView.xaml

    <syncfusion:SfDataGrid
        ItemsSource="{x:Bind Users, Mode=OneWay}"
        SelectedItem="{x:Bind SelectedUser, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    
    <local:UserDetailView
        Grid.Column="1"
        User="{x:Bind SelectedUser, Mode=OneWay}" />