MVVM和View / ViewModel层次结构

时间:2012-09-07 09:32:36

标签: c# windows xaml mvvm viewmodel

我正在使用C#和XAML为Windows 8制作我的第一个游戏。我仍然在学习核心概念和最佳实践,而MVVM一直是个障碍。我将尝试分两部分提问。

背景

我正在制作的游戏是数独游戏。 Sudoku有一块包含9x9网格的棋盘。我有三个模型 - GameBoardTile。创建Game后,它会自动创建Board,并在创建Board时创建81(9x9)Tiles

1。使用视图层次结构,如何创建相应的视图模型?

为了匹配模型的层次结构,我希望有一个视图层次结构(GameView包含BoardView,其中包含81 TileViews)。在XAML中,使用用户控件创建这种视图层次结构非常容易,但我不明白如何创建视图模型。

在我看到的示例中,用户控件的数据上下文通常设置为视图模型(使用ViewModelLocator作为源),这将创建视图模型的新实例。如果你有一个平面视图,这似乎很有效,但是当你有一个层次结构时,它似乎也会变得混乱。 GameView是否会创建GameViewModel并将其留给BoardView子项创建BoardViewModel?如果是,GameViewModel如何与BoardViewModel进行通信? BoardViewModel可以将层次结构通信回GameViewModel吗?

2。视图模型如何获取模型数据?

在iOS中,我首先使用服务来获取预先填充了数据的Game模型。然后我会创建一个GameViewController视图控制器(负责创建视图)并将Game传递给它。在MVVM中,我看到让视图负责创建自己的视图模型(理想情况下使用ViewModelLocator)的价值,但我不明白该视图模型如何获得模型。

在我在网上找到的所有示例中,视图模型使用一些服务来获取自己的数据。但我没有遇到任何接受从更高级别导航传递的构造函数params或params的示例。这是怎么做到的?

我不想为我的模型使用应用程序资源或其他类型的单例存储方法,因为,不是我这样做,但如果我想一次在屏幕上显示多个谜题怎么办?每个GameView都应包含自己的Game

GameViewModel不仅需要对Game模型的引用,而且以某种方式创建的BoardViewModel(请参阅问题1)需要引用Board属于Game模型的模型。所有Tiles都是如此。所有这些信息如何传递到链条中?我可以完全在XAML中完成这么多繁重的工作,还是我将不得不在代码中进行某种绑定或其他初始化?

呼<!/ em>的

我感谢您提出的任何建议,即使这不是一个完整的答案。我也很想找到任何与我自己有类似挑战的MVVM项目的例子。万分感谢!

1 个答案:

答案 0 :(得分:15)

我首先要创建一个用于开始应用程序的类。通常我会将该类称为ApplicationViewModelShellViewModel,即使从技术上讲它可以遵守不同于我通常用于ViewModel

的规则

此类在启动时实例化,并且是DataContextShellView

ApplicationView
// App.xaml.cs
private void OnStartup(object sender, StartupEventArgs e)
{
    var shellVM = new ShellViewModel(); 
    var shellView = new ShellView();    
    shellView.DataContext = shellVM;  
    shellView.Show(); 
}

这通常是我直接为UI组件设置DataContext的唯一地方。从现在开始,您的ViewModel是应用程序。在使用MVVM时,记住这一点非常重要。您的视图只是一个用户友好的界面,允许用户与ViewModels交互。它们实际上并不被视为应用程序代码的一部分。

例如,您的ShellViewModel可能包含:

  • BoardViewModel CurrentBoard
  • UserViewModel CurrentUser
  • ICommand NewGameCommand
  • ICommand ExitCommand

并且您的ShellView可能包含以下内容:

<DockPanel>
    <Button Command="{Binding NewGameCommand}" 
            Content="New Game" DockPanel.Dock="Top" />
    <ContentControl Content="{Binding CurrentBoard}" />
</DockPanel>

这实际上会将BoardViewModel对象作为ContentControl.Content呈现给用户界面。要指定如何绘制BoardViewModel,您可以在DataTemplate中指定ContentControl.ContentTemplate,也可以使用隐式DataTemplates

对于没有与DataTemplate关联的类,隐式DataTemplate只是x:Key。只要遇到UI中指定类的对象,WPF就会使用此模板。

所以使用

<Window.Resources>
    <DataTemplate DataType="{x:Type local:BoardViewModel}">
        <local:BoardView />
    </DataTemplate>
</Window.Resources>

将意味着而不是绘制

<ContentControl>
    BoardViewModel
</ContentControl>

它将绘制

<ContentControl>
    <local:BoardView />
</ContentControl>

现在BoardView可能包含类似

的内容
<ItemsControl ItemsSource="{Binding Squares}">
    <ItemsControl.ItemTemplate>
        <ItemsPanelTemplate>
            <UniformGrid Rows="3" Columns="3" />
        </ItemsPanelTemplate>
    <ItemsControl.ItemTemplate>
</ItemsControl>

它将使用3x3 UniformGrid绘制一个板,每个单元格包含Squares数组的内容。如果您的BoardViewModel.Squares属性恰好是TileModel个对象的数组,那么每个网格单元格都会包含TileModel,您可以再次使用隐式DataTemplate告诉WPF如何绘制每个TileModel

现在关于你的ViewModel如何获取其实际数据对象,这取决于你。我更喜欢抽象一个类Repository之类的所有数据访问,并让我的ViewModel简单地调用SodokuRepository.GetSavedGame(gameId);之类的东西。它使应用程序易于测试和维护。

但是,您获得了数据,请记住ViewModelModels是您的应用,因此他们应该负责获取数据。请勿在{{1​​}}中执行此操作。就个人而言,我喜欢将View图层保留为仅包含数据的普通对象,因此只能从我的ViewModel执行数据访问操作。

对于Model之间的沟通,我实际上有一个article on my blog。总而言之,使用消息系统,如Microsoft Prism的ViewModels或MVVM Light EventAggregator。它们的工作方式类似于一种分页系统:任何类都可以订阅接收特定类型的消息,任何类都可以广播消息。

例如,您的Messenger可能会订阅接收ShellViewModel条消息,并在听到应用程序时关闭该应用程序,您可以在应用程序的任何位置广播ExitProgram消息。

我想另一种方法是将处理程序从一个类附加到另一个类,例如从ExitProgram调用CurrentBoardViewModel.ExitCommand += Exit;,但我发现它很麻烦,而且更喜欢使用消息传递系统。

无论如何,我希望能回答你的一些问题,并指出你正确的方向。 Goodluck与您的项目:)