Reputation: 22007
I am binding Canvas
height to first children height so that it can be layed out properly, an example of layout:
<StackPanel>
<Canvas Height="{Binding Children[0].ActualHeight , RelativeSource={RelativeSource Self}}">
<Rectangle Height="100" Width="100" Fill="Red" />
</Canvas>
<TextBlock Text="Text" />
</StackPanel>
Without binding Canvas.Height
value is 0
, so that the "Text" overlaps, with binding - text is under in designer (you can try it yourself).
However during run-time binding fails and text overlaps.
System.Windows.Data Error: 17 : Cannot get 'Item[]' value (type 'UIElement') from 'Children' (type 'UIElementCollection'). BindingExpression:Path=Children[0].ActualHeight; DataItem='Canvas' (Name=''); target element is 'Canvas' (Name=''); target property is 'Height' (type 'Double') ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Why? Can I have designer-time behavior at run-time?
I would like not to give child a x:Name
and to bind using ElementName
.
Here is better MCVE:
<ListBox>
<TextBlock Text="1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1" />
<TextBlock Text="2" />
<TextBlock Text="3" />
<Canvas Height="100">
<TextBlock Canvas.Left="{Binding HorizontalOffset, RelativeSource={RelativeSource AncestorType=ScrollViewer}}"
Text="Frozen" />
</Canvas>
<TextBlock Text="4" />
<TextBlock Text="5" />
</ListBox>
It works like this:
"Frozen"
stays on screen disregards horizontal scrolling, while still treated as an item (vertical scrolling can move it away from view). This is possible to achieve due to Canvas
properties: it has 0
height and width and doesn't cause any effect on layout. Note binding to HorizontalOffset
.
Replace Height="100"
with Height="{Binding Children[0].ActualHeight, RelativeSource={RelativeSource Self}}"
. It works in designer, but not at run-time!
Upvotes: 1
Views: 309
Reputation: 7325
You could put this to a behavior:
private void Canvas_Loaded(object sender, RoutedEventArgs e)
{
(sender as Canvas)?.SetBinding(Canvas.HeightProperty, new Binding("Children[0].ActualHeight") { RelativeSource=new RelativeSource { Mode= RelativeSourceMode.Self } });
}
Upvotes: 1
Reputation: 22007
As @MartinZikmund comment
the problem is that the Binding at runtime is evaluated before the child actually exists
the second example can be rewritten as
<Canvas>
<TextBlock Canvas.Left="{Binding HorizontalOffset, RelativeSource={RelativeSource AncestorType=ScrollViewer}}"
Text="Frozen" />
<Canvas.Height>
<Binding Path="Children[0].ActualHeight" RelativeSource="{RelativeSource Self}" />
</Canvas.Height>
</Canvas>
Height
binding is set after content. Which makes it working at run-time.
Still the question why designer-time has no problems. Some wpf-magic I guess.
Upvotes: 1