0
votes

I will start with my code (entire sample is here: https://github.com/robertwojnar/MvvmDemo1) In my little demo I have single view application with usercontrol inside. So I have:

  1. MainWindow.xaml (my view)
  2. FooUserControl.xaml (view of my usercontrol)
  3. MainWindowViewModel.cs (viewmodel for my view)

and that's basically it. Very simple. Here is the code:

FooUserControl.xaml

    <UserControl x:Class="MvvmDemo1.WPF.Views.FooUserControl"
                 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:local="clr-namespace:MvvmDemo1.WPF.Views"
                 mc:Ignorable="d" 
                 d:DesignHeight="100" d:DesignWidth="100">
        <Grid MouseDown="UIElement_OnMouseDown">
            <Rectangle Fill="BlueViolet" />
        </Grid>
    </UserControl>

FooUserControl (code-behind)

    public partial class FooUserControl : UserControl
    {
        public FooUserControl()
        {
            InitializeComponent();
        }   

        public event EventHandler<BarEventArgs> BarClick;
        private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            double x = e.GetPosition(this).X;
            double y = e.GetPosition(this).Y;
            string value_to_pass = "[" + x + "," + y + "]"; 

            BarEventArgs bar = new BarEventArgs() { Bar = 2, Foo = value_to_pass };
            BarClick?.Invoke(sender, bar);
        }
    }

MainWindow.xaml (no code behind)

    <Window x:Class="MvvmDemo1.WPF.Views.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:MvvmDemo1.WPF.Views"
            mc:Ignorable="d"
            Title="MainWindow" Height="300" Width="300"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:cmd="http://www.galasoft.ch/mvvmlight"
            xmlns:viewModels="clr-namespace:MvvmDemo1.WPF.ViewModels">
        <Window.DataContext>
            <viewModels:MainWindowViewModel />
        </Window.DataContext>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Loaded">
                <cmd:EventToCommand Command="{Binding Mode=OneWay, Path=LoadedCommand}" PassEventArgsToCommand="True" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
        <Grid>
            <local:FooUserControl>
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="BarClick">
                        <cmd:EventToCommand Command="{Binding ClickedCommand}" PassEventArgsToCommand="True" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </local:FooUserControl>
        </Grid>
    </Window>

MainWindowViewModel.cs

    public class MainWindowViewModel : ObservableObject
    {
        public string Title => "Main window";

        public ICommand LoadedCommand => new RelayCommand(Loaded);
        private void Loaded()
        {
            Debug.WriteLine("Loaded");
        }   

        public ICommand ClickedCommand => new RelayCommand<BarEventArgs>(o => Clicked(o.Foo));
        private void Clicked(string a)
        {
            Debug.WriteLine("Clicked " + a);
        }
    }

As you can see the app is just a purple rectange which (on click event) sends click coordinates to MainWindowViewModel (via ICommand, using MVVM Light's EventToCommand and Interaction.Triggers).

Here is my problem and question:

I want to add a ViewModel for my UserControl. Want to add FooUserControlViewModel to FooUserControl's DataContext. The problem is, ClickedCommand is not fired when I set DataContext of FooUserControl. The question is Why?

When you clone my repo and change FooUserControl (code-behind) constructor to this, you will get my point:

    public FooUserControl()
    {
        InitializeComponent();
        DataContext = new FooUserControlViewModel();
    }

EDIT: It looks like, MainWindowViewModel is assigned to FooUserControl's DataContext (I think, because Iteraction.Triggers or EventToCommand). Why is that happening? Isn't this violating the rule that view-viewmodel connection should be 1<->1. One View - one ViewModel?

1
Is there any reason you don't just make your controls concrete by using the code-behind as the model? Is there some requirement that you have separated models or do you down the road intend to make them replaceable on the same visual or something? Concrete controls are much simpler and easier to work with and eliminate a lot of the kind of trouble you're seeing here, not sure why so many engineers try to over-complicate MVVM.tpartee

1 Answers

1
votes

If you want to assign a new DatContext to your local:FooUserControl you can do that but you'll no longer will be able to bind your controls from MainWindowViewModel directly. You have to change your Binding like below for Command:

<cmd:EventToCommand Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}},Path=DataContext.ClickedCommand}" PassEventArgsToCommand="True" />

When we set the DataContext of a control it will aotomatically pased down to it's child controls by Property Value Inheritance. And if you change DataContext of a UserControl in middle of a hierarchy/VisualTree the DataContext will be different for Ancestors and Descendants.

Also there is no such rule 1 View -1 ViewModel. It's totally depends your design a complexity/ Requirement how many ViewModels you have to design for your View or one ViewModel for different View.