GridBagLayout网格宽度不能按预期工作

时间:2015-08-20 13:56:42

标签: java swing gridbaglayout

我正在使用java swing LayoutManager GridBagLayout,并遇到了这个问题。我想要一个像这样的布局

ACC
BB

但是得到这样的布局

ACC
B

尽管B的网格宽度为2,但A的网格宽度为1,A和B占用相同数量的列。我不认为A,B和C之间可能存在一个非常小的列因为C从第1列开始。如果C的网格宽度为1而不是2,则不会出现问题。我对输出感到困惑。

为什么会发生这种情况?我该如何解决这个问题?

JFrame test = new JFrame();
test.setSize(800,800);
test.setLayout(new GridBagLayout());
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

GridBagConstraints c;

c = new GridBagConstraints();
c.gridx=0;
c.gridy=0;
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.weighty = 1;
test.add(new JButton("A"), c);

c = new GridBagConstraints();
c.gridx=0;
c.gridy=2;
c.gridwidth=2;
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.weighty = 1;
test.add(new JButton("B"), c);

c = new GridBagConstraints();
c.gridx=1;
c.gridy=0;
c.gridwidth=2;
c.gridheight=2;
c.fill = GridBagConstraints.BOTH;
c.weightx = 2;
c.weighty = 2;
test.add(new JButton("C"), c);

test.setVisible(true);

2 个答案:

答案 0 :(得分:3)

我知道你的感受......似乎GridBagLayout在没有被1x1组件填充时将列的宽度减少到0。我不知道以下是否是最有效的解决方案,但它有效(通过添加两个虚拟JPanel来“膨胀”第1列和第2列):

JFrame test = new JFrame();
test.setSize(800,800);
test.setLayout(new GridBagLayout());
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

GridBagConstraints c;

c = new GridBagConstraints();
c.gridx=0;
c.gridy=0;
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.weighty = 1;
test.add(new JButton("A"), c);

c = new GridBagConstraints();
c.gridx=0;
c.gridy=1;
c.gridwidth=2;
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.weighty = 1;
test.add(new JButton("B"), c);

c = new GridBagConstraints();
c.gridx=1;
c.gridy=0;
c.gridwidth=2;
c.gridheight=1;
c.fill = GridBagConstraints.BOTH;
c.weightx = 2;
c.weighty = 2;
test.add(new JButton("C"), c);

c = new GridBagConstraints();
c.gridx=1;
c.gridy=2;
c.gridwidth=1;
c.gridheight=0;
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
c.weighty = 0;
test.add(new JPanel(), c);

c = new GridBagConstraints();
c.gridx=2;
c.gridy=3;
c.gridwidth=1;
c.gridheight=0;
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
c.weighty = 0;
test.add(new JPanel(), c);

test.setVisible(true);

答案 1 :(得分:1)

TL; DR

要么添加一个小的单单元格JPanelJSeparator的虚拟第一行,它们可以用作填充 并克服了这一错误特征。另一个替代方法是,如果您事先知道权重(例如,如果您只是希望所有列都具有相同的大小),则将GridBagLayout.columnWeights设置为合理的值,但是尽管这种解决方法可以解决权重问题,但仍然可能导致错误的列 widths。

此答案描述了列宽的问题,但是将相同的算法应用于行高,因此,如果您遇到多行组件的问题,则以下所有内容仍然适用(只需更改xy和宽度到高度)。

文档

此行为背后的原因是本来就非常灵活和有用的布局管理器的一个真正的怪异属性。我什至不知道这是错误还是功能,因为列的宽度(以及组件的宽度)是由权重决定的,所有文档都说权重是

网格袋布局管理器计算列的重量为 列中所有组件的最大权重x。如果 最终的布局在水平方向上小于其所需的面积 填充,多余的空间按比例分配给每列 它的重量。权重为零的列不会收到任何额外费用 空间。

这根本没有帮助,因为它没有说明组件占用多个单元格的情况。该教程更具“帮助性”:

