MVVM和View/ViewModel层次结构

Bre*_*aut 12 c# windows xaml mvvm viewmodel

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

背景

我正在制作的游戏是数独游戏.Sudoku有一块包含9x9网格的棋盘.我有三个型号- Game,BoardTile.当Game被创建时,它自动创建Board,并且当Board被创建时,它创建81(9×9) Tiles.

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

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

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

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

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

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

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

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

唷!

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

Rac*_*hel 15

我将首先创建一个类来开始应用程序.通常我称这个类是ApplicationViewModel或类似的东西ShellViewModel,尽管从技术上来说它可以遵守不同于我通常使用的规则ViewModel

该类被启动时实例化,是DataContextShellViewApplicationView

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

这通常是我DataContext直接为UI组件设置的唯一位置.从现在开始,您的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>
Run Code Online (Sandbox Code Playgroud)

这实际上会将您的BoardViewModel对象呈现为UI ContentControl.Content.要指定如何绘制BoardViewModel,可以指定DataTemplatein ContentControl.ContentTemplate或使用隐式DataTemplates.

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

所以使用

<Window.Resources>
    <DataTemplate DataType="{x:Type local:BoardViewModel}">
        <local:BoardView />
    </DataTemplate>
</Window.Resources>
Run Code Online (Sandbox Code Playgroud)

将意味着而不是画画

<ContentControl>
    BoardViewModel
</ContentControl>
Run Code Online (Sandbox Code Playgroud)

它会画画

<ContentControl>
    <local:BoardView />
</ContentControl>
Run Code Online (Sandbox Code Playgroud)

现在BoardView可能包含类似的内容

<ItemsControl ItemsSource="{Binding Squares}">
    <ItemsControl.ItemTemplate>
        <ItemsPanelTemplate>
            <UniformGrid Rows="3" Columns="3" />
        </ItemsPanelTemplate>
    <ItemsControl.ItemTemplate>
</ItemsControl>
Run Code Online (Sandbox Code Playgroud)

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

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

但是,您获取数据时请记住,ViewModel并且Models是您的应用程序,因此他们应该负责获取数据.不要那样做View.就个人而言,我喜欢将我的Model图层保留为仅包含数据的普通对象,因此只能从我的ViewModel执行数据访问操作.

对于之间的沟通ViewModels,我实际上在我的博客上有一篇关于它的文章.总而言之,使用消息系统,如Microsoft Prism EventAggregator或MVVM Light Messenger.它们的工作方式类似于一种分页系统:任何类都可以订阅接收特定类型的消息,任何类都可以广播消息.

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

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

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

  • @BrentTraut我个人不喜欢使用ViewModelLocator.它有一些限制,例如你只能有一个ViewModel实例,并且无法传递它的参数.我更喜欢在代码中构建我的应用程序,并让UI只为我的类提供用户友好的界面. (2认同)