假设我有以下模型结构:
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);
}
}
修改
我最初的问题并不清楚,球员不会完全使用球员。我想要遵循以下三种方案:
答案 0 :(得分:0)
您的课程层次清晰:团队聚合玩家。球队是球队所有者,拥有球员。因此,在创建播放器VM时,您可以将团队VM作为构造函数参数传递。
现在明显的局限是现在你没有没有球队的球员。可能的解决方案是:强制玩家始终由某个团队拥有;支持null作为团队VM并稍后设置适当的值;创建一个“空团队”对象并将其用于无团队的玩家。
在这些情况下,如果有明确的聚合层次结构,我会使用OwnedObservableCollection<T, TOwner>
。有了它,我可以在团队中创建一个集合_players = new OwnedObservableCollection<PlayerVM, TeamVM>(this)
,然后只使用Add
和Remove
在团队中添加和删除玩家。