WPF中TabControl的两个ViewModel间值更新通知问题
Great question—let's break down what's happening here and fix that binding update issue, plus talk through the best approaches for sharing state between your ViewModels in WPF, since you have that React/Redux background to lean on.
Why Your Current Setup Isn't Updating
The core problem is that your TemplatesTabViewModel doesn't react to changes in ExamsTabViewModel:
- Your
StringFromExamsTabproperty is a simple getter that pulls the current value ofExamStringonce during initial binding. - WPF's binding engine only re-evaluates properties if the view model owning the property raises the
PropertyChangedevent, or if the source object itself changes. SinceTemplatesTabViewModeldoesn't listen toExamsTabViewModel'sPropertyChangedevents, it never knows when to tell the UI to update.
Also, your tech lead is right about avoiding putting shared state in MainWindow as a resource—MVVM best practice keeps state in ViewModels, not View layer elements.
Solution 1: Direct ViewModel Event Listening
This follows your lead's suggestion of having ViewModels share information directly. We'll make TemplatesTabViewModel implement INotifyPropertyChanged and listen for changes in ExamsTabViewModel:
Update TemplatesTabViewModel
namespace Gui.Tabs.TemplatesTab { public partial class TemplatesHome : Page { public TemplatesHome() { InitializeComponent(); DataContext = ViewModel; // Clean up event subscription to avoid memory leaks Unloaded += (s, e) => ExamsHome.ViewModel.PropertyChanged -= ViewModel.OnExamsPropertyChanged; } public static readonly TemplatesTabViewModel ViewModel = new TemplatesTabViewModel(); } public class TemplatesTabViewModel : INotifyPropertyChanged { public TemplatesTabViewModel() { // Subscribe to ExamsViewModel's property changes ExamsHome.ViewModel.PropertyChanged += OnExamsPropertyChanged; } public string StringFromExamsTab { get => ExamsHome.ViewModel.ExamString; } private void OnExamsPropertyChanged(object sender, PropertyChangedEventArgs e) { // Trigger update only when ExamString changes if (e.PropertyName == nameof(ExamsTabViewModel.ExamString)) { OnPropertyChanged(nameof(StringFromExamsTab)); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
Now when ExamString updates, TemplatesTabViewModel catches the event and tells the UI to refresh StringFromExamsTab.
Solution 2: Shared State Container (Redux-Inspired)
Since you're familiar with Redux, this approach will feel more natural. We'll create a single global state container that both ViewModels depend on, centralizing shared state:
Step 1: Create a Global AppState Class
namespace Gui.Core { public class AppState : INotifyPropertyChanged { // Singleton instance for global access public static readonly AppState Instance = new AppState(); private string _examString = "Default exam string"; public string ExamString { get => _examString; set { _examString = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
Step 2: Update ExamsTabViewModel to Use AppState
namespace Gui.Tabs.ExamsTab { public partial class ExamsHome : Page { public ExamsHome() { InitializeComponent(); DataContext = ViewModel; } public static readonly ExamsTabViewModel ViewModel = new ExamsTabViewModel(); } public class ExamsTabViewModel : INotifyPropertyChanged { public string ExamString { get => AppState.Instance.ExamString; set { AppState.Instance.ExamString = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
Step 3: Update TemplatesTabViewModel to Use AppState
namespace Gui.Tabs.TemplatesTab { public partial class TemplatesHome : Page { public TemplatesHome() { InitializeComponent(); DataContext = ViewModel; Unloaded += (s, e) => AppState.Instance.PropertyChanged -= ViewModel.OnAppStateChanged; } public static readonly TemplatesTabViewModel ViewModel = new TemplatesTabViewModel(); } public class TemplatesTabViewModel : INotifyPropertyChanged { public TemplatesTabViewModel() { AppState.Instance.PropertyChanged += OnAppStateChanged; } public string StringFromExamsTab { get => AppState.Instance.ExamString; } private void OnAppStateChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(AppState.ExamString)) { OnPropertyChanged(nameof(StringFromExamsTab)); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
This approach centralizes your shared state, making it easy to add more shared properties later and keeping your ViewModels focused on their specific UI logic—just like Redux!
Key Takeaways
- Always implement
INotifyPropertyChangedon any ViewModel that has bound properties that need to update. - For cross-ViewModel state sharing, either:
- Have ViewModels listen directly to each other's
PropertyChangedevents (good for small, isolated cases), or - Use a centralized state container (better for larger apps, aligns with Redux patterns you know).
- Have ViewModels listen directly to each other's
- Avoid putting state in View layer elements like
MainWindowresources—it breaks MVVM separation of concerns.
内容的提问来源于stack exchange,提问作者Jonathan Tuzman

