JPanel 中的 JPanel 太多(使用 GridBagLayout)

Too many JPanels inside a JPanel (with GridBagLayout)

所以基本上,如果我将 JPanels 放入使用 GridBagLayoutJPanel 中,并使用 setPreferredSize 限制大小,最终它会达到可以'持有所有这些,它表现出附图所示的行为:

我正在制作 accordion。这只是展示我遇到的问题的示例。手风琴的每个部分都可以单独打开,它们的大小是任意的,可以即时添加。很容易获得所有单个面板的高度并将它们与总高度进行比较,但是当添加太多时,它会表现出我所展示的嘎吱作响的行为。这也会缩小高度,因此更难确定何时发生了嘎吱作响。我将不得不缓存高度并以某种方式预先计算添加的新部件的高度。最终目标是在添加新面板并且没有足够的空间容纳它时删除旧面板。

是否有一种简单的方法可以确定某物在不受约束的情况下的高度,或者是否有一种受支持的方法来检测何时发生这种嘎吱嘎吱的声音(这样我就可以在再次涂漆之前快速将其变薄)?使 GridBagLayout 表现得像其他一些布局并溢出到 hammerspace 而不是压缩的选项也可以。

代码示例:

import java.awt.*;
import java.awt.event.*;
import javaisms.out;
import javax.swing.*;

public class FoldDrag extends JLayeredPane {
    public TexturedPanel backingPanel = new TexturedPanel(new GridBagLayout(),"data/gui/grayerbricks.png");
    static JPanel windowbase=new JPanel();
    static JPanel restrictedpanel=new JPanel(new GridBagLayout());

    GridBagConstraints gbc = new GridBagConstraints();

    public FoldDrag() {
        JButton addpan  = new JButton("Add things");
        windowbase.add(addpan);
        windowbase.add(restrictedpanel);
        restrictedpanel.setBackground(Color.red);
        restrictedpanel.setPreferredSize(new Dimension(200,200));
        gbc.weighty=1;
        gbc.weightx=1;
        gbc.gridx=0;
        gbc.gridy=0;
        gbc.gridheight=1;
        gbc.gridwidth=1;
        gbc.fill=GridBagConstraints.HORIZONTAL;
        addpan.addActionListener(new ActionListener() {
            int number=0;
            @Override
            public void actionPerformed(ActionEvent e)
            {
                number++;
                gbc.gridy=number;
                JPanel tmppanel = new JPanel();
                tmppanel.setPreferredSize(new Dimension(100,30));
                if(number%3==0)
                    tmppanel.setBackground(Color.blue);
                if(number%3==1)
                    tmppanel.setBackground(Color.yellow);
                if(number%3==2)
                    tmppanel.setBackground(Color.green);
                restrictedpanel.add(tmppanel,gbc);
                restrictedpanel.validate();
            }
        });
        windowbase.setVisible(true);
    }
    private static void createAndShowUI() {
        JFrame frame = new JFrame("DragLabelOnLayeredPane");
        frame.getContentPane().add(windowbase);
        FoldDrag thedrag=new FoldDrag();
        windowbase.add(thedrag);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(300,300));
        frame.pack();
        frame.setResizable(true);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        out.active=true;
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                createAndShowUI();
            }
        });
    }
}

编辑:似乎我没有很好地描述我的手风琴版本。这是 link.

panel.getPreferredSize().height != panel.getHeight()panel.getPreferredSize().width != panel.getWidth()

时,您可以分辨出某些东西是 "crunched"

您有特殊要求,使用它的布局管理器可能会更好地满足您的要求。这使您能够控制布局的各个方面,而无需诉诸 hack 或 "work arounds" 永远不会奏效或有奇怪的副作用

public class AccordionLayout implements LayoutManager {

    // This "could" be controlled by constraints, but that would assume
    // that more then one component could be expanded at a time
    private Component expanded;

    public void setExpanded(Component expanded) {
        this.expanded = expanded;
    }

    public Component getExpanded() {
        return expanded;
    }

    @Override
    public void addLayoutComponent(String name, Component comp) {
    }

    @Override
    public void removeLayoutComponent(Component comp) {
    }

    @Override
    public Dimension preferredLayoutSize(Container parent) {
        Dimension size = minimumLayoutSize(parent);
        if (expanded != null) {
            size.height -= expanded.getMinimumSize().height;
            size.height += expanded.getPreferredSize().height;
        }

        return size;
    }

