DCardinal
DCardinal

Reputation: 195

How to use TemplateBinding in Path.Data?

How can I bind a property inside Path.Data to a TemplateBinding? I noticed that in this following example, the properties SegmentColor and StrokeThickness are set and updated correctly, but not the property TargetPoint. Further testing seems to confirm the issue seems to be related to the property being nested in an element of Path.Data. The following code tries to simplify the context which I am facing while creating the template for a custom control.

C#:

public class TestProgressBar : ProgressBar
{
    public Brush SegmentColor
    {
        get { return (Brush)GetValue(SegmentColorProperty); }
        set { SetValue(SegmentColorProperty, value); }
    }

    public double StrokeThickness
    {
        get { return (double)GetValue(StrokeThicknessProperty); }
        set { SetValue(StrokeThicknessProperty, value); }
    }

    public Point TargetPoint
    {
        get { return (Point)GetValue(TargetPointProperty); }
        set { SetValue(TargetPointProperty, value); }
    }

    public static readonly DependencyProperty StrokeThicknessProperty =
        DependencyProperty.Register(nameof(StrokeThickness), typeof(double), typeof(TestProgressBar), new PropertyMetadata());

    public static readonly DependencyProperty SegmentColorProperty =
        DependencyProperty.Register(nameof(SegmentColor), typeof(Brush), typeof(TestProgressBar), new PropertyMetadata(new SolidColorBrush(Colors.Red)));

    public static readonly DependencyProperty TargetPointProperty =
        DependencyProperty.Register(nameof(TargetPoint), typeof(Point), typeof(TestProgressBar), new PropertyMetadata());
}

Xaml:

<c:TestProgressBar StrokeThickness="15"
                   TargetPoint="100,0">
    <c:TestProgressBar.Style>
        <Style TargetType="{x:Type c:TestProgressBar}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type c:TestProgressBar}">
                        <Grid>
                            <Path
                                  Stroke="{TemplateBinding SegmentColor}"
                                  StrokeThickness="{TemplateBinding StrokeThickness}"
                                  Width="100" Height="100">
                                <Path.Data>
                                    <PathGeometry>
                                        <PathFigure>
                                            <LineSegment Point="{TemplateBinding TargetPoint}"/>
                                        </PathFigure>
                                    </PathGeometry>
                                </Path.Data>
                            </Path>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </c:TestProgressBar.Style>
</c:TestProgressBar>

Upvotes: 0

Views: 436

Answers (2)

thatguy
thatguy

Reputation: 22079

The issue is a little known detail about TemplateBinding, they do not work on properties Freezables. In fact, a LineSegment indirectly derives from Freezable as you can see in its inheritance hierarchy.

Object -> DispatcherObject -> DependencyObject -> Freezable -> Animatable -> PathSegment -> LineSegment

However, Path does not and that is why TemplateBindings on its properties work. As a template binding is just an optimized but limited version of a Binding, you can always use its binding syntax equivalent that does not come with any limitations and will also work on Freezables.

<LineSegment Point="{Binding TargetPoint, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>

Note, that the Mode is set to OneWay here, to point out that TemplateBindings are always one-way and this is the equivalent Binding, but bindings are more powerful and support any binding mode.

I am using readonly dependency properties, so the issue can't be linked to TwoWay binding problems (as I've hard can be the case with TemplateBinding).

A dependency property declaration must be read-only, but that does not make the dependency property read-only itself, that is done by declaring a dependency property key and using the RegisterReadOnly method.

Upvotes: 2

DCardinal
DCardinal

Reputation: 195

It seems that using TemplateBindings within the Path.Data causes the issue. Replacing it with a TemplatedParent Binding fixes the issue:

<LineSegment Point="{Binding TargetPoint, RelativeSource={RelativeSource TemplatedParent}}"/>

I can't quite explain why this is, though. In my original code, I am using readonly dependency properties, so the issue can't be linked to TwoWay binding problems (as I've hard can be the case with TemplateBinding).

I know that Binding using TemplatedParent is runtime-evaluated, as opposed to compile-time for TemplateBinding, so perhaps something along those lines fixes the binding.

Upvotes: 1

Related Questions