如何在 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
" 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您的代码中有拼写错误: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
"/>
<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 MVVM 如何在ViewModel中操作View中的控件事件