2

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?

Vladimir Almaev
  • 2,358
  • 1
  • 28
  • 38
Schrödinger's Box
  • 3,218
  • 1
  • 30
  • 51
  • 2
    Is there a typo in AddEventToUserControl()? Did you mean _MyUserControlViewModel instead of _MainContentViewModel? – TomBot Dec 12 '13 at 18:52
  • Yes, it was a typo, thank you for noticing. – Schrödinger's Box Dec 12 '13 at 22:30
  • If `LoadingEvent` is null, it means no one has successfully subscribed to the event. There's not really enough info provided in the question, to tell why. For example, I can't tell whether the instance of `MyUserControlViewModel` that you create in `AddEventToUserControl` actually gets assigned to the control's `DataContext`. – McGarnagle Dec 12 '13 at 22:38
  • Side note: you should perhaps look into [Caliburn Micro](http://caliburnmicro.codeplex.com/), which is IMO a godsend. – Dom Dec 13 '13 at 06:57
  • @McGarnagle, indeed the instance of MyUserControlViewModel does not get assigned to the DataContext. If I try to change a property with that object nothing happens, so I'm guessing it's not pointing to the right place. – Schrödinger's Box Dec 13 '13 at 09:42

1 Answers1

2

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; }
}
Vladimir Almaev
  • 2,358
  • 1
  • 28
  • 38
  • How is the `Set(() => UserControlViewModel, ref _userControlViewModel, value);` supposed to work? It returns an error saying that `Set` does not exist in the current context. Am I missing something? – Schrödinger's Box Dec 13 '13 at 09:52
  • @josemartins.grupoma Have you added reference to MvvmLightLibs NuGet package to project? Set(...) is a method from ViewModelBase class which is a part of MvvmLight library. – Vladimir Almaev Dec 13 '13 at 10:10
  • That was missing, my bad. How can I trigger `InvokeEventCommand` in `UserControlViewModel` directly in code whithout the `Command` bind to the `UserControlView` button? – Schrödinger's Box Dec 13 '13 at 10:37
  • It's not clear in your question who responsible for triggering event. It can be user (as in my example), it can be some third-party code, or it can be UserControlViewModel itself. – Vladimir Almaev Dec 13 '13 at 10:51
  • In my case I want to trigger it myself in code, so how would you do it? – Schrödinger's Box Dec 13 '13 at 11:03
  • 2
    In which exactly code: UserControlViewModel, WindowViewModel, AnotherViewModel? In any case this code is triggering by something or somebody:) Execution flow of program isn't clear for me. What are you trying to achieve? Maybe you can provide high-level description of your problem? If you need to pass values between viewmodels, you can use Mediator approach as alternative to events (IMessenger in MvvmLight, IEventAggregator in Caliburn.Micro). – Vladimir Almaev Dec 13 '13 at 11:16
  • I udpated my original post with more info, maybe it will help you understand my problem and what I need to solve it. – Schrödinger's Box Dec 13 '13 at 12:02
  • Where you specify DataContext for FatherView? – Vladimir Almaev Dec 13 '13 at 12:15
  • When I create the FatherView, it was missing in the info, I added it now. – Schrödinger's Box Dec 13 '13 at 12:18
  • Where you specify GrandFatherView's DataContext? Is there a property of type FatherViewModel in GrandFatherViewModel? Maybe you create instance of FatherViewModel in code-behind but bind to FatherViewModel property in GrandFatherViewModel? – Vladimir Almaev Dec 13 '13 at 12:45
  • I added to my answer taste of family relationships :) – Vladimir Almaev Dec 13 '13 at 12:52
  • Tank you Vladimir, it works. However, why do I have to declare the `SonViewModel` inside the `FatherViewModel` when I set the `FatherView.DataContext` in the `GrandfatherView.cs`? I noticed that if I remove it, it stops working. – Schrödinger's Box Dec 13 '13 at 14:36
  • It stops working because SonView binds to FatherViewModel's SonViewModel property. If SonViewModel property is null, then SonView doesn't have any DataContext. – Vladimir Almaev Dec 13 '13 at 15:05
  • Also I recommends: 1. using mediators instead of events; 2. using DataTemplates instead of directly instantiation views in xaml (ViewModel-First vs View-First). – Vladimir Almaev Dec 13 '13 at 15:09
  • Do you have any good suggestion on how to implement Mediators given my situation? And I will definitely bear in mind DataTemplates for the next project. – Schrödinger's Box Dec 13 '13 at 15:24
  • Fortunately, you don't need to implement mediator by yourself. You can use any realisation from any MVVM framework (certainly, any modern framework have it). For instance, you can see how to use and test IMessenger (MvvmLight) and IEventAggregator (Prism) in this [answer](http://stackoverflow.com/questions/20487243/prism-vs-mvvm-light-for-wpf/20571091#20571091). – Vladimir Almaev Dec 13 '13 at 16:27