C# – How to use multiple ViewModels in WPF and bind them via one MainViewModel

cdata-bindingwpfxaml

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

My MainWindow.xaml looks like following:

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

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

It looks like, the DataContext is not able to bind to the ViewModels via MainViewModel.

Best Answer

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

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.

just add IsAsync=True to the Command binding:.

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


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

Related Topic