阻止RowHeight = Auto来拉伸我的网格?

时间:2011-07-21 21:44:47

标签: wpf grid autosize

在我的场景中,最终用户通过将用户界面分成行并定义这些行的高度规则(固定,填充空间,适合内容)来自定义用户界面。我使用WPF Grid实现了这个。

网格开始填满整个屏幕,并且不应该变得更大 - 用户必须始终能够看到整个网格(滚动条WITHIN行是可以的,但不是整个网格的滚动条)。 / p>

我的问题的关键:当用户创建一个或多个“自动”大小的行时,这些行中的内容可以强制整个网格的大小扩展,引入滚动条,即使我设置了最大值网格高度为固定数字。

当涉及星形行时会变得更糟,因为一旦网格被拉伸一点,那个星形大小的行就会填充可用空间,因此即使自动大小行稍后收缩,网格也会永久拉伸。

我需要找到一种方法来限制“自动”行,以便它们根据需要进行扩展和缩小,但所有行的总实际高度绝不会大于整个网格。

为了说明,我的网格具有固定的最大高度,以及代表所有尺寸模式的行。

<Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="200"/>
    <RowDefinition Height="*"/>
</Grid.RowDefinitions>

<ScrollViewer VerticalScrollBarVisibility="Auto">
    <TextBlock>
        abc<LineBreak/>
        abc<LineBreak/>
        abc<LineBreak/>
        abc<LineBreak/>
        abc<LineBreak/>                    
    </TextBlock>
</ScrollViewer>

在此示例中,随着“abc”文本块的扩展,网格的总高度超出固定的“300”最大高度。如何防止此行为保证网格的最大高度,同时保持自动调整行的灵活性?

1 个答案:

答案 0 :(得分:0)

好的,我发现我必须继承Grid,以便我可以覆盖Measure()和Arrange()布局步骤。

我并不认为这是一个很棒的通用解决方案,但它适用于我的场景。特别要注意的是我没有处理列,因为在我的情况下,只有一列。我也没有在细胞内定位元素(我将它们固定在左上角)。

如果您需要更通用的解决方案,我认为这是一个非常好的开始。列问题与行问题相同,只是在另一个方向。

