Как я могу решить эти проблемы с размером с помощью JToggleButtons, JPanels и JScrollPanes?

Я пытаюсь разработать форму аккордеонного меню. Есть небольшое количество (2-12) опций, которые можно включать/выключать. При включении появится JPanel с дополнительными настройками, которые станут видимыми. При отключении дополнительные настройки не будут видны.

Я создал класс SelectableExpandablePanel, который расширяет JPanel и реализует ActionListener и ComponentListener. Панель содержит две вещи — JToggleButton и дочернюю Component (которая обычно будет JPanel, но я не хочу ограничивать себя повторным использованием этой концепции в будущем) в BoxLayout для принудительного применения одного столбца. При выборе переключателя дочерний элемент становится видимым. Когда переключатель снят, дочерний элемент скрыт.

Когда я использую этот компонент, я намереваюсь поместить его на JPanel внутри JScrollPane, как показано в примере основного метода.

Кажется, есть две проблемы, с которыми мне трудно справиться:

  1. Если я не укажу размер JFrame, он будет достаточно большим для ширины каждого дочернего элемента и достаточно высоким для трех кнопок. Когда я нажимаю кнопку, я ожидаю, что JScrollPane сделает свое дело и создаст вертикальную полосу прокрутки. Этого не происходит.

  2. Я бы хотел, чтобы кнопки переключения были на всю ширину JPanel, которая их содержит. Я думал, что то, что я сделал в конструкторе плюс Component Listener, справится с этим, но это не так.

То, что представлено ниже, компилируется и имеет метод main. Если он скомпилирован и выполнен, он управляет компонентом, который я создаю, чтобы предоставить тестовый кадр и возможность воспроизвести проблемы, о которых я говорю.

import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;

public class SelectableExpandablePanel extends JPanel implements
        ActionListener, ComponentListener {
    private JToggleButton titleButton;
    private JComponent childComponent;

    public SelectableExpandablePanel(JComponent child) {
        this(child, null, null);
    }

    public SelectableExpandablePanel(JComponent child, String title) {
        this(child, title, null);
    }

    public SelectableExpandablePanel(JComponent child, String title,
            String tooltip) {
        super();

        if (child == null) {
            throw new IllegalArgumentException("Child component cannot be null");
        }

        childComponent = child;

        titleButton = new JToggleButton();
        titleButton.setText(title);
        titleButton.addActionListener(this);
        titleButton.setPreferredSize(new Dimension(getSize().width, titleButton
                .getPreferredSize().height));
        titleButton.setToolTipText(tooltip);

        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        setPreferredSize(new Dimension(childComponent.getPreferredSize().width,
                titleButton.getPreferredSize().height));
        setSize(new Dimension(childComponent.getPreferredSize().width,
                titleButton.getPreferredSize().height));

        add(titleButton);

        this.addComponentListener(this);
    }

    public void actionPerformed(ActionEvent e) {
        if (titleButton.isSelected()) {
            add(childComponent);
            setSize(new Dimension(childComponent.getPreferredSize().width,
                    titleButton.getPreferredSize().height
                            + childComponent.getPreferredSize().height));

        } else {
            remove(childComponent);
            setSize(new Dimension(childComponent.getPreferredSize().width,
                    titleButton.getPreferredSize().height));
        }

        invalidate();
        revalidate();
    }

    public void componentHidden(ComponentEvent arg0) {
        // Do nothing
    }

    public void componentMoved(ComponentEvent arg0) {
        // Do nothing
    }

    public void componentResized(ComponentEvent arg0) {
        titleButton.setSize(this.getWidth(),
                titleButton.getPreferredSize().height);
    }

    public void componentShown(ComponentEvent arg0) {
        // Do nothing
    }

    public static void main(String[] args) {
        JScrollPane scrollPane = new JScrollPane();

        // These panels simulates a complex, multi-line configuration panel.
        JPanel testPanel = new JPanel();
        testPanel.setLayout(new BoxLayout(testPanel, BoxLayout.Y_AXIS));
        testPanel.add(new JLabel("Test JLabel"));
        testPanel.add(new JLabel("Test JLabel 2"));
        testPanel.add(new JLabel("Test JLabel 3"));

        JPanel testPanel2 = new JPanel();
        testPanel2.setLayout(new BoxLayout(testPanel2, BoxLayout.Y_AXIS));
        testPanel2.add(new JLabel("Test JLabel"));
        testPanel2.add(new JLabel("Test JLabel 2"));
        testPanel2.add(new JLabel("Test JLabel 3"));

        JPanel testPanel3 = new JPanel();
        testPanel3.setLayout(new BoxLayout(testPanel3, BoxLayout.Y_AXIS));
        testPanel3.add(new JLabel("Test JLabel"));
        testPanel3.add(new JLabel("Test JLabel 2"));
        testPanel3.add(new JLabel("Test JLabel 3"));

        // This panel simulates the panel that will contain each of the
        // SelectableExpandablePanels.
        JPanel testHolder = new JPanel();
        testHolder.setLayout(new BoxLayout(testHolder, BoxLayout.Y_AXIS));
        testHolder.add(new SelectableExpandablePanel(testPanel, "Test"));
        testHolder.add(new SelectableExpandablePanel(testPanel2, "Test 2"));
        testHolder.add(new SelectableExpandablePanel(testPanel3, "Test 3"));

        // We add the test holder to the scroll pane. The intention is that if
        // the expansion is too big to fit, the holding JFrame won't expand, but
        // the scroll pane will get scroll bars to let the user scroll up and
        // down through the toggle buttons and any enabled items.
        scrollPane.setViewportView(testHolder);

        JFrame testFrame = new JFrame("Expandable Panel Test");
        testFrame.getContentPane().add(scrollPane);
        testFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        testFrame.pack();
        testFrame.setVisible(true);
    }
}

