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

22,166

Solution 1

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

Solution 2

Few things I would try: First I 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:

So create two user controls: (RegisterControl and 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:

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:

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.

Solution 3

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

<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

Share:
22,166
user2495085
Author by

user2495085

Updated on May 22, 2020

Comments

  • user2495085
    user2495085 almost 4 years

    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.