如何在 WPF 中使用多个 ViewModel 并通过一个 MainViewModel 绑定它们?

Posted

技术标签:

【中文标题】如何在 WPF 中使用多个 ViewModel 并通过一个 MainViewModel 绑定它们?【英文标题】:How to use multiple ViewModels in WPF and bind them via one MainViewModel? 【发布时间】:2013-11-23 20:53:50 【问题描述】:

我被困在通过 MainViewModel 将 2 个 ViewModel 绑定到一个 View 上。

我的 MainWindow.xaml 如下所示:

<Window x:Class="Dojo4.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ViewModels="clr-namespace:Dojo4.ViewModels"
    Title="Dojo4" Height="346" Width="706">
<Window.DataContext>
    <ViewModels:MainViewModel/>
</Window.DataContext>
<Grid>
    <Button Content="Register" DataContext="Binding RegisterViewModel" Command="Binding Register" HorizontalAlignment="Left" Margin="19,63,0,0" VerticalAlignment="Top" Width="75"/>
    <Label Content="Registration Name&#xD;&#xA;" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="25"/>
    <Label Content="Field Size" HorizontalAlignment="Left" Margin="161,10,0,0" VerticalAlignment="Top" Height="25"/>
    <Label Content="X" HorizontalAlignment="Left" Margin="161,35,0,0" VerticalAlignment="Top" Height="25"/>
    <Label Content="Y" HorizontalAlignment="Left" Margin="203,35,0,0" VerticalAlignment="Top" Height="25"/>
    <TextBox DataContext="Binding RegisterViewModel" Text="Binding Name" MaxLength="8" HorizontalAlignment="Left" Height="23" Margin="19,35,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
    <TextBox DataContext="Binding RegisterViewModel" HorizontalAlignment="Left" Height="23" Margin="161,62,0,0" TextWrapping="Wrap" Text="Binding X" VerticalAlignment="Top" Width="23"/>
    <TextBox DataContext="Binding RegisterViewModel" HorizontalAlignment="Left" Height="23" Margin="203,62,0,0" TextWrapping="Wrap" Text="Binding Y" VerticalAlignment="Top" Width="23"/>
    <Button Content="Up" DataContext="Binding PlayerControlViewModel" Command="Binding MovePlayer" CommandParameter="up" HorizontalAlignment="Left" Margin="79,118,0,0" VerticalAlignment="Top" Width="75" IsEnabled="False"/>
    <Button Content="Down" DataContext="Binding PlayerControlViewModel" Command="Binding MovePlayer" CommandParameter="down" HorizontalAlignment="Left" Margin="79,226,0,0" VerticalAlignment="Top" Width="75" IsEnabled="False"/>
    <Button Content="Left" DataContext="Binding PlayerControlViewModel" Command="Binding MovePlayer" CommandParameter="left" HorizontalAlignment="Left" Margin="10,173,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.707,0.409" IsEnabled="False"/>
    <Button Content="Right" DataContext="Binding PlayerControlViewModel" Command="Binding MovePlayer" CommandParameter="right" HorizontalAlignment="Left" Margin="145,173,0,0" VerticalAlignment="Top" Width="75" IsEnabled="False"/>
</Grid>

我的 MainViewModel 如下:

namespace Dojo4.ViewModels

    class MainViewModel : BaseViewModel
    
        private RegistrationViewModel _RegistrationViewModel;
        public RegistrationViewModel RegistrationViewModel
        
            get  return _RegistrationViewModel; 
        
        private PlayerControlViewModel _PlayerControlViewModel;

        public PlayerControlViewModel PlayerControlViewModel
        
            get  return _PlayerControlViewModel; 
        

        private GameModel _game;

        public MainViewModel()
        
            _game = new GameModel();
            _PlayerControlViewModel = new PlayerControlViewModel(_game);
            _RegistrationViewModel = new RegistrationViewModel(_game);
        
    

运行程序后,绑定将失败并出现以下错误:

