【问题标题】:MVVM and View/ViewModel hierarchyMVVM 和 View/ViewModel 层次结构
【发布时间】:2012-09-01 04:19:37
【问题描述】:

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

背景

我正在制作的游戏是数独。数独有一个包含 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)的价值,但我不明白该视图模型如何获取模型。

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

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

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

呼!

感谢您提供的任何建议,即使它不是完整的答案。我也很想找到任何与我自己有类似挑战的 MVVM 项目示例。非常感谢!

【问题讨论】:

  • 我认为您在谈论嵌套用户控件问题(请参阅catel.catenalogic.com/…)。 Catel 本身还不能用于 WinRT(虽然有测试版),但你至少可以理解我认为应该如何做。

标签: c# windows xaml mvvm viewmodel


【解决方案1】:

我将首先创建一个类来开始应用程序。通常我把这个类称为ApplicationViewModelShellViewModel,尽管从技术上讲它可以遵守与我通常用于ViewModel 不同的规则

这个类在启动时被实例化,并且是ShellViewApplicationViewDataContext

// 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 时记住这一点很重要。您的视图只是一个用户友好的界面,允许用户与视图模型交互。它们实际上不被视为应用程序代码的一部分。

例如,您的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 呈现到UI 中。要指定如何绘制BoardViewModel,您可以在ContentControl.ContentTemplate 中指定DataTemplate,或使用隐式DataTemplates

隐式 DataTemplate 只是一个 DataTemplate 用于没有与之关联的 x:Key 的类。 WPF 将在 UI 中遇到指定类的对象时使用此模板。

所以使用

<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 是您的应用程序,因此它们应该负责获取数据。不要在View 中这样做。就我个人而言,我喜欢将 Model 层保留为仅包含数据的普通对象,因此只能从我的 ViewModel 执行数据访问操作。

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

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

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

无论如何,我希望这能回答您的一些问题,并为您指明正确的方向。祝你的项目好运:)

【讨论】:

  • 哇,要消化的东西太多了!让我总结一下,看看我是否掌握了它:父视图将包含 ContentTemplates,其内容属性设置为查看模型对象。 DataTemplates 定义那些 ContentTemplates 以包含相应的视图。这意味着您没有使用 ViewModelLocator 或 IoC,对吗?
  • 另外,我仍然关心数据访问。如果视图模型负责获取自己的模型,有没有办法可以指定有关使用哪个模型的附加信息?如果我有困难的谜题和简单的谜题,我如何告诉我的新 GameViewModel 使用哪个谜题?
  • @BrentTraut 我个人不喜欢使用 ViewModelLocator。它有一些限制,例如您只能拥有一个 ViewModel 实例,并且无法为其传递参数。我更喜欢用代码构建我的应用程序,并让 UI 为我的类提供一个用户友好的界面。
  • @BrentTraut 至于您关于数据访问的问题,我会将这种逻辑放入 ViewModel 中。要么用户选择一个要加载的谜题(在这种情况下,你的 ViewModel 中有一个 ObservableCollection&lt;GameBoard&gt; AvailablePuzzlesGameBoard SelectedPuzzle),或者你根据 ViewModel 中的一些其他逻辑加载一个谜题
  • @Rachel 您选择通过指定 ViewModel 并让 DataTemplates 用相应的 View 对象替换它们来在 XAML 中构建您的视觉层次结构(而不​​是直接指定 View 并使用 ViewModelLocator 或其他绑定 ViewModel 的方法)。当同一个窗口中存在多个视图时,这种方法似乎使微调视觉布局变得更加困难。例如,如果您在一个窗口中有两个视图绑定到同一类的 ViewModel,那么使用您的方法很难在不影响另一个的情况下改变一个视图的大小。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-14
  • 1970-01-01
  • 2011-01-01
  • 2015-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多