wpfuser-controlstemplatebinding

ControlTemplate LayoutTransform Binding System.Windows.Data Error 2 or 4


I'm creating a custom UserControl which will act as a container, showing a self-sized watermark behind its Content.

The relevant part of the ControlTemplate (I'll give you the full thing below) is

    <TextBlock
                        Text="{TemplateBinding WatermarkText}"
                        Foreground="{TemplateBinding WatermarkBrush}"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        FontWeight="Bold">
                                <TextBlock.LayoutTransform>
                                    <RotateTransform Angle="{Binding WatermarkAngle,RelativeSource={RelativeSource AncestorType={x:Type local:WatermarkUserControl}}}"/>
                                </TextBlock.LayoutTransform>
                            </TextBlock>

My UserControl has dependency properties for WatermarkText, WatermarkBrush, WatermarkAngle and WatermarkVisibility (I'll include that below). Notice that the TemplateBindings for WatermarkText, WatermarkBrush and WatermarkVisibility all work fine.

Using TemplateBinding for WatermarkAngle didn't work, because TemplateBinding is a lightweight "binding", so it doesn't support inheritance context. The WatermarkAngle binding that I ended up with (above) actually works; and yet a binding error is reported:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='[redacted namespace].WatermarkUserControl', AncestorLevel='1''. BindingExpression:Path=WatermarkAngle; DataItem=null; target element is 'RotateTransform' (HashCode=59772470); target property is 'Angle' (type 'Double')

So is there a way of doing this better, which avoids the error being reported? And why is it reporting an error with the binding, given that the binding is actually working? (If I change the value, it reflects that.)


That concludes the question. Here are all the parts you need, to satisfy MCVE:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

public class WatermarkUserControl : UserControl
{
    public static readonly DependencyProperty WatermarkTextProperty =
        DependencyProperty.Register(nameof(WatermarkText), typeof(string), typeof(WatermarkUserControl), new PropertyMetadata("Watermark"));
    public static readonly DependencyProperty WatermarkBrushProperty =
        DependencyProperty.Register(nameof(WatermarkBrush), typeof(Brush), typeof(WatermarkUserControl), new PropertyMetadata(new SolidColorBrush(Colors.LightGray)));
    public static readonly DependencyProperty WatermarkAngleProperty =
        DependencyProperty.Register(nameof(WatermarkAngle), typeof(double), typeof(WatermarkUserControl), new PropertyMetadata(0d));
    public static readonly DependencyProperty WatermarkVisibilityProperty =
        DependencyProperty.Register(nameof(WatermarkVisibility), typeof(Visibility), typeof(WatermarkUserControl), new PropertyMetadata(Visibility.Visible));

    public string WatermarkText
    {
        get { return (string)GetValue(WatermarkTextProperty); }
        set { SetValue(WatermarkTextProperty, value); }
    }

    public Brush WatermarkBrush
    {
        get { return (Brush)GetValue(WatermarkBrushProperty); }
        set { SetValue(WatermarkBrushProperty, value); }
    }

    public double WatermarkAngle
    {
        get { return (double)GetValue(WatermarkAngleProperty); }
        set { SetValue(WatermarkAngleProperty, value); }
    }

    public Visibility WatermarkVisibility
    {
        get { return (Visibility)GetValue(WatermarkVisibilityProperty); }
        set { SetValue(WatermarkVisibilityProperty, value); }
    }
}

ResourceDictionary:

<Style x:Key="WatermarkUserControlBaseStyle" TargetType="local:WatermarkUserControl">
    <Setter Property="Padding" Value="0"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:WatermarkUserControl">
                <Grid>
                    <local:NoSizeDecorator Visibility="{TemplateBinding WatermarkVisibility}">
                        <Viewbox>
                            <TextBlock
                                Text="{TemplateBinding WatermarkText}"
                                Foreground="{TemplateBinding WatermarkBrush}"
                                HorizontalAlignment="Center"
                                VerticalAlignment="Center"
                                FontWeight="Bold">
                                <TextBlock.LayoutTransform>
                                    <RotateTransform Angle="{Binding WatermarkAngle,RelativeSource={RelativeSource AncestorType={x:Type local:WatermarkUserControl}}}"/>
                                </TextBlock.LayoutTransform>
                            </TextBlock>
                        </Viewbox>
                    </local:NoSizeDecorator>
                    <ContentPresenter Content="{TemplateBinding Content}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Style x:Key="DraftWatermarkStyle" TargetType="local:WatermarkUserControl" BasedOn="{StaticResource WatermarkUserControlBaseStyle}">
    <Setter Property="WatermarkText" Value="DRAFT"/>
    <Setter Property="WatermarkBrush" Value="LightPink"/>
    <Setter Property="WatermarkAngle" Value="-20"/>
</Style>

NoSizeDecorator is defined here.

Example of use:

<local:WatermarkUserControl
    Style="{StaticResource DraftWatermarkStyle}"
    WatermarkVisibility="True">
    <StackPanel>
        <Label Content="Mr Smith"/>
        <Label Content="1 High Street"/>
        <Label Content="London"/>
    </StackPanel>
</local:WatermarkUserControl>

Solution

  • I've worked out a way of doing it. It has made the error go away, and it still seems to work, and I haven't spotted any side effects yet.

    I declared the RotateTransform as a local resource inside the Viewbox in the ControlTemplate, and then bound that as a StaticResource to the LayoutTransform property of the TextBlock. So the new version of the Viewbox from the question looks like this:

    <Viewbox>
        <Viewbox.Resources>
            <RotateTransform x:Key="RotateWatermarkTransform" Angle="{Binding WatermarkAngle,RelativeSource={RelativeSource TemplatedParent}}"/>
        </Viewbox.Resources>
    
        <TextBlock
            Text="{TemplateBinding WatermarkText}"
            Foreground="{TemplateBinding WatermarkBrush}"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontWeight="Bold"
            LayoutTransform="{StaticResource RotateWatermarkTransform}"/>
    </Viewbox>