    @Override
    public Dimension minimumLayoutSize(Container parent) {
        int height = 0;
        int width = 0;
        for (Component comp : parent.getComponents()) {
            width = Math.max(width, comp.getPreferredSize().width);
            height += comp.getMinimumSize().height;
        }
        return new Dimension(width, height);
    }

    @Override
    public void layoutContainer(Container parent) {

        Insets insets = parent.getInsets();
        int availableHeight = parent.getHeight() - (insets.top + insets.bottom);
        int x = insets.left;
        int y = insets.top;

        int maxSize = 0;
        Dimension minSize = minimumLayoutSize(parent);
        if (expanded != null) {
            minSize.height -= expanded.getMinimumSize().height;
            // Try an honour the preferred size the expanded component...
            maxSize = Math.max(expanded.getPreferredSize().height, availableHeight - minSize.height);
        }

        int width = parent.getWidth() - (insets.left + insets.right);
        for (Component comp : parent.getComponents()) {
            if (expanded != comp) {
                comp.setSize(width, comp.getMinimumSize().height);
            } else {
                comp.setSize(width, maxSize);
            }
            comp.setLocation(x, y);
            y += comp.getHeight();
        }

    }

}

以及可运行的示例...

这就到了极致,创建了一个专门的组件来充当每个 "fold",但这只是从外部降低了 API 的复杂性,也就是说,你只需要考虑一下关于标题和内容,让 API 的其余部分自行处理

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.LineBorder;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private AccordionLayout layout;

        public TestPane() {
            layout = new AccordionLayout();
            setLayout(layout);

            AccordionListener listener = new AccordionListener() {
                @Override
                public void accordionSelected(Component comp) {
                    layout.setExpanded(comp);
                    revalidate();
                    repaint();
                }
            };

            Color colors[] = {Color.RED, Color.BLUE, Color.CYAN, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.YELLOW};
            String titles[] = {"Red", "Blue", "Cyan", "Green", "Magenta", "Orange", "Pink", "Yellow"};
            for (int index = 0; index < colors.length; index++) {
                AccordionPanel panel = new AccordionPanel(titles[index], new ContentPane(colors[index]));
                panel.setAccordionListener(listener);
                add(panel);
            }
        }

    }

    public class ContentPane extends JPanel {

        public ContentPane(Color background) {
            setBackground(background);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(100, 100);
        }

    }

    public interface AccordionListener {

        public void accordionSelected(Component comp);

    }

    public class AccordionPanel extends JPanel {

        private JLabel title;
        private JPanel header;
        private Component content;

        private AccordionListener accordionListener;

        public AccordionPanel() {
            setLayout(new BorderLayout());

            title = new JLabel("Title");

            header = new JPanel(new FlowLayout(FlowLayout.LEADING));
            header.setBackground(Color.GRAY);
            header.setBorder(new LineBorder(Color.BLACK));
            header.add(title);
            add(header, BorderLayout.NORTH);

            header.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    AccordionListener listener = getAccordionListener();
                    if (listener != null) {
                        listener.accordionSelected(AccordionPanel.this);
                    }
                }
            });
        }

        public AccordionPanel(String title) {
            this();
            setTitle(title);
        }

        public AccordionPanel(String title, Component content) {
            this(title);
            setContentPane(content);
        }

        public void setAccordionListener(AccordionListener accordionListener) {
            this.accordionListener = accordionListener;
        }

        public AccordionListener getAccordionListener() {
            return accordionListener;
        }

        public void setTitle(String text) {
            title.setText(text);
            revalidate();
        }

        public String getText() {
            return title.getText();
        }

        public void setContentPane(Component content) {
            if (this.content != null) {
                remove(this.content);
            }

            this.content = content;
            if (this.content != null) {
                add(this.content);
            }
            revalidate();
        }

        public Component getContent() {
            return content;
        }

        @Override
        public Dimension getMinimumSize() {
            return header.getPreferredSize();
        }

        @Override
        public Dimension getPreferredSize() {
            Dimension size = content != null ? content.getPreferredSize() : super.getPreferredSize();
            Dimension min = getMinimumSize();
            size.width = Math.max(min.width, size.width);
            size.height += min.height;
            return size;
        }

    }

    public class AccordionLayout implements LayoutManager {

        // This "could" be controled by constraints, but that would assume
        // that more then one component could be expanded at a time
        private Component expanded;

        public void setExpanded(Component expanded) {
            this.expanded = expanded;
        }

        public Component getExpanded() {
            return expanded;
        }

        @Override
        public void addLayoutComponent(String name, Component comp) {
        }

        @Override
        public void removeLayoutComponent(Component comp) {
        }

        @Override
        public Dimension preferredLayoutSize(Container parent) {
            Dimension size = minimumLayoutSize(parent);
            if (expanded != null) {
                size.height -= expanded.getMinimumSize().height;
                size.height += expanded.getPreferredSize().height;
            }

            return size;
        }

        @Override
        public Dimension minimumLayoutSize(Container parent) {
            int height = 0;
            int width = 0;
            for (Component comp : parent.getComponents()) {
                width = Math.max(width, comp.getPreferredSize().width);
                height += comp.getMinimumSize().height;
            }
            return new Dimension(width, height);
        }

        @Override
        public void layoutContainer(Container parent) {

            Insets insets = parent.getInsets();
            int availableHeight = parent.getHeight() - (insets.top + insets.bottom);
            int x = insets.left;
            int y = insets.top;

            int maxSize = 0;
            Dimension minSize = minimumLayoutSize(parent);
            if (expanded != null) {
                minSize.height -= expanded.getMinimumSize().height;
                // Try an honour the preferred size the expanded component...
                maxSize = Math.max(expanded.getPreferredSize().height, availableHeight - minSize.height);
            }

            int width = parent.getWidth() - (insets.left + insets.right);
            for (Component comp : parent.getComponents()) {
                if (expanded != comp) {
                    comp.setSize(width, comp.getMinimumSize().height);
                } else {
                    comp.setSize(width, maxSize);
                }
                comp.setLocation(x, y);
                y += comp.getHeight();
            }

        }

    }

}

