C# MVVM:双向模型元素的视图模型
假设我有以下模型结构:C# MVVM:双向模型元素的视图模型,c#,wpf,mvvm,C#,Wpf,Mvvm,假设我有以下模型结构: class Team { public string Name {get;set; } public List<Player> players {get;set;} } class Player { public int Age {get;set;} public string Name {get;set;} public Team Team {get;set;} } 班级团队{ 公共字符串名称{get;set;}
class Team {
public string Name {get;set; }
public List<Player> players {get;set;}
}
class Player {
public int Age {get;set;}
public string Name {get;set;}
public Team Team {get;set;}
}
班级团队{
公共字符串名称{get;set;}
公共列表玩家{get;set;}
}
职业选手{
公共整数{get;set;}
公共字符串名称{get;set;}
公共团队{get;set;}
}
我希望为此模型创建Viewmodels。然而,我也希望避免从TeamVM中的Player复制所有属性,反之亦然(对于这个简单的示例,这是可行的,但实际上相当麻烦)
查看文献和在线文章,似乎“纯粹”的方法是为每个模型创建一个ViewModel,让ViewModel只返回其他ViewModel,而不返回任何模型。这一切都很好,但我的问题是:如何创建这些视图模型而不陷入递归陷阱。假设我这样做:
public class TeamVM: ViewModel<Team> {
private ObservableCollection<PlayerVM> _players;
public TeamVM(Team t): base(t) {
_players = new ObservableCollection();
foreach (Player p in t.players) {
_players.Add(new PlayerVM(t));
}
}
public string Name {
get { return _modelElement.Name; }
set { _modelElement.Name = value; NotifyPropertyChanged(); }
}
public ObservableCollection<PlayerVM> Players {
get { return _players; }
}
}
公共类TeamVM:ViewModel{
私人可观测集合参与者;
公共团队虚拟机(团队t):基本团队(t){
_players=新的observeCollection();
foreach(t.players中的玩家p){
_添加(新PlayerVM(t));
}
}
公共字符串名{
获取{return\u modelement.Name;}
设置{u modelement.Name=value;NotifyPropertyChanged();}
}
公众可观察的集合玩家{
获取{return\u players;}
}
}
及
公共类PlayerVM:ViewModel{
私人团队虚拟机(TeamVM);;
公共玩家(玩家p):基本玩家(p){
_teamVm=新teamVm(p.Team);
}
公共信息{
获取{return\u modelement.Age;}
设置{u modelement.Age=value;NotifyPropertyChanged();}
}
公共字符串名{
获取{return\u modelement.Name;}
设置{u modelement.Name=value;NotifyPropertyChanged();}
}
公共团队虚拟机团队{
获取{return\u teamVM;}
设置{u teamVm=value;NotifyPropertyChanged();}
}
}
显然,上述方法永远不可能奏效,因为它会创建递归:创建TeamVM会导致创建PlayerVM,而PlayerVM又会再次生成TeamVM等
现在,我已经解决了这个问题,添加了一个中间类,如下所示:
public class TeamMinimalVM: ViewModel<Team> {
public TeamVM(Team t): base(t) {
}
public string Name {
get { return _modelElement.Name; }
set { _modelElement.Name = value; NotifyPropertyChanged(); }
}
}
public class TeamVM: TeamMinimalVM {
private ObservableCollection<PlayerVM> _players;
public TeamVM(Team t): base(t) {
_players = new ObservableCollection();
foreach (Player p in t.players) {
_players.Add(new PlayerVM(t));
}
}
}
public类teamminimavm:ViewModel{
公共团队虚拟机(团队t):基本团队(t){
}
公共字符串名{
获取{return\u modelement.Name;}
设置{u modelement.Name=value;NotifyPropertyChanged();}
}
}
公共类TeamVM:TeamMinimalVM{
私人可观测集合参与者;
公共团队虚拟机(团队t):基本团队(t){
_players=新的observeCollection();
foreach(t.players中的玩家p){
_添加(新PlayerVM(t));
}
}
}
然后让PlayerVM依赖于TeamMinimalVM而不是TeamVM。这意味着在视图中,您可以执行:{Binding Player.Team.Name}但不能执行{Binding Player.Team.Players.Name},我想这对我来说还行,因为我认为这样做不是一个好主意
我现在的问题是:是否有更好/更“标准”的方法来实现双向模型元素的“纯”VM?我不想在另一种类型中克隆一种类型的属性(属性太多),也不想直接公开模型元素
最后,我使用的ViewModel类就是这个(只是为了完整性,但对于我认为的问题来说,它不是必需的)
公共类ModelElementViewModel:ObserveObject其中T:class
{
私有布尔(u model element)变更;;
私有T_模型元素;
公共模型元素视图模型(T元素)
{
_modelement=元素;
}
///
///此viewmodel.Protected as的基础模型元素不应直接绑定到gui中的模型元素。
///
内模元素{
获取{return}modelement;}
设置{
if(_modelement!=值)
{
_modelement=值;
ModelElementChanged=false;
NotifyAllPropertiesChanged();
}
; }
}
///
///属性,该属性可用于查看是否通过此viewmodel更改了基础modelelement(请注意,外部
///未跟踪对模型图元的更改!)
///
公共布尔模型元素已更改{
专用设备
{
如果(_modelementchanged!=值)
{
_modelElementChanged=值;
NotifyPropertyChanged();
}
}
得到
{
return\u modelementchanged;
}
}
受保护的覆盖无效NotifyPropertyChanged([CallerMemberName]字符串propertyName=”“)
{
ModelElementChanged=true;
base.NotifyPropertyChanged(propertyName);
}
}
编辑:
从我最初的问题中不清楚的是,球员并不是只由球队使用的。我希望以下三种方案能够起作用:
你的职业有一个清晰的层次结构:团队聚集玩家。球队是主人,球员是主人。因此,在创建播放器VM时,可以将team VM作为构造函数参数传递 这样做的明显限制是,现在你不能没有球队的球员。可能的解决方案是:强制球员始终由某个团队拥有;支持null作为团队VM,并在以后设置适当的值;创建一个“空团队”对象并将其用于无团队的玩家 在这样的情况下,当有一个清晰的聚合层次结构时,我使用我的。有了它,我可以在一个团队中创建一个集合
\u players=newownedObservableCollection(this)
public class TeamMinimalVM: ViewModel<Team> {
public TeamVM(Team t): base(t) {
}
public string Name {
get { return _modelElement.Name; }
set { _modelElement.Name = value; NotifyPropertyChanged(); }
}
}
public class TeamVM: TeamMinimalVM {
private ObservableCollection<PlayerVM> _players;
public TeamVM(Team t): base(t) {
_players = new ObservableCollection();
foreach (Player p in t.players) {
_players.Add(new PlayerVM(t));
}
}
}
public class ModelElementViewModel<T> : ObservableObject where T : class
{
private bool _modelElementChanged;
private T _modelElement;
public ModelElementViewModel(T element)
{
_modelElement = element;
}
/// <summary>
/// The underlying model element for this viewmodel. Protected as one should not bind directly to model elements from the gui.
/// </summary>
internal T ModelElement {
get { return _modelElement; }
set {
if (_modelElement != value)
{
_modelElement = value;
ModelElementChanged = false;
NotifyAllPropertiesChanged();
}
; }
}
/// <summary>
/// Property that can be used to see if the underlying modelelement was changed through this viewmodel (note that an external
/// change to the model element is not tracked!)
/// </summary>
public bool ModelElementChanged {
private set
{
if (_modelElementChanged != value)
{
_modelElementChanged = value;
NotifyPropertyChanged();
}
}
get
{
return _modelElementChanged;
}
}
protected override void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
ModelElementChanged = true;
base.NotifyPropertyChanged(propertyName);
}
}