Java Swing: объединить эффекты CardLayout и JLayeredPane

Я пытаюсь разместить несколько JPanel один над другим, полностью перекрывая друг друга. Я использую JLayeredPane, чтобы они были разной «глубины», поэтому я могу изменить глубину и непрозрачность, чтобы увидеть определенную панель «под» другой. Вот небольшой тест, который я сделал, который работает правильно:

    public class LayeredCardPanel extends JPanel  {

        private static final String BLUE_PANEL     =  "blue   ";
        private static final String RED_PANEL      =  "red     ";
        private static final String GREEN_PANEL    =  "green";

        private String[] panelNames = { BLUE_PANEL, RED_PANEL, GREEN_PANEL };
        private Color[] panelColors = { Color.BLUE, Color.RED,  Color.GREEN };

        private List<JPanel> panels = new ArrayList<>();

        private final int TOP_POSITION = 30;
        private static final int PANELS_FIRST_POS = 10;

        private JLayeredPane layeredPane;


        public LayeredCardPanel()    {

            setLayout(new BoxLayout(this, BoxLayout.X_AXIS));

            add(createControlPanel());

            layeredPane = new JLayeredPane();

            ////////////////////////////////////////////////////////////////
            //setting layout here results in all grey, non functioning panel.
            //layeredPane.setLayout(new CardLayout(0, 0));
            //////////////////////////////////////////////////////////////

            add(layeredPane);

            //adding 3 panel
            for (int i = 0; i < panelNames.length; i++) {

                JPanel panel = new JPanel();
                panel.setBackground(panelColors[i]);

                layeredPane.add(panel);
                layeredPane.setLayer(panel, PANELS_FIRST_POS + i);

                panels.add(panel);
            }

            ////////////////////////////////////////////////////////////
            //setting the card here, after adding panels, works fine
            layeredPane.setLayout(new CardLayout(0, 0));
            //////////////////////////////////////////////////////////
        }

Результат теста выглядит следующим образом:

Как вы можете видеть между строкой комментария //////, этот тест работает нормально, только если я устанавливаю CardLayout в JLayeredPane после добавления к нему JPanel. Мне кажется, что JPanel добавляются к JLayeredPane с помощью менеджера компоновки по умолчанию. Макет (установка и изменение границ для заполнения JLayeredPane) выполняется с помощью CardLayout, примененного позже.

Мой вопрос: хотя это работает так, как мне нужно, решение (использование одного менеджера компоновки для добавления, а затем замена его для получения нужного макета) не выглядит элегантным решением. Я ищу лучшие способы сделать это.


Вот и весь SSCE:

    public class LayeredCardPanel extends JPanel  {

        private static final String BLUE_PANEL     =  "blue   ";
        private static final String RED_PANEL      =  "red     ";
        private static final String GREEN_PANEL    =  "green";

        private String[] panelNames = { BLUE_PANEL, RED_PANEL, GREEN_PANEL };
        private Color[] panelColors = { Color.BLUE, Color.RED,  Color.GREEN };

        private List<JPanel> panels = new ArrayList<>();

        private final int TOP_POSITION = 30;
        private static final int PANELS_FIRST_POS = 10;

        private JLayeredPane layeredPane;

        public LayeredCardPanel()    {

            setLayout(new BoxLayout(this, BoxLayout.X_AXIS));

            add(createControlPanel());

            layeredPane = new JLayeredPane();

            add(layeredPane);

            //add 3 panel
            for (int i = 0; i < panelNames.length; i++) {

                JPanel panel = new JPanel();
                panel.setBackground(panelColors[i]);

                layeredPane.add(panel);
                layeredPane.setLayer(panel, PANELS_FIRST_POS + i);

                panels.add(panel);
            }

            layeredPane.setLayout(new CardLayout());
        }

        private JPanel createControlPanel() {

            ActionListener aListener = new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {

                    if(e.getSource() instanceof JButton ) {

                        moveToTop(((JButton) e.getSource()).getActionCommand() );
                    }
                }
            };

            JPanel controls = new JPanel();
            controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS));

            JButton blueViewBtn = new JButton(BLUE_PANEL);
            blueViewBtn.setActionCommand(BLUE_PANEL);
            blueViewBtn.addActionListener(aListener);
            controls.add(blueViewBtn);

            JButton redViewBtn = new JButton(RED_PANEL);
            redViewBtn.setActionCommand(RED_PANEL);
            redViewBtn.addActionListener(aListener);
            controls.add(redViewBtn);

            JButton greenViewBtn = new JButton(GREEN_PANEL);
            greenViewBtn.setActionCommand(GREEN_PANEL);
            greenViewBtn.addActionListener(aListener);
            controls.add(greenViewBtn);

            return controls;
        }

        private void moveToTop(String panelName) {

            for(int i = 0; i < panelNames.length; i++) {

                if(panelNames[i].equals(panelName)) {
                    layeredPane.setLayer(panels.get(i),TOP_POSITION);
                } else {
                    layeredPane.setLayer(panels.get(i), PANELS_FIRST_POS + i);
                }
            }
        }

        public static void main(String[] args) {

            javax.swing.SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {

                    JFrame frame = new JFrame("Layered Card Panel Simulation");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                    JComponent newContentPane = new LayeredCardPanel();
                    newContentPane.setPreferredSize(new Dimension(300,100));
                    frame.setContentPane(newContentPane);

                    frame.pack();
                    frame.setVisible(true);
                }
            });
        }
     }

