c# - 如何处理主模型(MVVM)中子模型的变化
问题描述
从模型更新动态创建的复选框状态的最佳实践是什么?复选框的实际值保存在主模型的子模型中,并根据其逻辑进行更改。复选框的属性绑定到它们各自的 FooViewModel。但是如何改变 FooViewModel 的属性呢?
1方式:主模型触发特殊事件->主VM处理它并使用事件参数找到要更新的目标FooViewModel->主VM使用事件参数中指定的值设置目标FooViewModel属性->通过绑定到FooViewModel更新复选框
2 方式:主模型包含实现 INPC 的 FooModel 的可观察集合,并且每个都被 FooViewModel 包装(使用主 VM 中的 CollectionChanged 事件)。主模型设置一些 FooModel 的属性 -> FooViewModel 处理 PropertyChanged 并将其传输到进一步触发自己的 PropertyChanged 事件 -> 复选框通过绑定到 FooViewModel 更新。FooViewModel 中的转移代码:
this._model.PropertyChanged += (s, a) => this.RaisePropertyChangedEvent(a.PropertyName);
接下来是我的第二种方式的实现:
// MainModel class that holds collection of extra models (CfgActionModel):
class MainModel: BindableBase
{
ObservableCollection<CfgActionModel> _actionsColl
= new ObservableCollection<CfgActionModel>();
public ObservableCollection<CfgActionModel> ActionCollection
{
get => this._actionsColl;
}
public void AddAction(ConfigEntry cfgEntry, bool isMeta)
{
CfgActionModel actionModel = new CfgActionModel()
{
CfgEntry = cfgEntry,
Content = cfgEntry.ToString(),
IsEnabled = true,
IsChecked = false
};
this._actionsColl.Add(actionModel);
}
}
// Extra model that is wrapped with CfgActionViewModel:
class CfgActionModel: BindableBase
{
ConfigEntry _cfgEntry; // Custom enumeration value unique for each checkbox
string _content;
bool _isEnabled = false;
bool _isChecked = false;
public ConfigEntry CfgEntry
{
get => this._cfgEntry;
set
{
if (this._cfgEntry == value) return;
this._cfgEntry = value;
this.RaisePropertyChangedEvent(nameof(CfgEntry));
}
}
public string Content
{
get => this._content;
set
{
if (this._content == value) return;
this._content = value;
this.RaisePropertyChangedEvent(nameof(Content));
}
}
public bool IsEnabled
{
get => this._isEnabled;
set
{
if (this._isEnabled == value) return;
this._isEnabled = value;
this.RaisePropertyChangedEvent(nameof(IsEnabled));
}
}
public bool IsChecked
{
get => this._isChecked;
set
{
if (this._isChecked == value) return;
this._isChecked = value;
this.RaisePropertyChangedEvent(nameof(IsChecked));
}
}
}
// CfgActionViewModel that is checkbox in UI is bound to:
class CfgActionViewModel: BindableBase
{
CfgActionModel _model;
public CfgActionViewModel(CfgActionModel model)
{
this._model = model;
this._model.PropertyChanged += (s, a) => this.RaisePropertyChangedEvent(a.PropertyName);
}
public string Content
{
get => this._model.Content;
set => this._model.Content = value;
}
public bool IsEnabled
{
get => this._model.IsEnabled;
set => this._model.IsEnabled = value;
}
public bool IsChecked
{
get => this._model.IsChecked;
set => this._model.IsChecked = value;
}
}
// MainViewModel where we fill the model with data:
class MainViewModel
{
MainModel model;
readonly ObservableCollection<CfgActionViewModel> _actionVMColl = new ObservableCollection<CfgActionViewModel>();
public ObservableCollection<CfgActionViewModel> ActionVMCollection => this._actionVMColl;
public MainViewModel()
{
this.model = new MainModel();
this.model.ActionCollection.CollectionChanged += (s, a) =>
{
// when new model is created we create new ViewModel wrapping it
if (a.Action == NotifyCollectionChangedAction.Add)
{
CfgActionModel newModel = (CfgActionModel) a.NewItems[0];
CfgActionViewModel actionViewModel = new CfgActionViewModel(newModel);
_actionVMColl.Add(actionViewModel);
}
};
model.AddAction(ConfigEntry.AutoBuy, false);
model.AddAction(ConfigEntry.Bomb, false);
}
}
View 中的 DataTemplate 如下所示:
<DataTemplate DataType="{x:Type mvvm:CfgActionViewModel}">
<CheckBox
IsChecked="{Binding Path=IsChecked, Mode=TwoWay}"
IsEnabled="{Binding Path=IsEnabled, Mode=TwoWay}"
Content="{Binding Path=Content, Mode=OneWay}"/>
</DataTemplate>
MVVM 是否可以接受避免与某处的 MainViewModel 交互(第二种方式)或每个 subViewModel 的属性必须由 MainViewModel 设置(第一种方式)?
解决方案
这两种方法都是可以接受的。但就我个人而言,我会采用方法 #1 以使我的模型尽可能薄。
您可以参考示例代码,了解如何执行方法 #1。
public class MainViewModel : BindableBase
{
public ObservableCollection<SubViewModel> SubViewModels { get; }
public MainViewModel()
{
SubViewModels = new ObservableCollection<SubViewModel>();
SubViewModels.CollectionChanged += SubViewModels_CollectionChanged;
}
private void SubViewModels_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if(e.Action == NotifyCollectionChangedAction.Add)
{
foreach(var subVM in e.NewItems.Cast<SubViewModel>())
{
subVM.PropertyChanged += SubViewModel_PropertyChanged;
}
}
// TODO: Unsubscribe to SubViewModels that are removed in collection to avoid memory leak.
}
private void SubViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(SubViewModel.IsChecked):
// TODO: Do your thing here...
break;
}
}
}
public class SubViewModel : BindableBase
{
private bool _isChecked;
public bool IsChecked
{
get => _isChecked;
set => SetProperty(ref _isChecked, value);
}
}
如您所见,我什至不需要在示例代码中包含任何模型,这意味着这里的所有逻辑显然都是表示层的一部分。现在,您可以专注于模型中的业务/领域逻辑。
推荐阅读
- android - Android Preferences DataStore 与现有 Room 实现
- apache - 如何让 Apache 将 Web 应用程序作为根文档提供服务?
- javascript - ReactJS:按钮(跨度)视觉应该在单击时重新渲染和更改,但不会(使用 setState)
- apache-spark - PySpark-在独立集群模式下运行时访问 udf 中的广播变量时出错
- .net - 以 .Net 框架为目标时消除程序集绑定重定向
- python - 不能使用从 Python 多处理返回的值
- python - Scipy最小二乘警告:“无法估计参数的协方差”
- r - GitHub 安装在包“MazamaScience/RAWSmet”上
- c++ - 拥有构造函数会导致错误“错误:没有运算符“=”匹配这些操作数”
- r - 如何过滤多个可能的值?