Prevent RowHeight = Auto from Stretching my Grid?
在我的方案中,最终用户通过将其分成几行并为这些行定义高度规则(固定,填充空间,合适的内容)来自定义其用户界面。我使用WPF Grid来实现。
网格将开始填充整个屏幕,并且不应变大-用户必须始终可以看到整个网格(滚动条中没有行,但整个网格没有滚动条)。铅>
问题的症结所在:当用户创建一个或多个"自动"大小的行时,这些行中的内容可能会迫使整个网格的大小扩展,甚至在我已经创建了滚动条的情况下,也会引入滚动条将网格的最大高度设置为固定数字。
当涉及到星形大小的行时,情况会变得更糟,因为一旦网格稍稍拉伸,该星形大小的行就会填充可用空间,因此即使后来自动大小的行缩小,网格也会永久性地拉伸。
我需要找到一种方法来限制" auto "行,以便它们根据需要进行扩展和收缩,但是所有行的总实际高度永远不会大于整个网格。
为说明起见,我有一个固定最大高度的网格,并代表所有尺寸模式的行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <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 "最大高度。如何在保持自动调整大小行的灵活性的同时防止这种行为以保证网格的最大高度?
好吧,我发现我必须继承Grid的子类,以便可以覆盖Measure()和Arrange()布局步骤。
我并不是说这是一个很棒的通用解决方案,但是它适用于我的情况。请特别注意,我不处理列,因为在我的情况下,只有一列。我也没有在单元中定位元素(我将它们锚定在左上角)。
如果您需要更一般的解决方案,我认为这是一个很好的开始。列问题与行问题相同,只是在另一个方向上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | 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; } } |
这是一个测试用例。将其放在窗口中并调整窗口大小以使其正常工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <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> |