在我的场景中,最终用户通过将用户界面分成行并定义这些行的高度规则(固定,填充空间,适合内容)来自定义用户界面。我使用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”最大高度。如何防止此行为保证网格的最大高度,同时保持自动调整行的灵活性?
答案 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>