person c0der    schedule 03.03.2016    source источник
comment
As you can see - нет, мы не можем видеть, потому что ваш код не является исполняемым. Опубликуйте правильный SSCCE, демонстрирующий проблему при публикации кода.   -  person camickr    schedule 03.03.2016
comment
Как вы можете видеть между ////// : да, вы можете это видеть. Это в коде. Я загрузил минимальный код, чтобы продемонстрировать проблему. SSCE намного длиннее.   -  person c0der    schedule 03.03.2016


Ответы (2)


В основном вы НЕ используете CardLayout. Ваш код будет работать, даже если у вас нет оператора setLayout(...).

Когда вы используете CardLayout:

  1. Менеджер компоновки может отслеживать карты, добавленные только после того, как вы настроили менеджер компоновки.
  2. одновременно отображается только один компонент, что вам не нужно, так как вы хотите играть с прозрачностью панели

Редактировать:

Когда вы устанавливаете CardLayout ПОСЛЕ того, как панели добавляются в многоуровневую панель, они НЕ обрабатываются CardLayout. В результате вся панель остается видимой. По-видимому, все, что происходит, это то, что CardLayout устанавливает размер каждой панели, чтобы заполнить доступное пространство, поэтому рисование работает правильно.

Когда я изменяю ваш SSCCE, чтобы установить CardLayout ПЕРЕД добавлением панелей, тогда CardLayout управляет панелями. Таким образом, CardLayout вызывает setVisible(false) на всех панелях, кроме первой добавленной панели, которая в SSCCE является синей панелью. Таким образом, вы всегда будете видеть только синюю панель, даже если попытаетесь переместить разные панели на передний план.

Это легко проверить, внеся следующие изменения в SSCCE:

layeredPane.setLayer(panel, PANELS_FIRST_POS + i);
System.out.println(panel.isVisible());

Так что да, использование CardLayout - это действительно хак, а не способ использования CardLayout или способ использования JLayeredPane.

JLayeredPane действительно предназначен для использования с нулевым макетом, что означает, что вы несете ответственность за установку размера каждого компонента. Для лучшего решения я бы посоветовал вам добавить ComponentListener в многоуровневую панель. Затем вы должны обработать событие componentResized(...). Затем вы перебираете все компоненты, добавленные в многоуровневую панель, и устанавливаете размер каждого компонента равным размеру многоуровневой панели.

person camickr    schedule 03.03.2016
comment
Спасибо @camickr. Я также думал, что код будет работать без шины setLayout(...), но это не так. Он отображает серую область содержимого. Также: возможно иметь прозрачную карту в макете карты. - person c0der; 03.03.2016
comment
@OferYuval, на основе SSCCE, я внес правку, которая содержит лучшее описание того, что происходит, и возможное решение. it is possible to have a transparent card in card layout. - конечно, но одновременно видна только одна карта, так что вы не получите эффекта нескольких прозрачных карт, наложенных друг на друга. - person camickr; 04.03.2016
comment
Спасибо @camickr за подробный ответ, который объясняет, что происходит. Я опубликую здесь другое решение, основанное на применении подкласса Cardlayout к llayeredPane. P.S: С помощью CardLayout можно получить эффект нескольких прозрачных карточек друг над другом. - person c0der; 04.03.2016
comment
It is possible to "get the effect of multiple transparent cards on top of one another" using CardLayout. - уже ответил дважды. Один раз в моем первоначальном ответе и один раз в моем комментарии. I will post here another solution which is based on applying a subclass of Cardlayout - вам не следует использовать CardLayout по причинам, указанным в моем ответе выше. CardLayout предназначен для отображения ОДНОЙ панели за раз. Вам нужен макет стека, который отображает все панели и позволяет изменять порядок панелей. - person camickr; 04.03.2016

Я применил следующее решение: я изменил подрядчика, поэтому layeredPane использует пользовательский менеджер компоновки Layout() :

        public LayeredCardPanel()    {

            setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
            add(createControlPanel());

            layeredPane = new JLayeredPane();
            layeredPane.setLayout(new Layout());
            add(layeredPane);

            //add 3 panel
            for (int i = 0; i < panelNames.length; i++) {

                JPanel panel = new JPanel();
                panel.setBackground(panelColors[i]);

                layeredPane.add(panel);
                layeredPane.setLayer(panel, PANELS_FIRST_POS + i);

                panels.add(panel);
            }
        }

Layout() расширяет CardLayout, переопределяя addLayoutComponent, чтобы ничего не делать:

    class Layout extends CardLayout{

        @Override
        public void addLayoutComponent(String name, Component comp) {
            // override to do nothing
        }
    }
person c0der    schedule 04.03.2016