Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make WPF/SL grid ignore a child element when determining size

I have a Grid which has several children, one of which is a ScrollViewer. I want the Grid to size itself based on all of its children except the ScrollViewer, which I want to just take up the space that is created by sizing the Grid for the rest of its children. (For example, the Grid is 2x2 and the ScrollViewer is in Row 0, Column 0, and so the other three Grid entries are enough to determine the dimensions of the Grid.)

Is there a nice way to do this? I've looked into creating a Custom Panel either to replace the Grid or to contain the ScrollViewer, but at no point during the MeasureOverride/ArrangeOverride calls do I ever get a call which can tell me about the final width/height of the grid row/column I care about (eg, Row 0, Column 0).

One thought I had was to derive from Grid and call the base MeasureOverride/ArrangeOverride but remove the ScrollViewer from the Children of the Grid right before the call (and put it back afterwards), but messing with the visual tree during layout computation seems like a bad idea.

Here's an example:

<Grid x:Name="LayoutRoot" Background="White">
    <Grid VerticalAlignment="Top" HorizontalAlignment="Left">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ScrollViewer Grid.Row="0" Grid.Column="0" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
            <Button Height="300" Width="300"/>
        </ScrollViewer>
        <Button Grid.Row="1" Grid.Column="0" Height="100" Width="100" Content="1,0"/>
        <Button Grid.Row="0" Grid.Column="1" Height="100" Width="100" Content="0,1"/>
        <Button Grid.Row="1" Grid.Column="1" Height="100" Width="100" Content="1,1"/>
    </Grid>
</Grid>

I would like the size of the Grid to not change as the size of the Button in the ScrollViewer changes - e.g., I want the Grid to give the ScrollViewer the 100x100 square that is determined by the rest of the Grid's contents. And if I changed each of the 3 100x100 buttons to be 200x200, I would want the ScrollViewer to get 200x200, etc.

Pavlo's example gets me the closest so far, with the Grid rows/columns appropriately sized, but ScrollViewer does not adapt to the Size given to it when Arrange is called. See below:

Image showing effect of Pavlo's NoSizeDecorator

like image 835
aggieNick02 Avatar asked Jan 31 '11 17:01

aggieNick02


2 Answers

If I understood correctly what you want, then you can do the following trick. Create a decorator that will ask for 0 space during the Measure stage and will arrange the child with all the given space at the Arrange stage:

public class NoSizeDecorator : Decorator
{
    protected override Size MeasureOverride(Size constraint) {
        // Ask for no space
        Child.Measure(new Size(0,0));
        return new Size(0, 0);
    }        
}

And your XAML will look like this:

<Grid x:Name="LayoutRoot" Background="White">
    <Grid VerticalAlignment="Top" HorizontalAlignment="Left">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <my:NoSizeDecorator Grid.Row="0" Grid.Column="0">
            <ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
                <Button Height="300" Width="300" Content="0,0"/>
            </ScrollViewer>
        </my:NoSizeDecorator>

        <Button Grid.Row="1" Grid.Column="0" Height="100" Width="100" Content="1,0"/>
        <Button Grid.Row="0" Grid.Column="1" Height="100" Width="100" Content="0,1"/>
        <Button Grid.Row="1" Grid.Column="1" Height="100" Width="100" Content="1,1"/>
    </Grid>
</Grid>
like image 126
Pavlo Glazkov Avatar answered Nov 10 '22 09:11

Pavlo Glazkov


Pavlo Glazkov's answer works very well for me! I have used it for a control that does not want to specify explicit sizes ... I added some logic and properties to defeat one dimension:

public sealed class NoSizeDecorator
            : Decorator
    {
        /// <summary>
        /// Sets whether the width will be overridden.
        /// </summary>
        public static readonly DependencyProperty DisableWidthOverrideProperty
                = DependencyProperty.Register(
                        nameof(NoSizeDecorator.DisableWidthOverride),
                        typeof(bool),
                        typeof(NoSizeDecorator),
                        new FrameworkPropertyMetadata(
                                false,
                                FrameworkPropertyMetadataOptions.AffectsMeasure));

        /// <summary>
        /// Sets whether the width will be overridden.
        /// </summary>
        public bool DisableWidthOverride
        {
            get => (bool)GetValue(NoSizeDecorator.DisableWidthOverrideProperty);
            set => SetValue(NoSizeDecorator.DisableWidthOverrideProperty, value);
        }

        /// <summary>
        /// Sets whether the height will be overridden.
        /// </summary>
        public static readonly DependencyProperty DisableHeightOverrideProperty
                = DependencyProperty.Register(
                        nameof(NoSizeDecorator.DisableHeightOverride),
                        typeof(bool),
                        typeof(NoSizeDecorator),
                        new FrameworkPropertyMetadata(
                                false,
                                FrameworkPropertyMetadataOptions.AffectsMeasure));

        /// <summary>
        /// Sets whether the height will be overridden.
        /// </summary>
        public bool DisableHeightOverride
        {
            get => (bool)GetValue(NoSizeDecorator.DisableHeightOverrideProperty);
            set => SetValue(NoSizeDecorator.DisableHeightOverrideProperty, value);
        }


        protected override Size MeasureOverride(Size constraint)
        {
            UIElement child = Child;
            if (child == null)
                return new Size();
            constraint
                    = new Size(
                            DisableWidthOverride
                                    ? constraint.Width
                                    : 0D,
                            DisableHeightOverride
                                    ? constraint.Height
                                    : 0D);
            child.Measure(constraint);
            return new Size(
                    DisableWidthOverride
                            ? child.DesiredSize.Width
                            : 0D,
                    DisableHeightOverride
                            ? child.DesiredSize.Height
                            : 0D);
        }
    }
like image 2
Steven Coco Avatar answered Nov 10 '22 07:11

Steven Coco