System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“MainViewModel”(HashCode=51295333)上找不到“Register”属性。绑定表达式:路径=注册; DataItem='MainViewModel' (HashCode=51295333);目标元素是 'Button' (Name='');目标属性是“命令”(类型“ICommand”) System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“MainViewModel”(HashCode=51295333)上找不到“RegisterViewModel”属性。绑定表达式:路径=注册视图模型; DataItem='MainViewModel' (HashCode=51295333);目标元素是 'Button' (Name='');目标属性是“DataContext”(类型“对象”) System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“MainViewModel”(HashCode=51295333)上找不到“RegisterViewModel”属性。绑定表达式:路径=注册视图模型; DataItem='MainViewModel' (HashCode=51295333);目标元素是'TextBox'(名称='');目标属性是“DataContext”(类型“对象”) System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“MainViewModel”(HashCode=51295333)上找不到“RegisterViewModel”属性。绑定表达式:路径=注册视图模型; DataItem='MainViewModel' (HashCode=51295333);目标元素是'TextBox'(名称='');目标属性是“DataContext”(类型“对象”) System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“MainViewModel”(HashCode=51295333)上找不到“RegisterViewModel”属性。绑定表达式:路径=注册视图模型; DataItem='MainViewModel' (HashCode=51295333);目标元素是'TextBox'(名称='');目标属性是“DataContext”(类型“对象”) System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“PlayerControlViewModel”(HashCode=65331996)上找不到“MovePlayer”属性。绑定表达式:路径=移动播放器; DataItem='PlayerControlViewModel' (HashCode=65331996);目标元素是 'Button' (Name='');目标属性是“命令”(类型“ICommand”) System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“PlayerControlViewModel”(HashCode=65331996)上找不到“MovePlayer”属性。绑定表达式:路径=移动播放器; DataItem='PlayerControlViewModel' (HashCode=65331996);目标元素是 'Button' (Name='');目标属性是“命令”(类型“ICommand”) System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“PlayerControlViewModel”(HashCode=65331996)上找不到“MovePlayer”属性。绑定表达式:路径=移动播放器; DataItem='PlayerControlViewModel' (HashCode=65331996);目标元素是 'Button' (Name='');目标属性是“命令”(类型“ICommand”) System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“PlayerControlViewModel”(HashCode=65331996)上找不到“MovePlayer”属性。绑定表达式:路径=移动播放器; DataItem='PlayerControlViewModel' (HashCode=65331996);目标元素是 'Button' (Name='');目标属性是“命令”(输入“ICommand”)

看起来,DataContext 无法通过MainViewModel 绑定到ViewModels

【问题讨论】:

这很有趣,如果我在 MainWindow.xaml.cs 中设置 DataContext 而不是直接在 MainWindow.xaml 标记中,我会得到 MainViewModel 的属性未找到错误以及我的 PlayerControlViewModel 的属性未找到错误。 - 我认为设置 DataContext 的两种方法是相等的......:S 最后的错误现在是基于拼写错误。但无论如何,我不明白在 xaml 或 xaml.cs 中看到 DataContext 之间有什么区别...... 您是否尝试过公开您的课程? 【参考方案1】:

您的代码中有拼写错误:RegisterViewModel 与 RegistrationViewModel。但还有其他问题:

这段代码有问题 DataContext="Binding RegisterViewModel" Command="Binding Register" 是,Command 属性上的绑定可能比 DataContext 绑定更早地被评估。

只需将 IsAsync=True 添加到 Command 绑定:。

<Button DataContext="Binding RegisterViewModel"
        Command="Binding Register, IsAsync=True" />

当您绑定 datacontext 并同时绑定同一元素的另一个属性(例如 Command 属性)时,您应该将 IsAsync=True 设置为除 datacontext 之外的所有绑定,以便更早地评估 datacontext。


但是,您应该避免在元素上设置 datacontext,其他属性也被绑定:

<Button Command="Binding RegisterViewModel.Register" />

