Java Swing 递归布局



我知道这看起来像是重复的,但是通过尝试无效/验证/重新验证/重新绘制,我一无所获,所以请耐心等待。我的面板结构如下所示:

JPanel/GridBagLayout (1)
+-- JPanel/BorderLayout
    +-- JPanel/GridBagLayout (2)
    |   +-- JTextField (3)
    |   +-- JComboBox
    +-- JPanel/WhateverLayout
    ...

。等等。在我的子面板中(2)我更改了插图(右侧和底部),并且我想将整个顶部面板布局(1)。有没有一些简单的方法来布局所有内容(最好是从顶部面板(1)向下,但任何有效的方法都是好的)。现在我已经尝试了所有级别的无效/验证/重新验证/重新绘制的组合,但似乎没有任何效果(实际上根本没有任何变化)。谢谢!

编辑:我发现GridBagLayout在添加组件时克隆GridBagConstraints,所以我的代码无法通过运行重新验证和朋友来工作的原因是我更新了错误的约束。我在下面找到并添加了解决此问题的方法。

  • 对于整个re_layout JFrameJDialog e.i.)有没有JFrame#pack()

  • 对于填充容器中的可用区域(remove/add之后)是否有revalidate()repaint()

  • 您必须决定Components hierarchy中的哪些容器

关于 pack() & (re)validate()repaint() 的代码示例

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
public class AddComponentsAtRuntime {
    private JFrame f;
    private JPanel panel;
    private JCheckBox checkValidate, checkReValidate, checkRepaint, checkPack;
    public AddComponentsAtRuntime() {
        JButton b = new JButton();
        b.setBackground(Color.red);
        b.setBorder(new LineBorder(Color.black, 2));
        b.setPreferredSize(new Dimension(600, 10));
        panel = new JPanel(new GridLayout(0, 1));
        panel.add(b);
        f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(panel, "Center");
        f.add(getCheckBoxPanel(), "South");
        f.setLocation(200, 200);
        f.pack();
        f.setVisible(true);
    }
    private JPanel getCheckBoxPanel() {
        checkValidate = new JCheckBox("validate");
        checkValidate.setSelected(false);
        checkReValidate = new JCheckBox("revalidate");
        checkReValidate.setSelected(false);
        checkRepaint = new JCheckBox("repaint");
        checkRepaint.setSelected(false);
        checkPack = new JCheckBox("pack");
        checkPack.setSelected(false);
        JButton addComp = new JButton("Add New One");
        addComp.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JButton b = new JButton();
                b.setBackground(Color.red);
                b.setBorder(new LineBorder(Color.black, 2));
                b.setPreferredSize(new Dimension(600, 10));
                panel.add(b);
                makeChange();
                System.out.println(" Components Count after Adds :" + panel.getComponentCount());
            }
        });
        JButton removeComp = new JButton("Remove One");
        removeComp.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int count = panel.getComponentCount();
                if (count > 0) {
                    panel.remove(0);
                }
                makeChange();
                System.out.println(" Components Count after Removes :" + panel.getComponentCount());
            }
        });
        JPanel panel2 = new JPanel();
        panel2.add(checkValidate);
        panel2.add(checkReValidate);
        panel2.add(checkRepaint);
        panel2.add(checkPack);
        panel2.add(addComp);
        panel2.add(removeComp);
        return panel2;
    }
    private void makeChange() {
        if (checkValidate.isSelected()) {
            panel.validate();
        }
        if (checkReValidate.isSelected()) {
            panel.revalidate();
        }
        if (checkRepaint.isSelected()) {
            panel.repaint();
        }
        if (checkPack.isSelected()) {
            f.pack();
        }
    }
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                AddComponentsAtRuntime makingChanges = new AddComponentsAtRuntime();
            }
        });
    }
}
GridBagLayout

添加组件时克隆GridBagConstraints(这就是为什么更改我的原始内容对布局没有影响的原因),因此我扩展了GridBagLayout以便能够更新实际的约束运行时。下面的代码根据组件的类型设置布局边距,如果"展开",这是我用来在两种模式之间切换的:

