1

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.

  • 1
    Since you're registering your VMs as Transient, a new instance of the view model will be created everytime the corresponding view is recreated. That might result in your VMs seemingly not updating. Try changing the `services.AddTransient();` to `services.AddSingleton();` and see if that resolves the problem. – Michal Diviš Dec 12 '22 at 12:29
  • Unfortunately, changing to AddSingleton didn't bring any changes. – Halil Ibrahim Özcan Dec 12 '22 at 12:39
  • @MichalDiviš It ended up working, but only after I brought both Receiver- and Senderviewmodel together inside the MainViewModel. Thanks for your help! – Halil Ibrahim Özcan Dec 16 '22 at 13:01

1 Answers1

2

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

  • I have been tinkering with the library for some time now, and my understanding is that you do not have to use the Register-Method in your ReceiverViewModel-Constructor IF you implement IRecipient. For reference, download the sample app from Microsoft Store and go to the Messaging-Section. But just by looking at your implementation I can tell that it's going to work. My problem was that I didn't put the viewmodels in the same scope, e.g. a mainviewmodel. – Halil Ibrahim Özcan Jan 13 '23 at 18:23