Propagate / Bubble events across several ViewModel in MVVM

I have a MainWindow.xaml with the DataContext set as MainWindowViewModel.cs. This window has nothing in it until the user clicks a button and adds a MyUserControl.xaml to the window. That MyUserControl.xaml has a MyUserControlViewModel.cs set as the DataContext.

I want to trigger an event in the MyUserControlViewModel.cs that gets caught by the MainWindowViewModel.cs.

So far I have this. In my MyUserControlViewModel.cs

public event EventHandler<LoadingEventArgs> LoadingEvent;
(...)

public void MyFunction() {
    LoadingEvent(this, EventLibrary.LoadingEvent(2));
}

And in my MainWindowViewModel.cs

private MyUserControlViewModel _myUserControlViewModel;
public MyUserControlViewModel _MyUserControlViewModel {
    get { return _myUserControlViewModel; }
    private set { _myUserControlViewModel = value; OnPropertyChangedEvent("MyUserControlViewModel"); }
}

public MainWindowViewModel() {
    // Can't attach event here
}

public void AddEventToUserControl() {
    _MyUserControlViewModel = new MyUserControlViewModel();
    _MyUserControlViewModel.LoadingEvent += MyUserControlViewModel_LoadingEvent;
}

private void MyUserControlViewModel_LoadingEvent(object sender, LoadingEventArgs e) {
    MessageBox.Show("I got here!");
}

The message "I got here" never shows because the LoadingEvent in the MyUserControlViewModel.cs is null.
The reason I'm only attaching the MyUserControlViewModel_LoadingEvent outside the MainWindowViewModel() function is because the MyUserControl.xaml is not added when the windows loads, but only after, when the user clicks the button.
I think the problem here is that the event is not added as the same time as the creation of the MyUserControlViewModel.cs.

I need to know how can I trigger bubbling events through ViewModels when they are created dynamically through code.


UPDATE - MORE INFO ADDED
Given that my above situation may be to simple and lacks important details, I'm going to describe my exact situation.

I have three views - a grandfather, a father and a son. For each one I have a ViewModel class. In my father I have the son declared in the XAML because it is created at the same time, so when I have SonViewModel = new SonViewModel() in my FatherViewModel, it points to the right place and I can access everything I need in the SonViewModel. Consequently, any event triggered in SonViewModel that is being listened by FatherViewModel gets caught.
The two most important facts here are that both father and son are created at the same time and the son is created in the XAML like so:

<View:SonView DataContext="{Binding SonViewModel, Mode=OneWay}" />

This works fine does exactly what I want.

The problem comes with the grandfather and the father because they're not created at the same time. I first create the GrandfatherView.xaml and in it's associated GrandfatherView.cs (I know it's not MVVM practice, but I'm trying to implement some MVVM features in a project that's not with MVVM pattern) I create and add the FatherView to a Grid, like so:

FatherView _fatherView = new FatherView();
_fatherView.DataContext = new FatherViewModel();
mainGrid.Children.Add(_fatherView);

Because I'm creating the FatherView after I created the GrandfatherView and adding it through code and not having a specific binding, when I create the FatherViewModel object in my GrandfatherViewModel class, it's not pointing to the actual ViewModel of the FatherView I see in my app.
I'm not sure how to create the binding <View:SonView DataContext="{Binding SonViewModel, Mode=OneWay}" /> in code, so I can make sure the attributes and bindings are the same, so the ViewModel object I have in my GrandfatherViewModel is actually the one I see in my app and not a completely new object.

So, any suggestions?

Upvotes: 2

Views: 2356

Answers (1)

Vladimir  Almaev
Vladimir Almaev

Reputation: 2358

Complete solution to problem (with use of Mvvm Light):

GrandFatherView.xaml

<Window x:Class="FunWithDynamicallyCreatedViewModels.GrandFatherView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid Name="MainGrid" />
</Window>

GrandFatherView.xaml.cs

public sealed partial class GrandFatherView
{
    public GrandFatherView()
    {
        InitializeComponent();

        var fatherView = new FatherView
                         {
                             DataContext = new FatherViewModel
                                           {
                                               SonViewModel = new SonViewModel()
                                           }
                         };
        MainGrid.Children.Add(fatherView);
    }
}

FatherView.xaml

<UserControl x:Class="FunWithDynamicallyCreatedViewModels.FatherView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:FunWithDynamicallyCreatedViewModels">

    <local:SonView DataContext="{Binding SonViewModel}" />

</UserControl>

FatherViewModel.cs

public sealed class FatherViewModel : ViewModelBase
{
    private SonViewModel _sonViewModel;

    public SonViewModel SonViewModel
    {
        get { return _sonViewModel; }
        set
        {
            if (SonViewModel != null)
                SonViewModel.Event -= OnUserControlViewModelEvent;

            Set(() => SonViewModel, ref _sonViewModel, value);

            if (SonViewModel != null)
                SonViewModel.Event += OnUserControlViewModelEvent;
        }
    }

    private void OnUserControlViewModelEvent(object sender, EventArgs args)
    {
        // violation of MVVM for the sake of simplicity
        MessageBox.Show("I got here!");
    }
}

SonView.xaml

<UserControl x:Class="FunWithDynamicallyCreatedViewModels.SonView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Button Command="{Binding InvokeEventCommand}" 
            Content="Invoke SonViewModel Event" />

</UserControl>

SonViewModel.cs

public sealed class SonViewModel
{
    public SonViewModel()
    {
        InvokeEventCommand = new RelayCommand(() => Event(this, new EventArgs()));
    }

    public event EventHandler Event;

    public ICommand InvokeEventCommand { get; private set; }
}

Upvotes: 2

Related Questions