Reputation: 195
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
Reputation: 22079
The issue is a little known detail about TemplateBinding
, they do not work on properties Freezable
s. 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 TemplateBinding
s 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 Freezable
s.
<LineSegment Point="{Binding TargetPoint, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>
Note, that the Mode
is set to OneWay
here, to point out that TemplateBinding
s 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
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