TL;DR
添加一个虚拟的第一行小尺寸单格JPanels 或JSeparators 可以用作填充和克服这个错误的功能。如果您事先知道您的权重,另一种选择是将GridBagLayout.columnWeights 设置为合理的值(例如,如果您只希望所有列的大小相同),但是虽然此解决方法解决了权重问题,但仍可能导致错误列宽度。
这个答案描述了列宽的问题,但完全相同的算法适用于行高,所以如果您在使用多行组件时遇到问题,以下所有内容仍然适用(只需将 x 更改为 @ 987654325@ 和宽度到高度)。
文档
这种行为背后的原因是一个非常灵活和有用的布局管理器的一个真正奇怪的属性。我什至不知道这是一个错误还是一个功能,因为列的宽度(以及因此的组件)是由权重决定的,而所有关于权重的文档都是
网格包布局管理器计算列的权重为
列中所有组件的最大权重x。如果
生成的布局在水平方向上小于它需要的区域
填充,额外的空间按比例分配到每一列
它的重量。权重为零的列不会收到额外的
空间。
这根本没有用,因为它没有说明组件占用多个单元格的情况。该教程更“有用”:
通常以 0.0 和 1.0 作为极值来指定权重:
必要时使用中间的数字。较大的数字表明
组件的行或列应该获得更多空间。对于每一列,
重量与为组件指定的最高重量 x 相关
在该列中,每个多列组件的权重为
在组件所在的列之间以某种方式拆分。 同样,每个
行的权重与指定的最高权重有关
该行中的组件。额外的空间趋向于
最右边的列和底行。
(强调我的。)
真的吗? 以某种方式拆分?现在,那真的很有帮助。
实际算法
这种拆分背后的实际算法非常不直观。它多次迭代所有组件。在第一遍中,它仅适用于尺寸最小的组件(就占用的单元而言)。然后它采用下一个尺寸并再次迭代,直到所有尺寸都消失。在您的情况下,组件 A 的大小为 1,组件 B 和 C 的大小为 2,因此迭代顺序为 A、C、B。如果 A 的大小为 3,则顺序为 C、B、A .
在每次迭代期间,算法使用以下过程计算组件占用的列的权重。
-
获取组件权重并减去它占用的所有列的权重,因为它们是在此时计算的(包括先前针对较小尺寸的迭代!)。结果数字是当前列总重量与组件重量之间的差值。
-
如果结果数为零或更小,则什么也不做。
-
否则,在列之间分配此差异,与它们当前的权重成比例!
-
如果有任何重量差异,请将这些“剩余物”添加到组件占用的最右侧单元格中。
实际代码,感兴趣的朋友:
px = constraints.tempX + constraints.tempWidth; // tempX is gridx, tempWidth is gridwidth
weight_diff = constraints.weightx; // the weight of the component
for (k = constraints.tempX; k < px; k++) // iteration over the cells
weight_diff -= r.weightX[k]; // r.weightX holds the current column weights
if (weight_diff > 0.0) {
weight = 0.0;
for (k = constraints.tempX; k < px; k++)
weight += r.weightX[k]; // weight is the total weight of the cells
for (k = constraints.tempX; weight > 0.0 && k < px; k++) {
double wt = r.weightX[k];
double dx = (wt * weight_diff) / weight; // a part of the weight
r.weightX[k] += dx;
weight_diff -= dx;
weight -= wt;
}
/* Assign the remainder to the rightmost cell */
r.weightX[px-1] += weight_diff;
}
(我的一些cmets。)
这个算法听起来或多或少是合理的,但它有一些缺点。
如果其中一列的当前权重为零,则它根本不会增加权重。如果所有列的权重为零,则循环甚至不会开始,所有权重将被视为“剩余”并进入最右边的单元格!
这就是为什么你的论点
我认为 A、B 和
C,因为 C 从第 1 列开始。
并没有真正起作用,因为它是 C 结束(不是开始!)的列在这里很重要。
示例
这对您来说意味着什么?我假设gridy=2 是一个错字,BB 应该有gridy=1(但这并没有真正改变任何东西,因为你所做的只是在0 和2 之间添加一个不可见的行1)。
-
在第一次通过时,只访问了A。它有weightx 和1,都转到0 列。这里没有惊喜。但是1 和2 列的权重仍然为零。
-
在第二遍期间,首先访问CC。它有weightx 和2。它占据了1 和2 列,但由于它们的权重都为零,所以所有权重都进入了2 列,现在其权重为2。
-
然后访问BB。它有weightx 和1。它在0 和1 列之间拆分。但是1 列的权重仍然为零,因此所有这些都进入0 列。但是它已经有了重量1,所以它的重量和组件的重量之差为零,什么也没有发生。
得到的权重是1-0-2,布局是
A||CC
B|| // B actually goes into the second column, but it has zero width
但是如果BB 有weightx 或2 呢?然后在第 3 步之前一切都相同,但是组件的权重 (2) 与列 0 (1) 的当前权重之间的差异将是 1,因此 @987654363 @ 将添加到列0 的权重,其权重将变为2。 1 列的权重仍然为零,并且生成的布局将是
AA||CC
BB|| // B actually goes into the second column, but it has zero width
所有三个组件都将具有相同的宽度,但看起来仍然不像我们预期的那样。
解决方案
另一个答案中提到的一个明显的解决方案是在第一行添加一些虚拟单电池组件。一旦算法对第一行完成,它将导致列在所有通过期间具有非零权重。 JSeparator 或 JPanel 适用于此目的。通过调整insets 和组件的高度,很容易获得漂亮的外观。
另一种可能的解决方案
如果您事先知道您的列权重,您可以简单地覆盖整个算法并简单地提供正确的权重
GridBagLayout layout = new GridBagLayout();
layout.columnWeights = new double[] { 1.0, 1.0, 1.0 };
test.setLayout(layout);
如果您需要列具有不同的权重,请相应地调整权重。然而,这种方法的问题在于,应用了一种有点相似的算法来计算列 widths(而不是权重)。特别是,如果在某个时刻,发现某个组件需要的宽度大于该组件所占据的列的总宽度,则宽度差异会分布在所有中列。
这意味着,例如,如果一个需要 150 像素的组件想要占据当前宽度为 0 和 50 的两列,则差 (100) 在列之间平均分配,从而产生 50/100拆分而不是预期的 75/75。解决方案是切换到假的第一行解决方案,或者直接以与columnWeights 相同的方式设置列宽(通过设置GridBagLayout.columnWidths)。当然,这需要比宽度更复杂的计算。