好的,我发现我必须继承 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>