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);
}
}
编辑:

从我最初的问题中不清楚的是,球员并不是只由球队使用的。我希望以下三种方案能够起作用:

  • 我希望能够为单个播放器创建一个显示所有播放器信息的视图
  • 我希望能够为一个团队创建一个视图,显示该团队的信息和一个包含所有球员及其统计信息的表格
  • 例如,我还希望能够有一个PlayerBook视图,其中包括一个表,显示所有已知的球员及其球队名称

  • 你的职业有一个清晰的层次结构:团队聚集玩家。球队是主人,球员是主人。因此,在创建播放器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);
        }
    
    }