使用CommunityToolkit.Mvvm和DI跨ViewModel更新属性失败问题
问题:WPF ViewModel间消息通信失效,实现IRecipient后属性未更新
我正在实现一个简单WPF应用,MainWindow嵌套两个UserControl,各有独立ViewModel。SenderViewModel里有选择目录的命令,选中后要给所有注册的ViewModel发消息。现在有三个视图、三个ViewModel(含MainViewModel)加依赖注入容器,虽然实现了IRecipient接口,但ReceiverViewModel的属性始终未更新。
相关代码
ViewModel代码
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)); } } // 模拟文件夹选择对话框 [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; } }
ReceiverView.xaml代码
<Grid> <TextBlock Text="{Binding SelectedDirectory, UpdateSourceTrigger=PropertyChanged}" Width="120" Height="22"/> </Grid>
SenderView.xaml代码
<StackPanel> <Button Command="{Binding SelectDirectoryCommand}" Width="120" Height="22"/> <TextBlock Text="{Binding SelectedDirectory, UpdateSourceTrigger=PropertyChanged}" Width="120" Height="22"/> </StackPanel>
MainWindow代码
<StackPanel> <local:SenderView DockPanel.Dock="Top"/> <local:ReceiverView DockPanel.Dock="Bottom"/> </StackPanel>
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(); } }
视图DataContext设置(以ReceiverView为例)
public ReceiverView() { InitializeComponent(); DataContext = App.Current.Services.GetService<ReceiverViewModel>(); }
问题原因及修复方案
核心问题
- ReceiverViewModel未注册到Messenger:实现
IRecipient<T>接口后,必须主动向Messenger注册接收者,否则无法接收消息。 - 属性赋值未触发通知:直接给
selectedDirectory字段赋值(而非[ObservableProperty]生成的SelectedDirectory属性),不会触发PropertyChanged事件,UI无法感知更新。 - 可选:Messenger实例一致性问题:若未统一注入Messenger实例,可能导致发送和接收用的不是同一个信使。
修复步骤
1. 修复ReceiverViewModel的属性赋值
将字段赋值改为自动生成的属性赋值,确保触发UI更新:
public void Receive(PropertyChangedMessage<string> message) { SelectedDirectory = message.NewValue; // 使用属性而非字段 }
2. 注册ReceiverViewModel到Messenger
两种方式二选一:
- 方式一:构造函数手动注册
public partial class ReceiverViewModel : ObservableRecipient, IRecipient<PropertyChangedMessage<string>> { [ObservableProperty] private string selectedDirectory; public ReceiverViewModel() { Messenger.Register<PropertyChangedMessage<string>>(this, Receive); } public void Receive(PropertyChangedMessage<string> message) { SelectedDirectory = message.NewValue; } }
- 方式二:利用ObservableRecipient的IsActive属性
public partial class ReceiverViewModel : ObservableRecipient, IRecipient<PropertyChangedMessage<string>> { [ObservableProperty] private string selectedDirectory; public ReceiverViewModel() { IsActive = true; // 自动注册所有IRecipient接口实现 } public void Receive(PropertyChangedMessage<string> message) { SelectedDirectory = message.NewValue; } }
3. 统一注入Messenger实例(推荐)
在DI容器中注册单例Messenger,确保所有ViewModel使用同一实例:
private static IServiceProvider ConfigureServices() { var services = new ServiceCollection(); services.AddTransient<MainViewModel>(); services.AddTransient<SenderViewModel>(); services.AddTransient<ReceiverViewModel>(); // 注入单例Messenger services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default); return services.BuildServiceProvider(); }
然后在ViewModel构造函数中接收:
// SenderViewModel public partial class SenderViewModel : ObservableRecipient { private readonly IMessenger _messenger; public SenderViewModel(IMessenger messenger) { _messenger = messenger; } 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)); } } } // ReceiverViewModel public partial class ReceiverViewModel : ObservableRecipient, IRecipient<PropertyChangedMessage<string>> { private readonly IMessenger _messenger; [ObservableProperty] private string selectedDirectory; public ReceiverViewModel(IMessenger messenger) { _messenger = messenger; _messenger.Register<PropertyChangedMessage<string>>(this, Receive); } public void Receive(PropertyChangedMessage<string> message) { SelectedDirectory = message.NewValue; } }
4. 调整ViewModel生命周期(可选)
若希望ViewModel与UI组件生命周期一致,可将AddTransient改为AddSingleton或AddScoped:
services.AddSingleton<SenderViewModel>(); services.AddSingleton<ReceiverViewModel>();
内容的提问来源于stack exchange,提问作者Halil Ibrahim Özcan
相关产品推荐
相关产品推荐