public class ExpandableGridBagLayout extends GridBagLayout {
    public void setExpand(boolean expanded) {
        for (Map.Entry<Component, GridBagConstraints> entry : comptable.entrySet()) {
            setExpandedMargin(entry.getKey(), entry.getValue(), expanded);
        }
    }
    private void setExpandedMargin(Component component, GridBagConstraints constraints, boolean expanded) {
        constraints.insets.right = 2;
        if (component instanceof JLabel) {
            constraints.insets.top = expanded ? 3 : 0;
            constraints.insets.bottom = expanded ? 3 : 0;
        } else {
            constraints.insets.bottom = expanded ? 8 : 5;
        }
    }
}

然后我所要做的就是按预期打电话给panel.revalidate() (1)和布局工作。

仍然不完全确定您的确切上下文,但从您的"答案"来看,您似乎正在动态更改组件约束。要触发受影响部分的重新布局,您需要

    使
  • 约束已更改的子/ren 无效(注意:仅使父项无效是不够的),这将根据需要冒泡层次结构
  • 验证层次结构中的相应容器

片段:

ExpandableGridBagLayout bag = panel2.getLayout();
bag.setExpand(true);
for(Component child: panel2.getComponents())
    child.invalidate();
panel1.validate();

没有公共 API 可以递归地使给定容器下的所有内容无效(invalidateTree 是包私有的)。一个快速的技巧是暂时切换父容器的字体(内部消息无效树)

/**
 * Invalidates the component hierarchy below the given container.<p>
 * 
 * This implementation hacks around package private scope of invalidateTree by 
 * exploiting the implementation detail that the method is internally 
 * used by setFont, so we temporary change the font of the given container to trigger
 * its internal call.<p>
 * 
 * @param parent
 */
protected void invalidateTree(Container parent) {
    Font font = parent.getFont();
    parent.setFont(null); 
    parent.setFont(font);
}

编辑

不知道这个答案的哪一部分你的意思是不正确 - 显然,如果没有任何详细的知识,我就无法解决你的问题;-)

好奇的我想知道层次结构中的重新验证如何导致有效孙/子的重新布局:验证/树显然停止在有效组件上。下面是一个代码片段

  • 这是一个两级层次结构,父母有两个孩子
  • 该操作更改了脚下的姐妹的布局影响属性(我们更改了布局的 V/Hgap)并重新验证父级

结果各不相同,取决于父级的布局管理器

  • 使用FlowLayout,什么都不会发生,
  • 使用BoxLayout,它可能会被验证,具体取决于
    • 水平或垂直间隙是否改变,以及
    • 盒子的方向

看起来有效子项的重新布局可能是(或不是)更高层重新布局的副作用 - 没有任何保证发生并且难以预测。没有什么我想依靠的;-)

final JComponent sister = new JPanel();
final Border subBorder = BorderFactory.createLineBorder(Color.RED);
sister.setBorder(subBorder);
sister.add(new JTextField(20));
sister.add(new JButton("dummy - do nothing"));
JComponent brother = new JPanel();
brother.setBorder(BorderFactory.createLineBorder(Color.GREEN));
brother.add(new JTextField(20));
// vary the parent's LayoutManager
final JComponent parent = Box.createVerticalBox();
// final JComponent parent = Box.createHorizontalBox();
// final JComponent parent = new JPanel();
parent.add(sister);
parent.add(brother);
// action to simulate a change of child constraints 
Action action = new AbstractAction("change sub constraints") {
    @Override
    public void actionPerformed(ActionEvent e) {
        FlowLayout layout = (FlowLayout) sister.getLayout();
        layout.setHgap(layout.getHgap() * 2);
      //  layout.setVgap(layout.getVgap() * 2);
      //  sister.invalidate();
        parent.revalidate();
    }
};
brother.add(new JButton(action));
JFrame frame = new JFrame("play with validation");
frame.add(parent);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);

最新更新