1
votes

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!

2
What is the purpose of all that? Binding Width and Height can usually be avoided by using appropriate layout elements. E.g. replace the Canvas by a Grid or Border. - Clemens
@Clemens, to create layout. Canvas will be a part of ListViewItem (to simulate frozen content, which stay disregards of horizontal scroll), but content is in datatemplate and I though I could autosize Canvas like this, otherwise ListViewItem is too small (Canvas.Height is 0 as I said). - Sinatr
But why "autosize" a Canvas, when other Panels already implement auto sizing? - Clemens
Canvas is special. Any other container will affect ListView column/row, Canvas - doesn't. It allow to create content which fly on top of ListViewItem. - Sinatr
I think the problem is that the Binding at runtime is evaluated before the child actually exists. Why can't you use ElementName binding for this? It seems like a good fit - Martin Zikmund

2 Answers

1
votes

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.

1
votes

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 } });
}