Creating custom user controls in WPF and as well as fully supporting MVVM with binding is a pretty straightforward process, but there are a couple of things that, at a first glance, look like they should work but they’re not. At least for me. One of those things is when you bind DataContext
to code behind, or to Self
.
A simple custom user control
<UserControl x:Class="SomeWpfApp.CustomUserControl"
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:SomeWpfApp"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
d:DataContext="{d:DesignInstance Type=local:CustomUserControl, IsDesignTimeCreatable=True}"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="{Binding CustomText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</UserControl>
Here, we specify a custom user control with a TextBlock
that binds to a custom dependency property in code behind called CustomText
.
public static readonly DependencyProperty CustomTextProperty =
DependencyProperty.Register(nameof(CustomText),
typeof(string),
typeof(CustomUserControl),
new FrameworkPropertyMetadata(""));
public string CustomText
{
get => (string)GetValue(CustomTextProperty);
set => SetValue(CustomTextProperty, value);
}
A problem and a solution
The problem with code above is that the DataContext
binding chain is now broken.
In WPF, if not specified otherwise, DataContext
is passed from Parent to it’s children. But by binding DataContext
to Self we’ve broken that chain of inheritance.
Which means that if you try to use this custom control somewhere else and you bind to CustomText
property:
<CustomUserControl CustomText="{Binding SomeOtherProperty}"/>
CustomUserControl
will expect that SomeOtherProperty
is actually in its code behind. The solution for this problem is to move DataContext
binding from parent to first child, like this:
<UserControl x:Class="SomeWpfApp.CustomUserControl"
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:SomeWpfApp"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:CustomUserControl}}"
d:DataContext="{d:DesignInstance Type=local:AbbeooLogonUserControl, IsDesignTimeCreatable=True}">
<TextBlock Text="{Binding CustomText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</UserControl>
Different problem and a not so good solution
90% of the time above specified solution will suffice, but there are times where you need to bind DataContext
to parent. One of those times is when you have some custom properties that are directly manipulating some aspect of a parent element.
Currently the only solution i found is to specifically point to a place from which to find a bound property. So something like this won’t work:
<CustomUserControl CustomText="{Binding SomeOtherProperty}"/>
but if you, lets say, bind a DataContext
to code behind in a control where you implement your custom control, you can do something like this:
<CustomUserControl
CustomText="{Binding SomeOtherProperty, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=RootControl}}"/>
Sometimes you need to do it like this if your DataContext
is in another class:
<CustomUserControl
CustomText="{Binding DataContext.SomeOtherProperty, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=RootControl}}"/>
Summing up
I hope this helped, if you have any questions feel free to contact me!