person Thomas Owens    schedule 21.04.2015    source источник
comment
DYM или DYM, все зависит от того, что, как, когда дочерние элементы JFrames (да или нет) изменяются   -  person mKorbel    schedule 21.04.2015
comment
@mKorbel Позвольте мне взглянуть на них. При быстром рассмотрении кажется, что лучше использовать setVisible вместо добавления или удаления. Мне нужно копать глубже для проблем с панелью прокрутки и проблем с шириной.   -  person Thomas Owens    schedule 21.04.2015
comment
использование setVisible вместо добавления или удаления, вероятно, было бы лучше ---› не имеет значения внутри JScrollPane, это касается моего вопроса к вам   -  person mKorbel    schedule 21.04.2015
comment
@mKorbel Я не знаю, о чем ты спрашиваешь. Размер дочерних панелей можно изменять, да.   -  person Thomas Owens    schedule 21.04.2015
comment
все зависит от того, что, как, когда (чего вы ожидаете) дети JFrames (если да или нет) изменяются, тогда ответ проще   -  person mKorbel    schedule 21.04.2015
comment
обратите внимание, что JScolPane не знает, возвращает свой getPreferredSize обратно в контейнер, LayoutManager использует свои значения по умолчанию, должен переопределить getPreferredSize или нет setPreferredSize против каких-либо правил или хороших привычек   -  person mKorbel    schedule 21.04.2015
comment
@mKorbel Это может объяснить то, что я вижу. После внесения изменений, предложенных копег, почти все работает так, как я предполагал. Однако панель имеет только ширину кнопки. Когда дочерний компонент широкий, панель должна быть такой же ширины, как и дочерний компонент, чтобы исключить горизонтальную прокрутку.   -  person Thomas Owens    schedule 21.04.2015
comment
BoxLayout принимает все три ограничения: минимальный, максимальный и предпочтительный размер, это одна из вещей, которые мне не хватает в ответе camickrs здесь   -  person mKorbel    schedule 21.04.2015


Ответы (2)


Не пытайтесь управлять размерами самостоятельно:

//titleButton.setPreferredSize(new Dimension(getSize().width, titleButton.getPreferredSize().height));
 titleButton.setToolTipText(tooltip);

 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
//setPreferredSize(new Dimension(childComponent.getPreferredSize().width, titleButton.getPreferredSize().height));
//setSize(new Dimension(childComponent.getPreferredSize().width, titleButton.getPreferredSize().height));

Кроме того, избавьтесь от кода setSize() в ActionListener. Это все равно будет проигнорировано, так как менеджер компоновки определит размер.

Полосы прокрутки появятся, когда предпочтительный размер панели больше, чем размер области прокрутки. Если вы жестко задаете предпочтительный размер, то вы по умолчанию используете менеджер компоновки, и предпочтительный размер не изменится при добавлении/удалении компонентов.

Обратите внимание, что для чего-то подобного я обычно использую BorderLayout. Поместите кнопку на PAGE_START, а другую панель в ЦЕНТР. Компоненты автоматически заполнят доступное пространство.

person camickr    schedule 21.04.2015
comment
Не могли бы вы указать предпочтительный метод изменения размера панели прокрутки? На данный момент это мой основной метод. Я думаю, что столкнусь с этой проблемой, когда буду использовать этот компонент. Если я добавлю его в область прокрутки, как я сделал в основном методе примера, область прокрутки будет шириной с кнопки, которые в некоторых случаях намного уже, чем дочерние компоненты. Мне нужно, чтобы панель прокрутки была шириной самого широкого дочернего компонента. Что было бы лучшим подходом, чтобы справиться с этим? - person Thomas Owens; 21.04.2015
comment
@ThomasOwens, если вам нужно управлять предпочтительным размером вашей панели, переопределите метод getPreferredSize() вашего класса. Таким образом, предпочтительный размер будет рассчитываться динамически при каждом вызове менеджера компоновки. - person camickr; 21.04.2015

  1. Удалите все вызовы setSize/setPreferredSize и позвольте LayoutManager делать свое дело.
  2. Чтобы позволить JButtons заполнить всю ширину панели, вы можете использовать BorderLayout (например, добавить кнопку в CENTER, а дочерний контейнер в SOUTH и удалить все эти значения setSize, чтобы позволить LayoutManager обработать их).
person copeg    schedule 21.04.2015
comment
Это потрясающе. Я думаю. Мне нужно протестировать больше, но это похоже на ту функциональность, которая мне нужна. - person Thomas Owens; 21.04.2015
comment
Не используйте setPreferredSize(), это работа менеджера компоновки. - person camickr; 21.04.2015
comment
Единственная проблема заключается в том, что если я удалю setSize/setPreferredSize, размер SelectableExpandablePanel будет только шириной кнопок. Ширина должна быть шириной самого широкого дочернего компонента, так как я хотел бы предотвратить горизонтальную прокрутку. Кроме этого, все работает хорошо. - person Thomas Owens; 21.04.2015
comment
согласен с уведомлением о setPreferredSize(), но для JScrollpane будут другие ситуации (не упомянутые в его ответе) - person mKorbel; 21.04.2015