通常将权重的极值指定为0.0和1.0: 必要时使用中间的数字。较大的数字表示 组件的行或列应获得更多空间。对于每一列, 重量与为组件指定的最高重量x相关 在该列中,为每个多列组件的权重 在组件所在的列之间以某种方式拆分。类似地,每个 行的权重与为 该行中的组件。多余的空间倾向于 最右列和最底行。

(强调我的。)

真的吗? 以某种方式拆分?现在,确实很有帮助。

实际算法

此拆分背后的实际算法确实不直观。迭代所有组件几次。在第一遍中,它仅适用于具有最小尺寸(就所占用的单元而言)的组件。然后,它取下一个大小并再次进行迭代,直到所有大小消失。在您的情况下,组件A的大小为1,而组件B和C的大小为2,因此迭代的顺序为A,C,B。如果A的大小为3,则顺序为C,B,A

在每次迭代过程中,该算法使用以下过程为组件占用的列计算权重。

  1. 采用组件权重并减去它占用的所有列的权重,因为它们是在此时进行计算的(包括较小尺寸的先前迭代!)。结果数是当前列的总重量与组件重量之间的差。

  2. 如果结果数为零或更少,则什么也不做。

  3. 否则,请在列之间分配此差异,与它们的当前权重成比例!

  4. 如果重量差还剩一些,请将这些“剩菜剩饭”添加到组件所占据的最右边的单元格中。

对感兴趣的实际代码:

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;
}

(我的一些评论。)

此算法听起来或多或少是合理的,但是它有一些缺点。

如果其中一列的当前权重等于零,则将完全不增加权重。如果 all 列的权重为零,则循环甚至不会开始,所有权重都将被视为“剩菜剩饭”,并进入最右边的单元格!

这就是为什么你的论点

我认为A,B和 C,因为C从第1列开始。

并不能真正起作用,因为这是C 结束(不开始!)所在的列。

示例

这对您而言意味着什么?我假设gridy=2是一个错字,BB应该有gridy=1(但这并没有真正改变任何东西,因为您所做的只是在{之间添加了一个不可见的行1 {1}}和0)。

  1. 在第一遍过程中,仅访问2。它具有A中的weightx,并且全部进入列1。毫不奇怪。但是列01的权重仍然为零。

  2. 在第二遍过程中,2首先被访问。它的CC中的weightx。它占用21列,但是由于它们的权重均为零,因此所有权重都进入现在具有权重2的列2

  3. 然后访问2。它的BB中的weightx。它在列10之间划分。但是列1的权重仍然为零,因此所有列都进入列1。但是它已经具有权重0,因此它的权重与组件的权重之差为零,并且什么也没有发生。

结果权重为1-0-2,布局为

1

但是如果A||CC B|| // B actually goes into the second column, but it has zero width 的{​​{1}}中有BB个,该怎么办?然后,直到第3步,一切都将是相同的,但是组件的权重(weightx)与列22)的当前权重之间的差将是{{ 1}},以便将0添加到列1的权重中,其权重将变为11列的权重仍然为零,并且最终的布局为

0

这三个组件的宽度都相同,但看起来仍然不符合我们的预期。

解决方案

另一个答案中提到的一个显而易见的解决方案是在第一行中添加一些虚拟单电池组件。一旦算法在第一行完成,它将导致列在所有遍历中的权重都为非零。 21可以达到此目的。通过使用AA||CC BB|| // B actually goes into the second column, but it has zero width 和组件的高度,很容易获得漂亮的外观。

另一种可能的解决方案

如果您事先知道列权重,则可以简单地覆盖整个算法,并通过以下方式提供正确的权重

JSeparator

如果您需要使列具有不同的权重,请相应地调整权重。但是,这种方法的问题在于,应用了某种相似的算法来计算列的 width (而不是权重)。特别是,如果某个时刻某个组件所需的宽度大于该组件所占用的列的总宽度,则宽度 difference 会分散在 all 列。

这意味着,例如,如果需要150个像素的组件要占用当前宽度为0和50的两列,则将差异(100)在各列之间平均分配,得出50/100而不是像人们期望的那样分割为75/75。解决方案是切换到伪造的第一行解决方案,或者以与JPanel相同的方式直接设置列宽(通过设置insets)。当然,这需要比宽度更复杂的计算。