0

I have a pretty simple requirement where a UserControl needs to offer the user a way to select an item from its droplist. When the user clicks a button, the UserControl will perform some amount of internal tests, then it will call a method in the host application and pass it the user's selection.

I have this working using MVVM, but I'm a little perplexed by what I had to do to make it work. In my experience with databinding, it seems like I still have some gaps in my knowledge because each new implementation seems to get me with problems that I hadn't expected.

My UserControl is simple, it's a droplist and a button:

<UserControl x:Class="MyUserControl.UserControl1"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel Orientation="Vertical">
        <ComboBox ItemsSource="{Binding MyItems}" IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding MySelectedItem}" />
        <Button Content="Click me" Command="{Binding ClickMeCommand}" />
    </StackPanel>
</UserControl>

The code behind looks like this and just sets up the data for the controls:

using GalaSoft.MvvmLight.CommandWpf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace MyUserControl
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        public ICollectionView MyItems { get; set; }
        public RelayCommand ClickMeCommand { get; set; }
        public string MySelectedItem { get; set; }

        public ICommand HostClickMeCommand
        {
            get { return (ICommand)GetValue(HostClickMeCommandProperty); }
            set { SetValue(HostClickMeCommandProperty, value); }
        }

        // Using a DependencyProperty as the backing store for HostClickMeCommand.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HostClickMeCommandProperty =
            DependencyProperty.Register("HostClickMeCommand", typeof(ICommand), typeof(UserControl1), new PropertyMetadata(null));

        public UserControl1()
        {
            InitializeComponent();
            DataContext = this;
            MyItems = CollectionViewSource.GetDefaultView( new List<string> { "John", "Mary", "Joe" });
            ClickMeCommand = new RelayCommand( ExecuteClickMeCommand);
        }

        private void ExecuteClickMeCommand()
        {
            MessageBox.Show( "Hello from user control!");
            if( HostClickMeCommand != null) {
                HostClickMeCommand.Execute( MySelectedItem);
            }
        }
    }
}

You'll notice that the button click handler for my UserControl will display a message, then call into my application.

The application's XAML is also very easy:

<Window x:Class="MyHostApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:uc="clr-namespace:MyUserControl;assembly=MyUserControl"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <uc:UserControl1 HostClickMeCommand="{Binding MyHostClickMeCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
    </DockPanel>
</Window>

As is its code-behind:

using GalaSoft.MvvmLight.CommandWpf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace MyHostApplication {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public RelayCommand<string> MyHostClickMeCommand { get; set; }

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
            MyHostClickMeCommand = new RelayCommand<string>( (name) => { MessageBox.Show( String.Format( "Hello from host, {0}!", name)); });
        }
    }
}

This code works fine.

But my question is: Why do I have to have the RelativeSource specified in my binding? Since the DataContext for the application window is itself, why won't the Window bind UserControl's dependency property to MyHostClickMeCommand? If I remove the RelativeSource, the application's handler is not called.

I should also add that the reason why I want to figure out the proper way to define the binding is because I want to be able to set the ViewModel of my application to a different class. Ideally, I'd like my application to have this in the XAML:

<Window.DataContext>
    <local:MainViewModel />
</Window.DataContext>

where MainViewModel is in the ViewModels folder in my application's project file.

Dave
  • 14,618
  • 13
  • 91
  • 145
  • 1
    Explicitly setting its DataContext is probably the most frequently made mistake when creating a UserControl. Regardless of what they tell you in blogs or online tutorials, you should never ever do it, because it effectively prevents that your UserControl inherits the DataContext from its parent control. – Clemens Dec 05 '16 at 20:09
  • @Clemens thank you - yes, when I learned about databinding about 7 years ago, everyone was recommending this. And unfortunately, I never changed with the times. It will be different for me going forward! – Dave Dec 05 '16 at 21:09

2 Answers2

1

Whenever I see this outside of the main application startup :

DataContext = this;

or this

<UserControl.DataContext>
    <local:MyViewModel />
