user3155083
user3155083

Reputation: 31

How to excecute a command declared in child viewmodel in an MVVM app?

I've got a MainWindowVM and multiple child viewmodels inheriting from it. MainWindowVM inherits from ViewModelBase which implements INotifyPropertychanged.

Each view has DataContext set to CurrentViewModel defined in MainWindowVM and every button has got a binding to a command.

If I put the commands (and other command-handling code in the constructor) in the MainWindowVM, button clicks in every view works as expected. I set MainControlVM as CurrentViewModel in the constructor of MainWindowVM. Except for MainControlVM and MainWindowVM, setting commands in any other VM means they wont execute.

However, I want to have commands only in the VMs they are used.

I found many tutorials on MVVM with only one or two viewmodels so this situation isnt an issue for them.

Edit including code: This is the relevant code:

Part of one of the child views in XAML with a binding:

<Grid  DataContext="{Binding CurrentViewModel}" Margin="0,0,-186,0">
    <Button Content="Add" HorizontalAlignment="Left" Margin="25,249,0,0" VerticalAlignment="Top" Width="62" Height="32" 
Command="{Binding AddCategoryVMCommand}" />

MainWindowVM class contains:

   public ICommand AddCategoryVMCommand { get; private set; }

and, in the constructor:

AddCategoryVMCommand = new RelayCommand(() => ExecuteAddCategoryVMCommand());                   

and:

    protected void ExecuteAddCategoryVMCommand()
    {
        CurrentViewModel = new AddCategoryVM();
    }

....and the same kind of code for each command. Aso, CurrentViewModel is set in the MainWindowVM class. This is the property that the MainWindow view uses to determine which view to display along with a datatemplate:

        public ViewModelBase CurrentViewModel
    {
        get { return _currentViewModel; }
        set
        {
            if (_currentViewModel == value)
                           return;
            _currentViewModel = value; 
            this.RaiseNotifyPropertyChanged("CurrentViewModel");
        }
    }

How can I make commands execute when declared in child viewmodel?

Upvotes: 2

Views: 2031

Answers (3)

Mark Travis
Mark Travis

Reputation: 1089

There are a lot of comments going on, all out of sync and they appear to convolute the issue so I thought I would try to solve your problem with a basic example. The example deals solely with the command binding issue you appear to have.

I have created 3 ViewModel's, MyViewModel1 and MyViewModel2 are derived of MyViewModel. There is a command defined in the base ViewModel which is used to load the CurrentViewModel. The other 2 ViewModels contain their own commands.

public class MyViewModel : INotifyPropertyChanged
{
    private MyViewModel currentViewModel;

    public RelayCommand<object> MyCommand { get; set; } 

    public MyViewModel()
    {
        MyCommand = new RelayCommand<object>(MyCommandExecute);
    }

    public MyViewModel CurrentViewModel
    {
        get { return currentViewModel; }
        set
        {
            if (value != currentViewModel)
            {
                currentViewModel = value;
                OnPropertyChanged();
            }
        }
    }

    protected virtual void MyCommandExecute(object obj)
    {
        switch (int.Parse(obj.ToString()))
        {
            case 1:
                CurrentViewModel = new MyViewModel1();
                break;
            case 2:
                CurrentViewModel = new MyViewModel2();
                break;
        }
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
        {
            handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public class MyViewModel1 : MyViewModel
{

    public RelayCommand<object> MyCommand1 { get; set; }

    public MyViewModel1()
    {
        MyCommand1 = new RelayCommand<object>(MyCommand1Execute);
    }

    private void MyCommand1Execute(object obj)
    {
        Debug.WriteLine("MyCommand1");
    }
}

public class MyViewModel2 : MyViewModel
{

    public RelayCommand<object> MyCommand2 { get; set; }

    public MyViewModel2()
    {
        MyCommand2 = new RelayCommand<object>(MyCommand2Execute);
    }

    private void MyCommand2Execute(object obj)
    {
        Debug.WriteLine("MyCommand2");
    }
}

The code behind the UserControl1 is

public partial class UserControl1 : UserControl
{
    public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MyViewModel1), typeof(UserControl1));

    public UserControl1()
    {
        InitializeComponent();
    }

    public MyViewModel1 ViewModel
    {
        get { return GetValue(ViewModelProperty) as MyViewModel1; }
        set { SetValue(ViewModelProperty, value); }
    }
}

I have created the ViewModel Property as a DependencyProperty so I can bind to it from the MainWindow.

The Xaml of the user control is

<UserControl x:Class="StackOverflow._20937791.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:this="clr-namespace:StackOverflow._20937791"
             mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:UserControl1}}, Path=ViewModel}">
        <Button Content="View 1 Command" Command="{Binding Path=MyCommand1}" />
    </StackPanel>