class NoStretchGrid:Grid
{
    //this override determines what size we ask to be
    //gotta make sure we never ask for more than the max height
    protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint)
    {
        //what would a basic Grid do?
        System.Windows.Size desiredSize = base.MeasureOverride(constraint);

        if (desiredSize.Height > constraint.Height)
            desiredSize.Height = constraint.Height;

        //if max height is defined and desired height is too big, reduce it
        if (this.MaxHeight != double.NaN && desiredSize.Height > this.MaxHeight)
        {
            desiredSize.Height = this.MaxHeight;
        }

        return desiredSize;
    }

    //this override tells child controls how big they can be and where they're positioned
    protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
    {
        //must decide how tall each row will be
        double[] desiredHeights = new double[this.RowDefinitions.Count];
        double[] minimumHeights = new double[this.RowDefinitions.Count];
        double[] finalHeights = new double[this.RowDefinitions.Count];

        //first, find out how tall each row wants to be

        //check for fixed-size rows
        for (int i = 0; i < desiredHeights.Length; i++)
        {
            if (this.RowDefinitions[i].Height.IsAbsolute)
            {
                desiredHeights[i] = this.RowDefinitions[i].Height.Value;
            }
            else
            {
                desiredHeights[i] = 0;
            }

            minimumHeights[i] = this.RowDefinitions[i].MinHeight;
        }

        //then ask children how big they want to be
        foreach (UIElement child in this.InternalChildren)
        {
            int row = Grid.GetRow(child);
            if (!this.RowDefinitions[row].Height.IsAbsolute && child.DesiredSize.Height > desiredHeights[row])
            {
                desiredHeights[row] = child.DesiredSize.Height;
            }

            if ((child as FrameworkElement).MinHeight > minimumHeights[row])
            {
                minimumHeights[row] = (child as FrameworkElement).MinHeight;
            }
        }

        double availableHeight = arrangeSize.Height;

        //reserve minimum heights
        for (int i = 0; i < minimumHeights.Length; i++)
        {
            finalHeights[i] = minimumHeights[i];
            availableHeight -= finalHeights[i];
        }            

        //allow fixed-height rows their height - if some ignoramus made fixed-heights too big, we can't help him
        for (int i = 0; i < desiredHeights.Length; i++)
        {
            if (this.RowDefinitions[i].Height.IsAbsolute)
            {
                finalHeights[i] = this.RowDefinitions[i].Height.Value;
                availableHeight = availableHeight + minimumHeights[i] - finalHeights[i];
            }
        }

        //allow auto-size rows their desired heights, so long as there's height left to be had
        for (int i = 0; i < desiredHeights.Length; i++)
        {                
            if (this.RowDefinitions[i].Height.IsAuto)
            {
                double desiredHeightIncrease = desiredHeights[i] - minimumHeights[i];

                if (desiredHeightIncrease <= availableHeight)
                {
                    finalHeights[i] += desiredHeightIncrease;
                    availableHeight -= desiredHeightIncrease;
                }
                else
                {
                    finalHeights[i] = minimumHeights[i] + availableHeight;
                    availableHeight = 0;
                }
            }
        }

        //now that auto-size rows have been prevented from getting out of control, make the min heights of any star-size rows available again
        for (int i = 0; i < desiredHeights.Length; i++)
        {
            if (this.RowDefinitions[i].Height.IsStar)
            {
                availableHeight += minimumHeights[i];
            }
        }

        //divide any leftover available height proportionally amongst the star-sized rows, while there's height left to be had
        double totalStarValues = 0;
        for (int i = 0; i < desiredHeights.Length; i++)
        {
            if (this.RowDefinitions[i].Height.IsStar)
            {
                totalStarValues += this.RowDefinitions[i].Height.Value;
            }
        }

        for (int i = 0; i < desiredHeights.Length; i++)
        {
            if (this.RowDefinitions[i].Height.IsStar)
            {
                finalHeights[i] = availableHeight * (this.RowDefinitions[i].Height.Value / totalStarValues);
            }
        }

        //decide the vertical position of each row
        double[] rowPositions = new double[desiredHeights.Length];
        rowPositions[0] = 0;
        for (int i = 1; i < rowPositions.Length; i++)
        {
            rowPositions[i] = rowPositions[i - 1] + finalHeights[i - 1];
        }

        //tell children to lay themselves out based on these results
        foreach (UIElement child in this.InternalChildren)
        {
            int row = Grid.GetRow(child);

            //special case for scrollviewer, which doesn't size itself appropriately
            if (child is ScrollViewer)
            {
                ScrollViewer scrollViewer = child as ScrollViewer;

                //temporarily update its height value, JUST for the Arrange() call
                double oldHeight = scrollViewer.Height;
                scrollViewer.Height = finalHeights[row];
                child.Arrange(new Rect(0, rowPositions[row], arrangeSize.Width, finalHeights[row]));

                //restore the original value
                scrollViewer.Height = oldHeight;
            }

            //typical case for non-scroll-viewers
            else
            {
                child.Arrange(new Rect(0, rowPositions[row], arrangeSize.Width, finalHeights[row]));
            }
        }

        return arrangeSize;
    }
}

这是一个测试案例。将其放在窗口中并调整窗口大小以使其正常工作。

<local:NoStretchGrid VerticalAlignment="Stretch">

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="2*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="50"/>
    </Grid.RowDefinitions>

    <ScrollViewer VerticalScrollBarVisibility="Visible" MinHeight="50">
        <Rectangle Fill="Orange" Height="250"/>
    </ScrollViewer>

    <ScrollViewer VerticalScrollBarVisibility="Visible" Grid.Row="1" MinHeight="50">
        <Rectangle Fill="Blue" Height="200"/>
    </ScrollViewer>

    <Grid Background="Pink" Grid.Row="2" MinHeight="30"/>
    <Grid Background="Green" Grid.Row="3" MinHeight="30"/>
    <Grid Background="Red" Grid.Row="4"/>

</local:NoStretchGrid>