How to correctly bind to a dependency property of a usercontrol in a MVVM framework

I have been unable to find a clean, simple, example of how to correctly implement a usercontrol with WPF that has a DependencyProperty within the MVVM framework. My code below fails whenever I assign the usercontrol a DataContext.

I am trying to:

  1. Set the DependencyProperty from the calling ItemsControl , and
  2. Make the value of that DependencyProperty available to the ViewModel of the called usercontrol.

I still have a lot to learn and sincerely appreciate any help.

This is the ItemsControl in the topmost usercontrol that is making the call to the InkStringView usercontrol with the DependencyProperty TextInControl (example from another question).

<ItemsControl ItemsSource="{Binding Strings}" x:Name="self" >

    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>


    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <DataTemplate.Resources>
                <Style TargetType="v:InkStringView">
                    <Setter Property="FontSize" Value="25"/>
                    <Setter Property="HorizontalAlignment" Value="Left"/>
                </Style>
            </DataTemplate.Resources>

            <v:InkStringView TextInControl="{Binding text, ElementName=self}"  />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Here is the InkStringView usercontrol with the DependencyProperty.

XAML:

<UserControl x:Class="Nova5.UI.Views.Ink.InkStringView"
         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" 
         x:Name="mainInkStringView"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>   
            <RowDefinition/>
        </Grid.RowDefinitions>

        <TextBlock Grid.Row="0" Text="{Binding TextInControl, ElementName=mainInkStringView}" />
        <TextBlock Grid.Row="1" Text="I am row 1" />
    </Grid>
</UserControl>

Code-Behind file:

namespace Nova5.UI.Views.Ink
{
    public partial class InkStringView : UserControl
    {
        public InkStringView()
        {
            InitializeComponent();
            this.DataContext = new InkStringViewModel();   <--THIS PREVENTS CORRECT BINDING, WHAT
        }                                                   --ELSE TO DO?????

        public String TextInControl
        {
            get { return (String)GetValue(TextInControlProperty); }
            set { SetValue(TextInControlProperty, value); }
        }

        public static readonly DependencyProperty TextInControlProperty =
            DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));

    }
}

That is one of the many reasons you should never set the DataContext directly from the UserControl itself.

When you do so, you can no longer use any other DataContext with it because the UserControl's DataContext is hardcoded to an instance that only the UserControl has access to, which kind of defeats one of WPF's biggest advantages of having separate UI and data layers.

There are two main ways of using UserControls in WPF

  1. A standalone UserControl that can be used anywhere without a specific DataContext being required.

    This type of UserControl normally exposes DependencyProperties for any values it needs, and would be used like this:

    <v:InkStringView TextInControl="{Binding SomeValue}" />
    

    Typical examples I can think of would be anything generic such as a Calendar control or Popup control.

  2. A UserControl that is meant to be used with a specific Model or ViewModel only.

    These UserControls are far more common for me, and is probably what you are looking for in your case. An example of how I would use such a UserControl would be this:

    <v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
    

    Or more frequently, it would be used with an implicit DataTemplate. An implicit DataTemplate is a DataTemplate with a DataType and no Key, and WPF will automatically use this template anytime it wants to render an object of the specified type.

    <Window.Resources>
        <DataTemplate DataType="{x:Type m:InkStringViewModel}">
            <v:InkStringView />
        </DataTemplate>
    <Window.Resources>
    
    <!-- Binding to a single ViewModel -->
    <ContentPresenter Content="{Binding MyInkStringViewModelProperty}" />
    
    <!-- Binding to a collection of ViewModels -->
    <ItemsControl ItemsSource="{Binding MyCollectionOfInkStringViewModels}" />
    

    No ContentPresenter.ItemTemplate or ItemsControl.ItemTemplate is needed when using this method.

Don't mix these two methods up, it doesn't go well :)


But anyways, to explain your specific problem in a bit more detail

When you create your UserControl like this

<v:InkStringView TextInControl="{Binding text}"  />

you are basically saying

var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;

vw.DataContext is not specified anywhere in the XAML, so it gets inherited from the parent item, which results in

vw.DataContext = Strings[x];

so your binding that sets TextInControl = vw.DataContext.text is valid and resolves just fine at runtime.

However when you run this in your UserControl constructor

this.DataContext = new InkStringViewModel();

the DataContext is set to a value, so no longer gets automatically inherited from the parent.

So now the code that gets run looks like this:

var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;

and naturally, InkStringViewModel does not have a property called text, so the binding fails at runtime.


Seems like you are mixing the model of the parent view with the model of the UC.

Here is a sample that matches your code:

The MainViewModel:

using System.Collections.Generic;

namespace UCItemsControl
{
    public class MyString
    {
        public string text { get; set; }
    }

    public class MainViewModel
    {
        public ObservableCollection<MyString> Strings { get; set; }

        public MainViewModel()
        {
            Strings = new ObservableCollection<MyString>
            {
                new MyString{ text = "First" },
                new MyString{ text = "Second" },
                new MyString{ text = "Third" }
            };
        }
    }
}

The MainWindow that uses it:

<Window x:Class="UCItemsControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:v="clr-namespace:UCItemsControl"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <v:MainViewModel></v:MainViewModel>
    </Window.DataContext>
    <Grid>
        <ItemsControl 
                ItemsSource="{Binding Strings}" x:Name="self" >

            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>


            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <DataTemplate.Resources>
                        <Style TargetType="v:InkStringView">
                            <Setter Property="FontSize" Value="25"/>
                            <Setter Property="HorizontalAlignment" Value="Left"/>
                        </Style>
                    </DataTemplate.Resources>

                    <v:InkStringView TextInControl="{Binding text}"  />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

Your UC (no set of DataContext):

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

    public String TextInControl
    {
        get { return (String)GetValue(TextInControlProperty); }
        set { SetValue(TextInControlProperty, value); }
    }

    public static readonly DependencyProperty TextInControlProperty =
        DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}

(Your XAML is OK)

With that I can obtain what I guess is the expected result, a list of values:

First
I am row 1
Second
I am row 1
Third
I am row 1

You're almost there. The problem is that you're creating a ViewModel for your UserControl. This is a smell.

UserControls should look and behave just like any other control, as viewed from the outside. You correctly have exposed properties on the control, and are binding inner controls to these properties. That's all correct.

Where you fail is trying to create a ViewModel for everything. So ditch that stupid InkStringViewModel and let whoever is using the control to bind their view model to it.

If you are tempted to ask "what about the logic in the view model? If I get rid of it I'll have to put code in the codebehind!" I answer, "is it business logic? That shouldn't be embedded in your UserControl anyhow. And MVVM != no codebehind. Use codebehind for your UI logic. It's where it belongs."


You need to do 2 things here (I'm assuming Strings is an ObservableCollection<string>).

1) Remove this.DataContext = new InkStringViewModel(); from the InkStringView constructor. The DataContext will be one element of the Strings ObservableCollection.

2) Change

<v:InkStringView TextInControl="{Binding text, ElementName=self}"  />

to

<v:InkStringView TextInControl="{Binding }" />

The xaml you have is looking for a "Text" property on the ItemsControl to bind the value TextInControl to. The xaml I put using the DataContext (which happens to be a string) to bind TextInControl to. If Strings is actually an ObservableCollection with a string Property of SomeProperty that you want to bind to then change it to this instead.

<v:InkStringView TextInControl="{Binding SomeProperty}" />