关于wpf:防止RowHeight =自动拉伸网格?

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>