由于您有多个元素数据绑定到同一个视图模型,您应该按视图模型将它们分组到一个根元素,这样您的代码更具可读性和可维护性:

    <Grid DataContext="Binding RegisterViewModel" >
        <Button Command="Binding Register" />
        <Label Content="Registration Name&#xD;&#xA;"/>
        <Label Content="Field Size" />
        <Label Content="X" />
        <Label Content="Y" />
        <TextBox Text="Binding Name" />
        <TextBox Text="Binding X" />
        <TextBox Text="Binding Y" />
    </Grid>
    <Grid DataContext="Binding PlayerControlViewModel">
        <Button Command="Binding MovePlayer" CommandParameter="up" />
        <Button Command="Binding MovePlayer" CommandParameter="down"/>
        <Button Command="Binding MovePlayer" CommandParameter="left" />
        <Button Command="Binding MovePlayer" CommandParameter="right" />
    </Grid>

请注意,这有助于避免违反最简单形式的“不要重复自己”原则。我不会一直重复数据上下文绑定

【讨论】:

非常感谢您的详细解释!这么多年了还是很有趣!【参考方案2】:

我会尝试的几件事: 首先我会尝试将您的一些 xaml 提取到它自己的视图(用户控件/页面)中。我在这里遇到的问题是您有单独的视图模型,但没有单独的视图。大多数时候,我尝试将主窗口用作容纳其他视图的容器,我尝试遵循单一职责原则,这基本上意味着一个类/方法只做一件事,而且只做一件事。我可以这样做并为每个视图模型提供一个视图。我想我会尝试将您的视图设置如下:

所以创建两个用户控件:(RegisterControl 和 PlayerControl)

        <Window x:Class="Dojo4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:Dojo4.ViewModels"
Title="Dojo4" Height="346" Width="706">
<Window.DataContext>
   <ViewModels:MainViewModel/>
</Window.DataContext>
<Grid>
   <ContentPresenter Content="Binding RegisterView" />
   <ContentPresenter Content="Binding PlayerControlView" />
</Grid>

在您的 MainViewModel 中:

public RegisterControl RegisterView  get; set; 
public PlayerControl PlayerControlView  get; set; 

public MainWindow()

   RegisterView = new RegisterControl();
   PlayerControlView = new PlayerControl();

您可能需要实现 INotifyPropertyChange,以便在更改这些值时通知视图进行更新。为什么要更改这些值?假设您想在不同的时间显示不同的视图,那么您可以创建一个名为 IView 的界面并将您的属性更改为如下所示:

public IView RegisterView  get; set; 
public IView PlayerControlView  get; set; 

如果这些都不起作用或对你没有意义,我唯一能想到的你现在设置它的方式可能是导致你没有通知视图模型上的属性更改 (INotifyPropertyChanged)。尽管我不喜欢按照您的方式设置数据上下文。希望这至少有一点帮助。

【讨论】:

同意为每个视图模型创建单独的视图,但是您正在视图模型中创建视图,这不是一个好习惯。它违反了 MVVM 层的关注点分离。为什么不直接在 MainWindow.xaml 中创建 RegisterControl 而不是 ContentPresenter【参考方案3】:

你可以试试下面的方法

不要为每个控件设置 DataContext,而是在绑定期间自绑定实例中的属性,如下所示

<Button Content="Register" Command="Binding RegisterViewModel.Register" HorizontalAlignment="Left" Margin="19,63,0,0" VerticalAlignment="Top" Width="75"/>

由于您在 UI 中有很多控件,因此首先从 One 控件开始,注释掉所有其他控件,如果一个有效,您将清楚如何为其他控件执行此操作。其他明智的编译器会显示很多难以通过错误的错误

【讨论】:

以上是关于如何在 WPF 中使用多个 ViewModel 并通过一个 MainViewModel 绑定它们?的主要内容,如果未能解决你的问题,请参考以下文章

Caliburn.Micro-如何从继承的ViewModel在WPF视图中显示多个项目:Conductor 。Collection.AllActive

WPF - 使用具有多个 ViewModel 的单个模型

如何在MVVM中使用相同的ViewModel拥有多个视图?

WPF MVVM 如何在ViewModel中操作View中的控件事件

如何将视图与viewmodel关联或ViewModel的多个DataTemplates?

MVVM + WPF - 为具有多个视图的 ViewModel 设计