简体   繁体   中英

Binding to Children does not work at run-time

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!

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM