C# MVVM 中兩個 View Model 之間如何分享組件

文章目錄

需求

我有兩個 View Model — MainViewModel 和 OptionViewModel -, 他們共用一個名為 CustomConfiguration 的元件. 需求是在 MainWindow 中將資料載入 MainViewModel,並當在 MainWindow 上點選 Option 按鈕時, 會開啟 OptionWindow,並將 CustomConfiguration 資料傳遞給它.

Code

MainWindow.xaml

這裡只有 bind data (CustomConfig.GeneralSettings.IsLogEnabled) 和設定 command.

1<Window.DataContext>
2    <local:MainViewModel />
3</Window.DataContext>
4<StackPanel>
5    <TextBlock Text="{Binding CustomConfig.GeneralSettings.IsLogEnabled}" />
6    <Button Content="Open Options" Command="{Binding OpenOptionsCommand}" />
7</StackPanel>

OptionWindow.xaml

和 MainWindow.xaml 類似, 這裡只有 bind data (CustomConfig.GeneralSettings.IsLogEnabled) 和設定 command..

1<Window.DataContext>
2    <local:OptionViewModel />
3</Window.DataContext>
4<StackPanel>
5    <CheckBox Content="Enable Logging" IsChecked="{Binding CustomConfig.GeneralSettings.IsLogEnabled}" />
6    <Button Content="OK" Command="{Binding OKCommand}" />
7</StackPanel>

MainWindow.xaml.cs

這裡沒有特別的指令.

1public partial class MainWindow : Window {
2    public MainWindow() {
3        InitializeComponent();
4    }
5}

OptionWindow.xaml.cs

這裡沒有特別的指令.

1public partial class OptionWindow : Window {
2    public OptionWindow() {
3        InitializeComponent();
4    }
5}

OptionWindow.xaml.cs => If you want to clone data.

在前一個版本中,CustomConfiguration 的 instance 從 MainViewModel 傳到 OptionViewModel,這樣它們就共用一個實例. 如果你想要在 OptionViewModel 中 複製 CustomConfiguration,你可以使用這個 constructor. => 差別在於, 你在 OptionViewModel 中的改變, 不會即時反應到 MainViewModel.

1        public OptionWindow(CustomConfiguration config) {
2            InitializeComponent();
3
4            //_initialConfig = config;
5            _viewModel = new OptionViewModel(config.Clone());
6            DataContext = _viewModel;
7        }

CustomConfiguration.cs

 1public class CustomConfiguration : INotifyPropertyChanged {
 2    private GeneralSettingsClass _generalSettings = new GeneralSettingsClass();
 3    
 4    public GeneralSettingsClass GeneralSettings {
 5        get => _generalSettings;
 6        set {
 7            _generalSettings = value;
 8            OnPropertyChanged();
 9        }
10    }
11    
12    public event PropertyChangedEventHandler PropertyChanged;
13    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
14        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
15    }
16}
17
18public class GeneralSettingsClass : INotifyPropertyChanged {
19    private bool _isLogEnabled = false;
20    
21    public bool IsLogEnabled {
22        get => _isLogEnabled;
23        set {
24            _isLogEnabled = value;
25            OnPropertyChanged();
26        }
27    }
28    
29    public event PropertyChangedEventHandler PropertyChanged;
30    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
31        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
32    }
33}

MainViewModel.cs

如果您想要共用 CustomConfiguration 的一個實例,請務必檢查 Shared 變數,並將其設置為 true. 如果您想要在 OptionViewModel 中創建一個 CustomConfiguation 的副本,請將其設置為 false.

 1public class MainViewModel : INotifyPropertyChanged {
 2    public CustomConfiguration CustomConfig { get; } = new CustomConfiguration();
 3    
 4    public RelayCommand OpenOptionsCommand { get; }
 5    
 6    public MainViewModel() {
 7        OpenOptionsCommand = new RelayCommand(OpenOptionsCommandExecute);
 8    }
 9    
10    private void OpenOptionsCommandExecute() {
11        bool Shared = false;
12        if (Shared) {
13            // This ViewModel will be shared 
14            var optionViewModel = new OptionViewModel(CustomConfig);
15            var optionWindow = new OptionWindow() { DataContext = optionViewModel };
16            optionWindow.ShowDialog();
17        } else {
18            // This ViewModel will be cloned 
19            var optionWindow = new OptionWindow(CustomConfig.Clone());
20            optionWindow.ApplyChanges += (s, config) => {
21                this.CustomConfig = config;
22            };
23            optionWindow.ShowDialog();
24        }
25    }
26    
27    public event PropertyChangedEventHandler PropertyChanged;
28    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
29        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
30    }
31}

OptionViewModel.cs

 1public class OptionViewModel : INotifyPropertyChanged {
 2    private CustomConfiguration _customConfig;
 3    
 4    public CustomConfiguration CustomConfig {
 5        get => _customConfig;
 6        set {
 7            _customConfig = value;
 8            OnPropertyChanged();
 9        }
10    }
11    
12    public RelayCommand OKCommand { get; }
13    
14    public OptionViewModel(CustomConfiguration customConfig) {
15        CustomConfig = customConfig;
16        OKCommand = new RelayCommand(OKCommandExecute);
17    }
18    
19    private void OKCommandExecute() {
20        CustomConfig.Save();
21        // Close the option window
22        (Application.Current.MainWindow as Window)?.Close();
23    }
24    
25    public event PropertyChangedEventHandler PropertyChanged;
26    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
27        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
28    }
29}

Test

Case 1: 共用一個 instance

  1. Run the program.
  2. Click OpenOptions button.
  3. Click Enable Logging check box.
  4. You shuold see the flag in MainWindow is switched.

Case 2: 共用一個 instance

  1. Set Shared to false in MainViewModel.cs.
  2. Run the program.
  3. Click OpenOptions button.
  4. Click Enable Logging check box.
  5. You shuold see the flag in MainWindow is NOT changed.

Posts in this Series