【问题标题】:GridBagLayout gridwidth doesn't work as expectedGridBagLayout gridwidth 不能按预期工作
【发布时间】:2015-11-14 05:08:44
【问题描述】:

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

ACC
BB

但是得到这样的布局

ACC

尽管 B 的网格宽度为 2,而 A 的网格宽度为 1,但 A 和 B 占用相同数量的列。我认为 A、B 和 C 之间不可能有一个非常小的列,因为 C 从第 1 列开始.如果C的gridwidth是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);

【问题讨论】:

    标签: java swing gridbaglayout


    【解决方案1】:

    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 .

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

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

    (我的一些cmets。)

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

    如果其中一列的当前权重为零,则它根本不会增加权重。如果所有列的权重为零,则循环甚至不会开始,所有权重将被视为“剩余”并进入最右边的单元格!

    这就是为什么你的论点

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

    并没有真正起作用,因为它是 C 结束(不是开始!)的列在这里很重要。

    示例

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

    1. 在第一次通过时,只访问了A。它有weightx1,都转到0 列。这里没有惊喜。但是12 列的权重仍然为零。

    2. 在第二遍期间,首先访问CC。它有weightx2。它占据了12 列,但由于它们的权重都为零,所以所有权重都进入了2 列,现在其权重为2

    3. 然后访问BB。它有weightx1。它在01 列之间拆分。但是1 列的权重仍然为零,因此所有这些都进入0 列。但是它已经有了重量1,所以它的重量和组件的重量之差为零,什么也没有发生。

    得到的权重是1-0-2,布局是

    A||CC
    B|| // B actually goes into the second column, but it has zero width
    

    但是如果BBweightx2 呢?然后在第 3 步之前一切都相同,但是组件的权重 (2) 与列 0 (1) 的当前权重之间的差异将是 1,因此 @987654363 @ 将添加到列0 的权重,其权重将变为21 列的权重仍然为零,并且生成的布局将是

    AA||CC
    BB|| // B actually goes into the second column, but it has zero width
    

    所有三个组件都将具有相同的宽度,但看起来仍然不像我们预期的那样。

    解决方案

    另一个答案中提到的一个明显的解决方案是在第一行添加一些虚拟单电池组件。一旦算法对第一行完成,它将导致列在所有通过期间具有非零权重。 JSeparatorJPanel 适用于此目的。通过调整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)。当然,这需要比宽度更复杂的计算。

    【讨论】:

      【解决方案2】:

      我知道你的感受...似乎GridBagLayout 将列的宽度减小到 0 当它们没有被 1x1 组件填充时。我不知道以下是否是最有效的解决方案,但它有效(通过将两个虚拟 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);
      

      【讨论】:

      • 如果你使用gridwidth &gt; 1,你还必须设置weightx = 1; weighty = 0,或者如果你使用gridheight &gt; 1,你必须设置weightx = 0; weighty = 1,这样额外的组件就不会占用空间。可惜几天前我没有看到你的答案:)
      猜你喜欢
      • 2012-11-18
      • 1970-01-01
      • 2015-06-26
      • 2013-05-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多