Sinatr
Sinatr

Reputation: 21989

How to exclude child from layouting

Look at example:

<Grid VerticalAlignment="Top" Background="Yellow">
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition Width="auto" />
    </Grid.ColumnDefinitions>

    <TextBlock x:Name="textBlock"/>

    <Button Grid.Column="1">
        <Viewbox>
            <Path Height="100" Width="100" Fill="Black" Data="M 0,0 H 100 V 100 H 0 Z" />
        </Viewbox>
    </Button>
</Grid>

Here is a button with vector graphics and I want it to be as small as it needs to be (restrict vector graphics from exploding).

Here is how it looks like and how I want it to be:


There are several possible solutions how to overcome the problem, to list few:

  1. By binding Width/Height to some other element (this has issues with designer):

    ...
    <Button Height="{Binding ActualHeight, ElementName=textBlock}" ...>
    ...
    

    This is often used together with the ghost: special invisible element used by others (have no relationship with it) to layout themselves.

  2. By hosting element inside Canvas (which is magical container), but then Canvas itself require layouting.

    You can try putting Button inside Canvas. This will cause parent Grid only take height of TextBlock, but then there is another problem: how to position (layout) Canvas itself, so that its children are layout properly, kek.


In example above I don't actually want TextBlock to be a sister of Button, they could overlap (you wouldn't want the button to be hidden if available size is not enough, it should rather overlap something less important), I just want them to have same parent (if it moves - children will move). Confused? Look here:

<Grid>
    <TextBlock ... />
    <Button HorizontalAlignment="Right" ... />
</Grid>

This layout has same problems and can be solved similarly.


Now try to abstract from concrete examples above.

What I actually want: is to learn how to exclude element from layout of container. Like if element is collapsed, so the parent container will measure children sizes (except this element), layout children and then, after layouting is finished, element suddenly become visible and is restricted by parent container, while can use various alignments.

Does it make sense what I am asking? Maybe custom container is the way? Or do I miss something existing and obvious?

Upvotes: 0

Views: 249

Answers (1)

Mike Strobel
Mike Strobel

Reputation: 25623

If you want to constrain the Path by the size of the described geometry, it's as simple as setting StretchDirection="DownOnly" on the Viewbox.

If you truly want it to request no vertical space of its own, and have its height determined by its layout 'neighbors' (in this case, the TextBlock), then you'll need to wrap it in a custom layout container. But you can't simply exclude it from layout--at least not completely. If you did, the element would always end up with zero width and height. Instead, you can measure in two passes, with the first pass requesting a child height of zero, and the second pass basing the requested size on the arrange size given after the first pass.

I think the container below will give you what you want, but be warned that I haven't tested it thoroughly. Use at your own risk.

public class ZeroHeightDecorator : Border
{
    private Size _lastSize;
    private Size _idealSize;

    protected override void OnVisualChildrenChanged(DependencyObject added, DependencyObject removed)
    {
        base.OnVisualChildrenChanged(added, removed);
        _idealSize = new Size();
        _lastSize = new Size();
    }

    protected override Size MeasureOverride(Size constraint)
    {
        var child = this.Child;
        if (child == null)
            return new Size();
        if (child.IsMeasureValid)
            child.Measure(new Size(Math.Max(_lastSize.Width, constraint.Width), _lastSize.Height));
        else
            child.Measure(new Size(constraint.Width, 0d));
        _idealSize = child.DesiredSize;
        return new Size(_idealSize.Width, 0d);
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        var child = this.Child;
        if (child != null)
        {
            if (arrangeSize != _lastSize)
            {
                // Our parent will assume our measure is the same if the last
                // arrange bounds are still available, so force a reevaluation.
                this.InvalidateMeasure();
            }
            child.Arrange(new Rect(arrangeSize));
        }
        _lastSize = arrangeSize;
        return arrangeSize;
    }
}

A more flexible container would allow you to specify which dimension(s) to minimize: Width , Height, or Neither, or Both. Feel free to extend it :).

Example in action:

<Grid VerticalAlignment="Top" Background="Yellow">
  <Grid.ColumnDefinitions>
    <ColumnDefinition />
    <ColumnDefinition Width="Auto" />
  </Grid.ColumnDefinitions>

  <TextBlock x:Name="textBlock" />

  <t:ZeroHeightDecorator Grid.Column="1">
    <Button>
      <Viewbox>
        <Path Fill="Black" Data="M 0,0 H 100 V 100 H 0 Z" />
      </Viewbox>
    </Button>
  </t:ZeroHeightDecorator>
</Grid>

Screenshot

Upvotes: 1

Related Questions