</UserControl.DataContext>

it always throws up red flags for me.

Unless this is a special case, it is always my advice to never hardcode the DataContext property inside of a UserControl. By doing so, you are preventing any other DataContext from getting passed to the UserControl, which kind of defeats one of WPF's biggest advantages of having separate UI and data layers.

Either build the UserControl specifically for use with a specific Model or ViewModel being used as the DataContext, such as this :

<!-- Draw anything of type MyViewModel with control MyUserControl-->
<!-- DataContext will automatically set to the MyViewModel -->
<DataTemplate DataType="{x:Type local:MyViewModel}}">
    <local:MyUserControl /> 
</DataTemplate>

Or build it with the expectation that the DataContext can be absolutely anything, and DependencyProperites will be used to give the control the data it needs :

<!-- DataContext property can be anything, as long as it has a property called MyDataProperty -->
<local:MyUserControl MyDependencyProperty="{Binding MyDataProperty}" />

But to answer your question, the reason you need to use RelativeSource in your binding is because bindings will by default use the DataContext, so it is trying to bind to UserControl1.DataContext.MyHostClickMeCommand. Since you have hardcoded DataContext = this; in the constructor, it is trying to bind to MyUserControl1.MyHostClickMeCommand which does not exist. The use of RelativeSource tells the binding it should get it's source from something other than the current `DataContext.

I see a lot of confusion about the DataContext from WPF beginners, and I usually send them to this StackOverflow Answer about the DataContext is for

Community
  • 1
  • 1
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Thanks, @Rachel! Side question about usage - what about in other projects that might use the MvvmLightToolkit's ViewModelLocator? That seems like a reasonable way to specify the DataContext (but in my case, it didn't work either). Thank you for the other link. I'm sure I have read it before when I was a WPF beginner. I actually think I still am one! – Dave Dec 05 '16 at 21:03
  • @Dave I thought I recognized your name/icon :) Personally I don't like using a `ViewModelLocator` as I think it moves control of your application out of the ViewModels and into the View layer, where I don't think it belongs. If I want design-time data, I usually just use a design-time data context with `xmlns:d="http://schemas.microsoft.com/expression/blend/2008"` and `d:DataContext`. So I'm not sure I could help you much with any questions you might have about using them. – Rachel Dec 05 '16 at 21:14
  • From my understanding from [this answer](http://stackoverflow.com/a/5462324/302677) when I was looking into ViewModels a long time ago, they are largely used for providing design-time data, or hooking up major sections of your application such as hooking together ApplicationView/ViewModel, DialogView/ViewModel, MenuView/ViewModel, etc. My major complaints about it can be found [here](http://stackoverflow.com/a/6696983/302677)... I always meant to expand that answer into a blog post about why I don't like ViewModelLocators, but I never got around to it :) – Rachel Dec 05 '16 at 21:16
  • ok no problem, it was a side question anyway. I do appreciate your perspective on this. – Dave Dec 05 '16 at 21:17
  • I haven't used the ViewModelLocator much, other than what I inherited from someone else, but using it an IoC in general seemed like a nice way to get at the business logic without having to construct and pass objects around. For example, if my View has a button that needs to make a motor move, the View would communicate to the Model via the ViewModel, but then the ViewModel has to have access to the Model. Previously, I would construct the Model and then pass it to the ViewModel. This probably has little to do with the VML discussion, but as an aggregate they seemed reasonable. – Dave Dec 05 '16 at 21:20
1

When you create a binding like the following

<TextBox x:Name="foo" Text="{Binding MuhText}" />

this is the equivalent of the following

foo.Text = foo.DataContext.MuhText;

Binding paths are rooted at the DataContext of the control being bound. When you say this,

<UserControl x:Class="MyUserControl.UserControl1"
    RemoveUselessNoise="true" >
    <StackPanel Orientation="Vertical">
        <ComboBox ItemsSource="{Binding MyItems}" 
                  SelectedItem="{Binding MySelectedItem}" />
        <Button Content="Click me" Command="{Binding ClickMeCommand}" />
    </StackPanel>
</UserControl>

You're still binding against the DataContext of the control (ComboBox and Button).

What you WANT is to bind against the instance of the UserControl in which these controls are defined. Some people suggest you do something like this:

public UserControl1()
{
    InitializeComponent();
    DataContext = this;
}

These people are mad hacks who exist only to set you up for failure down the road. This typically results in bindings not working as expected and generally interrupts the flow of the DataContext.

You are doing almost everything correctly. Your solution is to remove that DataContext=this; then rebase the Binding to your UserControl. You can do this a number of ways, but I think the easiest is to give your root an x:Name and use an ElementName binding.

<UserControl x:Class="MyUserControl.UserControl1"
             x:Name="RootNodeLol"
             RemoveUselessNoise="true" >
    <StackPanel Orientation="Vertical">
        <ComboBox ItemsSource="{Binding MyItems, ElementName=RootNodeLol}" 
                  SelectedItem="{Binding MySelectedItem, ElementName=RootNodeLol}" />
        <Button Content="Click me" Command="{Binding ClickMeCommand,
                                                      ElementName=RootNodeLol}" />
    </StackPanel>
</UserControl>

Assuming no other hanky panky is going on here, you should be good to go with that.

Side note, you should grab a copy of Snoop. You can examine your bindings at runtime and see why things aren't working as expected.

  • thank you for your comment. I must say that I got really embarrassed when you said "mad hacks" because I have mostly used `DataContext = this` in all of my software. Mainly because I didn't know any better when I first learned about MVVM and WPF, and also because @Rachel is pretty spot-on in that the UserControls I write aren't for anyone else's usage but my own. I have some more learning to do! – Dave Dec 05 '16 at 20:51
  • I've used Snoop to see overall structure before, but I haven't had success analyzing binding issues with it. I'll try it again. I removed the offensive DataContext assignments and have added a name to `UserControl1` and `MyHostApplication`, but in both cases, the bindings no longer work. The droplist is empty and the command handler doesn't execute, but I also don't get any binding errors in the output window in VS2013... Snoop doesn't show anything when I select "Show only visuals with binding errors". – Dave Dec 05 '16 at 21:00
  • @Dave You might have to specify the binding mode is TwoWay in the UserControl. –  Dec 05 '16 at 21:12
  • I tried that and it didn't make a difference, though I wasn't expecting to need it to be Two-Way in my particular use case. I wonder why VS and Snoop don't report any errors, even though the bindings are clearly broken? I can't find anywhere else that the DataContext is set, unless MvvmLightToolkit is doing something nefarious in the background... – Dave Dec 05 '16 at 21:15
  • @Dave You probably need to put back the `DataContext = this;` line in the MainWindow constructor to hook up your initial ViewModel to the Window's DataContext. From there, everything should be inherited, so the binding on your UserControl should correctly bind to `UserControl1`.DataContext.MyHostClickMeCommand` because `UserControl1.DataContext` will be set to the `MainWindow` object – Rachel Dec 05 '16 at 21:19
  • Can anyone explain why @Will's suggestion to set a Name for my UserControl and then use it for the individual Bindings does not work? If I can remove my dependency on using `DataContext = this`, I'd love to do it... – Dave Dec 06 '16 at 15:22
  • @Dave what is the DataContext on the UserControl within the Window? Normally, this is your ViewModel. But you're doing funky stuff, setting DataContext to `this` everywhere... it is messing with how MVVM works in WPF. I'll link to some answers I have that have almost complete examples of MVVM bindings. They might help you understand how things should work... http://stackoverflow.com/a/5651542 Oooh, this is a really relevant one, pls read it http://stackoverflow.com/a/28815689/1228 –  Dec 06 '16 at 15:31
  • That last one can be turned into a prototype, I'd strongly suggest you do. Use Snoop to view how the DataContext flows through the controls and to the UserControl. –  Dec 06 '16 at 15:32