Bigeyes
Bigeyes

Reputation: 1666

Pass data from code behind and ViewModel to another ViewModel

I have a View which has 2 sub views on it and a ViewModel is assigned to each view:

ViewParent - ViewModelParent
{ 
   ViewA - ViewModelA
   ViewB - ViewModelB
}

The ViewParent structure likes below.

<StackPanel Orientation="Vertical">
    <local:AView DataContext="{Binding ViewModelA, Mode=TwoWay}"></localViews:AView>
</StackPanel>
<StackPanel Orientation="Vertical">
    <localViews:BView DataContext="{Binding ViewModelB, Mode=TwoWay}"></localViews:BView>
</StackPanel>

Now in code behind of ViewA, I have an event to select a row in a gridview.

  private void radGridView_SelectionChanged(object sender, SelectionChangeEventArgs e)
    {
        try
        {
            RadGridView grv = sender as RadGridView;                       

            if (e.AddedItems.Count > 0)
            {
               var row = grv.SelectedItem as SomeClass;
               if(condition)
                  // Enable a button in ViewB.

What I want is to pass row to ViewModelB. Also, I want to enable a button in ViewB. Finally I want to pass a few integers from ViewModelA to ViewModelB.

How to do it? I thought about constructor injection but still without clear idea.

Upvotes: 0

Views: 1853

Answers (1)

There's a lot of typing here, but there's no difficult logic and there's nothing that'll turn into a bug and bite you.

  1. Make sure ParentViewModel has references to ViewModelA and ViewModelB (as I understand it you're already there).

  2. Give ViewModelA a SelectedRowChanged event:

    public event EventHandler SelectedRowChanged;
    
  3. Give ViewModelB an IsMyButtonEnabled property (but name it better than that; you need the name to indicate which button its referring to)

    private bool _isMyButtonEnabled;
    public bool IsMyButtonEnabled {
        get { return _isMyButtonEnabled; }
        set { 
            _isMyButtonEnabled = value;
            OnPropertyChanged(nameof(IsMyButtonEnabled));
        }
    }
    
  4. In ViewB, bind the button's IsEnabled property to that property:

    <Button
        ...
        IsEnabled="{Binding IsMyButtonEnabled}"
        ...
    
  5. Give both ViewModelA and ViewModelB a SelectedRow property.

    private SomeClass _selectedRow;
    public SomeClass SelectedRow {
        get { return _selectedRow; }
        set { 
            _selectedRow = value;
            OnPropertyChanged(nameof(SelectedRow));
            /*
                DO ADDITIONAL STUFF HERE:
    
                VMA: 
                    SelectedRowChanged?.Invoke(this, EventArgs.Empty);
    
                VMB:
                    Set IsMyButtonEnabled to whatever is appropriate based on
                    the selected row. 
             */
        }
    }
    
  6. ParentViewModel sets up a handler for ViewModelA.SelectedRowChanged, which it can do because it has a reference to its instance of ViewModelA. Then the handler tells ViewModelB to do whatever needs to be done when SelectedRow changes. Another way to do this would be to give ViewModelB a reference to ViewModelA, and have it handle ViewModelA.SelectedRowChanged. But as a rule, you want your child viewmodels to be more loosely coupled than that. You don't want B to depend on having a sibling of type A. But the parent already depends on having both of those children. In for a penny, in for a pound. You can't get any more wet.

    ...
    //  Constructor or someplace
    this.VMA = new ViewModelA();
    this.VMA.SelectedRowChanged += ViewModelA_SelectedRowChanged;
    ...
    
    void ViewModelA_SelectedRowChanged(Object sender, EventArgs e)
    {
        //  VMB.SelectedItem's setter will enable the button appropriately
        VMB.SelectedItem = VMA.SelectedItem;
    }
    

You really "should" be doing the button thing with a Command, and enabling the Command instead of having an Is*ButtonEnabled property. That's a more versatile and powerful way to do things. But we can take things one step at a time.

Upvotes: 1

Related Questions