现在,如果您真的准备迎接挑战,您可以使用一些东西 a animated layout proxy 并做一些类似...

The end goal is to remove older panels when a new panel is added and there isn't enough room for it

我猜想在添加面板后,您将首选高度与实际高度进行比较。当首选高度更大时,您就会遇到问题,您会根据需要移除组件。

那么下一个问题是使用不改变面板高度的布局管理器。这仍然可以通过 GridBagLayout 完成。您只需要将 getMinimumSize() 方法覆盖到 return getPreferredSize() 维度。

Each part of the accordion can open individually and they're of arbitrary size and get added on the fly

您可能要考虑使用 Relative Layout。您可以添加其首选大小将得到尊重的组件。因此您将能够检查首选高度何时大于实际高度。

然后您还可以添加将根据面板中剩余的 space 数量调整大小的组件。这些将是您的扩展面板。

因此,在您的示例中,当您展开一个项目时,您可以将该组件配置为占用整个 space 可用空间。如果您展开两个项目,那么它们将各自获得一半的 space 可用空间。

也许是这样的:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ExpandingPanel extends JPanel
{
    private JPanel expanding;

    public ExpandingPanel(String text, Color color)
    {
        setLayout( new BorderLayout() );

        JButton button = new JButton( text );
        add(button, BorderLayout.NORTH);

        expanding = new JPanel();
        expanding.setBackground( color );
        expanding.setVisible( false );
        add(expanding, BorderLayout.CENTER);

        button.addActionListener( new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                expanding.setVisible( !expanding.isVisible() );

                Container parent = ExpandingPanel.this.getParent();
                LayoutManager2 layout = (LayoutManager2)parent.getLayout();

                if (expanding.isVisible())
                    layout.addLayoutComponent(ExpandingPanel.this, new Float(1));
                else
                    layout.addLayoutComponent(ExpandingPanel.this, null);

                revalidate();
                repaint();
            }
        });
    }

    private static void createAndShowGUI()
    {
        RelativeLayout rl = new RelativeLayout(RelativeLayout.Y_AXIS);
        rl.setFill( true );

        JPanel content = new JPanel( rl );
        content.add( new ExpandingPanel("Red", Color.RED) );
        content.add( new ExpandingPanel("Blue", Color.BLUE) );
        content.add( new ExpandingPanel("Green", Color.GREEN) );

        JFrame frame = new JFrame("Expanding Panel");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add( content);
        frame.setLocationByPlatform( true );
        frame.setSize(200, 300);
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
}