</UserControl>

Note I have set up the DataContext on the first content element of the control. The bindings on all child elements are against the ViewModel of the UserControl while any incoming bindings (from the parent control) will be evaluated from the DataContext of that parent control.

Another point to note is that by defining the DataContext in the Xaml, you will get autocomplete in the Binding expressions which will cut down on bad expression errors.

The second UserControl is the same but the ViewModel is of type MyViewModel2.

Finally, the code for the MainWindow is

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    public MyViewModel ViewModel { get; set; }
}

The Xaml is

<Window x:Class="StackOverflow._20937791.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:this="clr-namespace:StackOverflow._20937791"
        DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
        Title="MainWindow" Height="200" Width="300">
    <Window.Resources>
        <DataTemplate DataType="{x:Type this:MyViewModel1}">
            <this:UserControl1 ViewModel="{Binding}" />
        </DataTemplate>
        <DataTemplate DataType="{x:Type this:MyViewModel2}">
            <this:UserControl2 ViewModel="{Binding}" />
        </DataTemplate>
    </Window.Resources>
    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <Button Content="Show View 1" Command="{Binding Path=MyCommand}" CommandParameter="1" Width="100" Margin="4" />
            <Button Content="Show View 2" Command="{Binding Path=MyCommand}" CommandParameter="2" Width="100" Margin="0 4" />
        </StackPanel>
        <ContentControl Content="{Binding Path=CurrentViewModel}" Margin="20" />
    </StackPanel>
</Window>

The UserControl is referenced in the main window and it has its ViewModel passed in.

The application shows a window that looks like

enter image description here

I hope this helps.

Upvotes: 4

yo chauhan
yo chauhan

Reputation: 12295

It would be helpful if you would post your code .But if I havent misunderstood your question then you can try this

<Button Command="{Binding MainControlVM.ClickCommand}"

Set the binding MainControlVM.ClickCommand .Here ClickCommand is the name of your Command.

Update

I think the issue is in Setting the CurrentViewModel. You are setting the CurrentViewModel in the Action Of Command. I think you want to set the CurrentViewModel on the basis of Command. I think this could be better by CommandParameter . Like Bind all Buttons to same Base ViewModel Command and from each Command pass the different CommandParameter and then on Command compare that CommandParameter and set CurrentViewModel accordingly.

ViewModelBase ,Child1ViewModel ,Child2ViewModel

public class ViewModelBase:INotifyPropertyChanged
{
    private ICommand _clickCommand;

    public ICommand ClickCommand
    {
        get
        {
            return _clickCommand ?? (_clickCommand = new CommandHandler(MyAction,()=>true));
        }
    }

    public void MyAction(object obj)
    {
        if(obj == null )
            return;
        //if CommandParameter is Cild1VM
        if (obj.ToString() == "Child1VM")
            CurrentViewModel = new Child1ViewModel();
        //if CommandParameter is Cild1VM
        else if (obj.ToString() == "Child2VM")
            CurrentViewModel = new Child2ViewModel();
    }

    ViewModelBase _currentViewModel;

    public ViewModelBase CurrentViewModel
    {
        get { return _currentViewModel; }
        set
        {
            if (_currentViewModel == value)
                return;
            _currentViewModel = value;
            this.RaiseNotifyPropertyChanged("CurrentViewModel");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    void RaiseNotifyPropertyChanged(string propName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }
}

public class Child1ViewModel : ViewModelBase
{ }

public class Child2ViewModel : ViewModelBase
{ } 

xaml

  <StackPanel>
    <Button Content="Foo" Command="{Binding ClickCommand}" CommandParameter="Child1VM"/>
    <Button Content="Bar" Command="{Binding ClickCommand}" CommandParameter="Child2VM"/>
</StackPanel>

xaml.cs

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModelBase();
    }
}

I hope this will give you an idea.

Upvotes: 0

MichaelLo
MichaelLo

Reputation: 1319

Firt, FYI - your approach is called the strategy pattern. Now what you are doing sounds right but it's hard withou seeing your xaml. Maybe you need to raise a propertychanged event after setting your vm properties?

Upvotes: 0

Related Questions