【发布时间】:2016-07-30 23:17:18
【问题描述】:
假设我有以下模型结构:
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;}
}
我希望为此模型创建视图模型。但是,我也想避免在 TeamVM 中复制 Player 的所有属性,反之亦然(对于这个简单的示例,这是可行的,但实际上相当麻烦)。
查看文献和在线文章,似乎“纯粹”的方式是为每个模型创建一个 ViewModel,并且让一个 ViewModel 只返回其他 ViewModel 而从不返回 Model。这一切都很好,但我的问题是:如何在不陷入递归陷阱的情况下创建这些视图模型。假设我这样做:
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,而 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} 但不能执行 {Binding Player.Team.Players.Name},我想这对我来说还可以,因为我不认为无论如何,这样做是个好主意。
我现在的问题是:是否有更好/更“标准”的方式来执行双向模型元素的“纯”虚拟机?我不想将一种类型的属性克隆到另一种类型中(太多了),也不想直接公开 Model 元素。
最后,我使用的 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);
}
}
编辑:
我最初的问题并不清楚的是,球员并不是由球队专门使用的。我希望以下三种情况起作用:
- 我希望能够为单个玩家创建一个显示所有玩家信息的视图
- 我希望能够为球队创建一个视图,显示该球队的信息以及所有球员及其统计数据的表格
- 例如,我还希望能够拥有一个 Playersbook 视图,该视图由一个表格组成,其中显示所有已知球员及其团队名称。
【问题讨论】:
-
“看文献和在线文章,似乎“纯粹”的方式是为每个模型创建一个 ViewModel” - 这是不正确的。您为每个视图创建 ViewModel,而不是为每个模型。例如,VM 可能负责聚合许多模型。 VM 的工作是向视图提供信息
-
@MickyD:我同意您评论的基本思想,但仍然存在如何表示“玩家”数据的问题。可能有代表团队的视图,也有代表单个玩家的视图(例如用于
ItemsControl或其他东西的模板)。您认为播放器视图及其视图模型是什么样的? -
恐怕我不太明白这个问题。我的意思是,到目前为止提出的答案暗示了我认为显而易见的解决方案——只需将
TeamVM引用传递给PlayerVM构造函数,而不是让PlayerVM对象创建一个新对象(可选地比较TeamVM.TeamPlayer.Team值的属性,以确保关系有效)——但由于它看起来很明显,我不清楚你为什么不这样做。还有一个问题是为什么PlayerVM对象仍然需要TeamVM对象。那有什么意义呢? -
@MickyD 我同意你的说法。我创建虚拟机的起点是我需要的视图。然而,在那个特定的句子中,我的意思是在构建 VM 时,“纯粹主义者”似乎说你永远不应该返回模型元素,因此,应该为每个使用的域对象创建一个 VM 包装器。
-
@PeterDuniho 我之所以不那样做,是因为在实际项目中,并没有这么明确的“所有权”。为了扩展这里的类比,假设有其他模型元素(具有相应的视图模型)也引用了播放器(尽管播放器没有引用返回),例如列出所有现有播放器的“PlayersBook”。创建 PlayersBookVM 时,您希望创建 PlayerVM,但您无权访问 TeamVM。