14

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

背景

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

1. 视图层次结构,对应的视图模型是如何创建的?

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

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

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

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

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

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

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

呸!

我感谢您提供的任何建议,即使它不是一个完整的答案。我也很想找到任何与我有相似挑战的 MVVM 项目示例。万分感谢!

4

1 回答 1

19

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

此类在启动时被实例化,并且是DataContext用于ShellVieworApplicationView

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

这通常是我DataContext直接为 UI 组件设置的唯一位置。从此时起,您的 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. 要指定如何绘制您的BoardViewModel,您可以指定DataTemplateinContentControl.ContentTemplate或使用隐式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获取其实际数据对象,这取决于您。我更喜欢抽象类后面的所有数据访问,例如 a Repository,并让我ViewModel简单地调用类似SodokuRepository.GetSavedGame(gameId);. 它使应用程序易于测试和维护。

无论您如何获取数据,请记住ViewModelModels是您的应用程序,因此它们应该负责获取数据。不要在View. 就我个人而言,我喜欢Model为仅保存数据的普通对象保留我的层,因此只能从我的 ViewModel 执行数据访问操作。

对于 之间的交流ViewModels,我实际上在我的博客上有一篇关于此的文章。总而言之,使用诸如 Microsoft PrismEventAggregator或 MVVM Light 之类的消息传递系统Messenger。它们的工作方式类似于一种寻呼系统:任何类都可以订阅接收特定类型的消息,并且任何类都可以广播消息。

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

我想另一种方法是将处理程序从一个类附加到另一个类,例如CurrentBoardViewModel.ExitCommand += Exit;ShellViewModel.

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

于 2012-09-07T16:54:59.620 回答