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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20167738/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 10:06:37  来源:igfitidea点击:

How to use multiple ViewModels in WPF and bind them via one MainViewModel?

c#wpfxamldata-binding

提问by user2495085

I got stuck at binding 2 ViewModels via on MainViewModel to one View.

我陷入了通过 MainViewModel 将 2 个 ViewModel 绑定到一个 View 的问题。

My MainWindow.xaml looks like following:

我的 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>

and my MainViewModel like following:

和我的 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);
        }
    }
}

after running the program the binds will fail with the following errors:

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

System.Windows.Data Error: 40 : BindingExpression path error: 'Register' property not found on 'object' ''MainViewModel' (HashCode=51295333)'. BindingExpression:Path=Register; DataItem='MainViewModel' (HashCode=51295333); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand') System.Windows.Data Error: 40 : BindingExpression path error: 'RegisterViewModel' property not found on 'object' ''MainViewModel' (HashCode=51295333)'. BindingExpression:Path=RegisterViewModel; DataItem='MainViewModel' (HashCode=51295333); target element is 'Button' (Name=''); target property is 'DataContext' (type 'Object') System.Windows.Data Error: 40 : BindingExpression path error: 'RegisterViewModel' property not found on 'object' ''MainViewModel' (HashCode=51295333)'. BindingExpression:Path=RegisterViewModel; DataItem='MainViewModel' (HashCode=51295333); target element is 'TextBox' (Name=''); target property is 'DataContext' (type 'Object') System.Windows.Data Error: 40 : BindingExpression path error: 'RegisterViewModel' property not found on 'object' ''MainViewModel' (HashCode=51295333)'. BindingExpression:Path=RegisterViewModel; DataItem='MainViewModel' (HashCode=51295333); target element is 'TextBox' (Name=''); target property is 'DataContext' (type 'Object') System.Windows.Data Error: 40 : BindingExpression path error: 'RegisterViewModel' property not found on 'object' ''MainViewModel' (HashCode=51295333)'. BindingExpression:Path=RegisterViewModel; DataItem='MainViewModel' (HashCode=51295333); target element is 'TextBox' (Name=''); target property is 'DataContext' (type 'Object') System.Windows.Data Error: 40 : BindingExpression path error: 'MovePlayer' property not found on 'object' ''PlayerControlViewModel' (HashCode=65331996)'. BindingExpression:Path=MovePlayer; DataItem='PlayerControlViewModel' (HashCode=65331996); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand') System.Windows.Data Error: 40 : BindingExpression path error: 'MovePlayer' property not found on 'object' ''PlayerControlViewModel' (HashCode=65331996)'. BindingExpression:Path=MovePlayer; DataItem='PlayerControlViewModel' (HashCode=65331996); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand') System.Windows.Data Error: 40 : BindingExpression path error: 'MovePlayer' property not found on 'object' ''PlayerControlViewModel' (HashCode=65331996)'. BindingExpression:Path=MovePlayer; DataItem='PlayerControlViewModel' (HashCode=65331996); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand') System.Windows.Data Error: 40 : BindingExpression path error: 'MovePlayer' property not found on 'object' ''PlayerControlViewModel' (HashCode=65331996)'. BindingExpression:Path=MovePlayer; DataItem='PlayerControlViewModel' (HashCode=65331996); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')

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

It looks like, the DataContextis not able to bind to the ViewModelsvia MainViewModel.

看起来,DataContext无法绑定到ViewModelsvia MainViewModel

回答by Liero

You have typos in your code: RegisterViewModel vs RegistrationViewModel. But there are also other issues:

您的代码中有错别字: RegisterViewModel 与 RegistrationViewModel。但也有其他问题:

problem with this code DataContext="{Binding RegisterViewModel}" Command="{Binding Register}"is, that the binding on the Command property may be evaluated earlier than the DataContext binding.

此代码的问题 DataContext="{Binding RegisterViewModel}" Command="{Binding Register}"在于,Command 属性上的绑定可能比 DataContext 绑定更早地进行评估。

just add IsAsync=Trueto the Command binding:.

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

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

When you bind datacontext and you also bind another property of the same element (e.g Command property), you should set IsAsync=True to all bindings except datacontext, so datacontext will be evaluated earier.

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



However, you should avoid setting datacontext on element, where other properties are bound as well:

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

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

since you have multiple elements databound to the same viewmodel, you should group them to a root element by viewmodel, so your code is more readable and maintanable:

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

    <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>

Notice how this helps to avoid the violation of the simplest form of 'Dont Repeat Yourself' principle. I dont repeat the datacontext binding all the time

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

回答by TMan

Few things I would try: FirstI would try to extract some of your xaml into its own view(usercontrols/pages). The problem I have here is that you have separate view models but not separate views. Most of the time I try to use my Main Window as a container that holds other views, I try to go by the single-responsibility principal which basically means that a class/method does only one thing and one thing only. I can do this and have a view for each view model. I imagine I would try to set your view up as follows:

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

So create two user controls: (RegisterControl and PlayerControl)

所以创建两个用户控件:(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>

In your MainViewModel:

在您的 MainViewModel 中:

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

public MainWindow()
{
   RegisterView = new RegisterControl();
   PlayerControlView = new PlayerControl();
}

You may need to implement INotifyPropertyChange so that you can tell the view to update when you change these values. Why would you want to change these values? Say if you want to show different views at different times, then you can create an interface called IView and change your properties to look like:

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

public IView RegisterView { get; set; }
public IView PlayerControlView { get; set; }

If none of this is working or makes sense to you the only thing I could think of the way you have it setup now is maybe cause your not notifying property change (INotifyPropertyChanged) on your viewmodels. Again though I don't like setting the data context's the way you are though. Hope this helps a little at least.

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

回答by Kumareshan

you can try something like below

你可以尝试像下面这样

Don't set the DataContext to each control, instead during binding it self bind the property from the instance as below

不要将 DataContext 设置到每个控件,而是在绑定过程中它自己绑定来自实例的属性,如下所示

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

Since you have lot of controls in the UI, first start with the One control, comment out all others, if once one works you'll be clear how to do it for others. other wise compiler will show lot of error which is difficult to go through the errors

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