Update Properties across Viewmodels with CommunityToolkit.Mvvm and Dependency Injection

I am trying to implement a simple WPF Application, with two nested UserControls inside the Mainwindow, and each UserControl having their own viewmodel. One Viewmodel implements a command which allows the user to choose a Directory. When a folder is chosen inside the first viewmodel, it is supposed to send a message to all other registered viewmodels. So we have three views, three viewmodels (including MainViewModel) and the Dependency Container. Though I implemented IRecipient, the ReceiverVM does not update its property.

Here are my Viewmodels, for convenience I've put them in the same file:

namespace MessagingReproduction;

public partial class MainViewModel : ObservableRecipient
{

}

public partial class SenderViewModel : ObservableRecipient
{
    
    private string selectedDirectory;
    public string SelectedDirectory
    {
        get => selectedDirectory;
        set
        {
            var oldValue = selectedDirectory;
            SetProperty(ref selectedDirectory, value);
            Messenger.Send<PropertyChangedMessage<string>>(new PropertyChangedMessage<string>(this, nameof(SelectedDirectory), oldValue, selectedDirectory));
        }
    }

    // simulate a FolderDialog
    [RelayCommand]
    private void SelectDirectory()
        => this.SelectedDirectory = "C:";
}

public partial class ReceiverViewModel : ObservableRecipient, IRecipient<PropertyChangedMessage<string>>
{
    [ObservableProperty]
    private string selectedDirectory;

    public void Receive(PropertyChangedMessage<string> message)
    {
        selectedDirectory = message.NewValue;
    }
}

My ReceiverView.xaml:

    <Grid>
        <TextBlock Text="{Binding SelectedDirectory, UpdateSourceTrigger=PropertyChanged}"
                   Width="120" Height="22"/>
    </Grid>

my SenderView.xaml:

    <StackPanel>
        <Button Command="{Binding SelectDirectoryCommand}"
                Width="120" Height="22"/>
        <TextBlock Text="{Binding SelectedDirectory, UpdateSourceTrigger=PropertyChanged}"
                   Width="120" Height="22"/>
    </StackPanel>

The MainWindow:

    <StackPanel>
        <local:SenderView DockPanel.Dock="Top"/>
        <local:ReceiverView DockPanel.Dock="Bottom"/>
    </StackPanel>

The Setup for Dependency Injection insinde App.xaml.cs:

    public partial class App : Application
    {
        public App()
        {
            Services = ConfigureServices();
            InitializeComponent();
        }

        public new static App Current => (App)Application.Current;
        public IServiceProvider Services { get; }
        private static IServiceProvider ConfigureServices()
        {
            var services = new ServiceCollection();
            services.AddTransient<MainViewModel>();
            services.AddTransient<SenderViewModel>();
            services.AddTransient<ReceiverViewModel>();

            return services.BuildServiceProvider();
        }
    }

Setting Datacontexts inside each view like so:

        public ReceiverView()
        {
            InitializeComponent();
            DataContext = App.Current.Services.GetService<ReceiverViewModel>();
        }

Despite the implementation of IRecipient, the viewmodels do not communicate. Note that my Views, except the Mainwindow, are UserControls.

Upvotes: 1

Views: 1963

Answers (1)

Erwin Donker
Erwin Donker

Reputation: 21

I am only learning to use the MVVM toolkit myself, but it seems your are not implementing any message and also you are not registering your ReceiverViewModel as a recipient for the message.

First, create the message:

public class ChangeDirectoryMessage : ValueChangedMessage<string>
{
    public ChangeDirectoryMessage(string newDirectory) : base(newDirectory)
    {        
    }
}

Then, register the message in the receiving class:

public class ReceiverViewModel : ObservableObject, IRecipient<ChangeDirectoryMessage>
{
    [ObservableProperty]
    private string selectedDirectory;

    public ReceiverViewModel()
    {
        WeakReferenceMessenger.Default.Register<ChangeDirectoryMessage>(this);
    }

    public void Receive(ChangeDirectoryMessage message)
    {
        selectedDirectory = message.NewValue;
    }
}

And finally send a message from anywhere when the selected directory has changed:

WeakReferenceMessenger.Default.Send(new ChangeDirectoryMessage(selectedDirectory));

There is a good example on how to use this messenger in the documentation:

https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/messenger

Upvotes: 2

Related Questions