2

I have a UserControl which is just a simple DataGrid to show some stuff, it has his own ViewModel and Model.

I would like to whenever I click on the DataGrid, raise an Event. So far, it's working with commands, and I get the event on my UserControl.ViewModel.

The thing is that I need the event to be raise in the MainWindow.ViewModel, which is who contains the logic to load the stuff needed in the UserControl.DataGrid.

In my little understanding in WPF and MVVM, I have the following options:

  • DependencyProperty of the UserControl.DataGridClicked (which I would like to avoid, and stick as possible to MVVM)
  • Give the MainWindow.ViewModel instance to the constructor of my UserControl.ViewModel, so I can raise the event directly.
  • Create a BusinessLogic Class, and put the logic there, so I can call it on UserControl.ViewModel or MainWindow.ViewModel.
  • ?

This is how I call my UserControl:

<Window>
    <Grid>
        <TabControl>
            <TabItem Header="{Binding Model.TabImportHeader}">
                <views:ResultView DataContext="{Binding ResultViewModel}"/>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

And this is my UserControl:

<UserControl x:Class="ResultView"
             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:viewModels="clr-namespace:ViewModels"
             mc:Ignorable="d"
             d:DataContext="{d:DesignInstance Type=viewModels:ResultViewModel, IsDesignTimeCreatable=False}">

    <!--DataGrid-->
    <DataGrid ItemsSource="{Binding Model.ObservableCollection}"
              ColumnWidth="*"
              AutoGenerateColumns="false"
              IsReadOnly="True"
              BorderThickness="0"
              Background="White"
              HeadersVisibility="Column">

        <!--InputBindings-->
        <DataGrid.InputBindings>
            <MouseBinding Gesture="LeftClick"
                          Command="{Binding LeftClickCommand}">

            </MouseBinding>
        </DataGrid.InputBindings>
        <!--InputBindings-->

        <DataGrid.Columns>
            <DataGridTextColumn Header="Filename"
                                Binding="{Binding FileInfo.Name}"
                                Width="Auto" />
        </DataGrid.Columns>
    </DataGrid>
</UserControl>

Any WPF Master who also knows a little about good practices? I will really appreciate any help.

Thanks in advance.

EDIT: Command declaration:

public ICommand = new RelayCommand(LeftClick, () => true);
Nekeniehl
  • 1,633
  • 18
  • 35
  • 1
    You've made the same mistake again. Your UserControl must not have its own view model. – Clemens Feb 02 '18 at 13:44
  • Yes, you told me that I should not have a viewModel in a UserControl because I would override the dataContext, but I solve that and the question here its quite different as the duplicate Question you are marking. – Nekeniehl Feb 02 '18 at 13:49
  • `` in a DataTemplate for ResultViewModel requires that the DataContext is inherited by the control. If you set it explicitly, the DataTemplate is useless. – Clemens Feb 02 '18 at 13:50
  • I left the `` basically because I get Designs errors saying that cannot solve the Symbol due to unknown DataContext – Nekeniehl Feb 02 '18 at 13:54
  • You should then have a design time DataContext, as described [here](https://stackoverflow.com/q/8303803/1136211). Seriously you should fix that first, the DataTemplate won't work otherwise. If you don't actually need the DataTemlate, remove it from the question. – Clemens Feb 02 '18 at 13:57
  • Understood about the "Design DataContext" but I cannot follow you or I'm mixing terms. I have updated the code on my question with, what I think, you are telling, but I still don't see how I can bind the UserControl.LeftClickCommand to the MainWindow. – Nekeniehl Feb 02 '18 at 14:12
  • Where is the command that you want to invoke from the UserControl defined? – mm8 Feb 02 '18 at 15:56
  • @mm8 Not sure how this can help, but I have editted my question and adding the Command declaration. It is define on the UserControlViewModel. – Nekeniehl Feb 02 '18 at 16:05
  • @Nekeniehl: Where is the command property that you want to defined? In the main window view model? – mm8 Feb 02 '18 at 16:06
  • The Command is define on the UserControlViewModel, but the one who should listen to it, is the MainWindowViewModel. – Nekeniehl Feb 02 '18 at 16:09
  • @Nekeniehl: So what does the command in the view model really do? Why don't you bind directly to a command in the MainWindowViewModel? – mm8 Feb 02 '18 at 16:18
  • I have a MainWindowView with a MainWindowViewModel and a MainWindowModel. Then I have a UserControlView with a UserControlViewModel and a UserControlModel. The event, it is happening in the UserControlView and catch it on the UserControlViewModel. The MainWindowViewModel KNOWS about the UserControlViewModel, but not other way around, means, that when the event is triggered I should somehow bubble it to the MainWindowViewModel. That is basically my question, how to proceed with that. – Nekeniehl Feb 02 '18 at 16:28
  • You could use an event aggregator or messenger. See my answer. This is best practice when it comes to communicate between view models in a loosely coupled fashion in MVVM. – mm8 Feb 02 '18 at 16:33

1 Answers1

3

You could either bind directly to a command in the MainWindowViewModel using {RelativeSource}:

<MouseBinding Gesture="LeftClick" Command="{Binding DataContext.CommandPropertyInMainWindowVm, RelativeSource={RelativeSource AncestorType=Window}}" />

Or you could bind to a command of the UserControlViewModel that publishes an event using an event aggregator or message bus. The MainWindowViewModel would then subscribe to this event. Please refer to the following blog post for more information about this: https://blog.magnusmontin.net/2014/02/28/using-the-event-aggregator-pattern-to-communicate-between-view-models/.

Using an event aggregator to communicate between two components, such as two view models, removes the tight coupling between the publisher and the subscriber of the event. Both view models have a reference to the event aggregator but they don't have to anything about each other. This is best practice when it comes to communicate between view models in a loosely coupled fashion in MVVM.

You will need an event aggregator implementation. Most MVVM libraries contain one. In Prism the class is called EventAggregator. In Mvvm Light it's called Messenger: https://marcominerva.wordpress.com/2014/06/25/how-to-send-string-and-content-messages-with-mvvm-light-messenger/. The concept is the same though.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • Thanks for your answer, The direct binding works as expected but I will also take a look on the EventAggregator. Thanks for the links! – Nekeniehl Feb 02 '18 at 16:43