MVVM:双向模型元素

时间:2016-07-30 23:17:19

标签: 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;}
}

我希望为此模型创建Viewmodels。但是,我也希望避免在TeamVM中复制Player中的所有属性,反之亦然(对于这个简单的例子,这将是可行的,但实际上相当麻烦)。

观看文献和在线文章,似乎“纯粹”的方式是为每个模型创建一个ViewModel,并让ViewModel只返回其他ViewModel而不返回Models。这一切都很好,但我的问题是:如何在不进入递归陷阱的情况下创建这些视图模型。假设我这样做:

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; }
   }
}

public class PlayerVM : ViewModel<Player> {

   private TeamVM _teamVM;

   public PlayerVM(Player p): base(p) {
       _teamVm = new TeamVM(p.Team);
   }

   public int Age {
      get { return _modelElement.Age; }
      set { _modelElement.Age = value; NotifyPropertyChanged(); }
   }
   public string Name {
      get { return _modelElement.Name; }
      set { _modelElement.Name = value; NotifyPropertyChanged(); }
   }
   public TeamVM Team {
      get { return _teamVM; }
      set { _teamVm = value; NotifyPropertyChanged(); }
   }
}

显然,以上内容永远不会起作用,因为它会创建递归:创建TeamVM会导致创建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));
       }
   }
}

然后让PlayerVM依赖于TeamMinimalVM而不是TeamVM。这意味着在视图中,你可以这样做:{Binding Player.Team.Name}但不能绑定Player.Team.Players.Name},这对我来说没问题,因为我不认为无论如何,这是一个好主意。

我现在的问题是:是否有更好/更“标准”的方式来做双向模型元素的“纯”虚拟机?我不想在另一个类型中克隆一种类型的属性(太多),也不想直接公开模型元素。

最后,我使用的ViewModel类就是这个(只是为了完整性,但对于我认为的问题并不重要。)

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);
    }

}

修改

我最初的问题并不清楚,球员不会完全使用球员。我想要遵循以下三种方案:

  1. 我希望能够为显示所有玩家信息的单个玩家创建一个视图
  2. 我希望能够为团队创建一个视图,显示该团队的信息以及所有玩家的统计表及其统计信息
  3. 我还希望能够拥有一个Playersbook视图,其中包含一个显示所有已知玩家及其团队名称的表格。

1 个答案:

答案 0 :(得分:0)

您的课程层次清晰:团队聚合玩家。球队是球队所有者,拥有球员。因此,在创建播放器VM时,您可以将团队VM作为构造函数参数传递。

现在明显的局限是现在你没有没有球队的球员。可能的解决方案是:强制玩家始终由某个团队拥有;支持null作为团队VM并稍后设置适当的值;创建一个“空团队”对象并将其用于无团队的玩家。

在这些情况下,如果有明确的聚合层次结构,我会使用OwnedObservableCollection<T, TOwner>。有了它,我可以在团队中创建一个集合_players = new OwnedObservableCollection<PlayerVM, TeamVM>(this),然后只使用AddRemove在团队中添加和删除玩家。