Singleton with CardLayout won't show card when

2019-01-20 19:57发布

问题:

public class MainWindow extends JPanel {

public static MainWindow instance = new MainWindow();

private CardLayout cards = new CardLayout();

public MainWindow() {
    setLayout(cards);
    add(new FirstPage(), Pages.FIRST.toString());
    add(new SecondPage(), Pages.SECOND.toString());
    add(new ThirdPage(), Pages.THIRD.toString());
}

public void showPage(Pages page) {
    cards.show(this, page.toString());
}

}

the showPage(page); method works fine if I call it in the constructor of MainWindow. But when I try to call MainWindow.instance.showPage(Pages.SECOND); from an ActionListener in FirstPage nothing happens. I've checked that the showPage(page) method works correctly. I've checked that the ActionEvent is fired and enters the correct if/else clause. What am I doing wrong, why isn't my second page showing?

public class FirstPage extends JPanel {

    private JButton showSecond = new JButton("Show Second");
    private JButton showThird = new JButton("Show Third");

    public FirstPage() {
        insertButton(showSecond);
        insertButton(showThird);
    }

    private void insertButton(JButton button) {
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (e.getSource() == showSecond) {
                    MainWindow.instance.showPage(Pages.SECOND);
                } else {
                    MainWindow.instance.showPage(Pages.THIRD);
                }
            }
        });
        this.add(button);
    }
}

回答1:

It would suggest a reference issue. public static MainWindow instance = new MainWindow(); looks suspicious, as you would have had to create an instance of MainWindow first for it to be initialise which suggests you now have two instances of MainWindow, one on the screen and one that is not

Using static in this way is a bad idea, as it leads to issues like this. Instead you should pass a reference of the controller to the page. The controller would define the actions that each page could perform (and if done right, would be defined as an interface)

Alternatively, you could separate the navigation from the pages into a separate mechanism, this means the pages don't care and can simply displayed in any order you want or reused else where

Example #1 - Controller based pages

This examples defines a simple controller which the pages can call in order to effect the navigation of the pages

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class CardLayoutExample {

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

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

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

    public interface NavigationController {

        public void nextPage();

        public void previousPage();

        public void lastPage();

        public void firstPage();

    }

    public interface Page {

        public NavigationController getNavigationController();

        public JComponent getView();
        public String getName();

    }

    public class Wizard extends JPanel implements NavigationController {

        private List<Page> pages;
        private Page currentPage;

        private CardLayout cardLayout;

        public Wizard() {
            cardLayout = new CardLayout();
            pages = new ArrayList<>(25);
            setLayout(cardLayout);
            pages.add(new FirstPage("Page01", this));
            pages.add(new SecondPage("Page02", this));
            pages.add(new ThirdPage("Page03", this));

            for (Page page : pages) {
                add(page.getView(), page.getName());
            }

            firstPage();
        }

        @Override
        public void nextPage() {
            int index = pages.indexOf(currentPage);
            index++;
            if (index < pages.size()) {
                cardLayout.next(this);
                currentPage = pages.get(index);
            }
        }

        @Override
        public void previousPage() {
            int index = pages.indexOf(currentPage);
            index--;
            if (index >= 0) {
                cardLayout.previous(this);
                currentPage = pages.get(index);
            }
        }

        @Override
        public void lastPage() {
            Page page = pages.get(pages.size() - 1);
            showPage(page);
        }

        @Override
        public void firstPage() {
            Page page = pages.get(0);
            showPage(page);
        }

        protected void showPage(Page page) {
            cardLayout.show(this, page.getName());
            currentPage = page;
        }

    }

    public abstract class AbstractPage extends JPanel implements Page, ActionListener {

        private NavigationController navigationController;
        private JPanel buttons;
        private String name;

        public AbstractPage(String name, NavigationController navigationController) {
            this.name = name;
            this.navigationController = navigationController;
            setLayout(new BorderLayout());
            buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
            add(buttons, BorderLayout.SOUTH);
        }

        protected void insertButton(JButton button) {
            button.addActionListener(this);
            buttons.add(button);
        }

        @Override
        public NavigationController getNavigationController() {
            return navigationController;
        }

        @Override
        public JComponent getView() {
            return this;
        }

        @Override
        public String getName() {
            return super.getName(); 
        }

    }

    public class FirstPage extends AbstractPage implements Page {

        private JButton next = new JButton("Next >");

        public FirstPage(String name, NavigationController controller) {
            super(name, controller);
            JLabel label = new JLabel("First page");
            label.setHorizontalAlignment(JLabel.CENTER);
            add(label);
            insertButton(next);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == next) {
                getNavigationController().nextPage();
            }
        }

    }

    public class SecondPage extends AbstractPage implements Page {

        private JButton next = new JButton("Next >");
        private JButton previous = new JButton("< Previous");

        public SecondPage(String name, NavigationController controller) {
            super(name, controller);
            JLabel label = new JLabel("Second page");
            label.setHorizontalAlignment(JLabel.CENTER);
            add(label);
            insertButton(previous);
            insertButton(next);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == next) {
                getNavigationController().nextPage();
            } else if (e.getSource() == previous) {
                getNavigationController().previousPage();
            }
        }

    }

    public class ThirdPage extends AbstractPage implements Page {

        private JButton previous = new JButton("< Previous");

        public ThirdPage(String name, NavigationController controller) {
            super(name, controller);
            JLabel label = new JLabel("Third page");
            label.setHorizontalAlignment(JLabel.CENTER);
            add(label);
            insertButton(previous);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == previous) {
                getNavigationController().previousPage();
            }
        }

    }
}

Example #2 - central controller example

This example separates the controller from the pages, so that the buttons are not part of the pages themselves. This frees up the pages/views to be anything you need them to be

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;

public class CardLayoutExample2 {

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

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

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

    public class WizardPane extends JPanel {

        private List<String> pages;
        private String currentPage;

        private JButton first;
        private JButton previous;
        private JButton next;
        private JButton last;

        private CardLayout cardLayout;
        private JPanel contentPane;

        public WizardPane() {
            setLayout(new BorderLayout());
            cardLayout = new CardLayout();
            pages = new ArrayList<>(3);

            contentPane = new JPanel(cardLayout);
            contentPane.setBorder(new EmptyBorder(4, 4, 4, 4));

            pages.add("Page01");
            pages.add("Page02");
            pages.add("Page03");

            contentPane.add(new FirstPage(), "Page01");
            contentPane.add(new SecondPage(), "Page02");
            contentPane.add(new ThirdPage(), "Page03");

            JPanel actionsPane = new JPanel(new GridBagLayout());
            actionsPane.setBorder(new EmptyBorder(4, 4, 4, 4));
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            actionsPane.add((first = new JButton("<< First")), gbc);
            gbc.gridx++;
            gbc.weightx = 1;
            gbc.anchor = GridBagConstraints.WEST;
            actionsPane.add((previous = new JButton("< Previous")), gbc);

            gbc.gridx++;
            gbc.anchor = GridBagConstraints.EAST;
            actionsPane.add((next = new JButton("Next >")), gbc);
            gbc.gridx++;
            gbc.weightx = 0;
            actionsPane.add((last = new JButton("Last >>")), gbc);

            add(contentPane);
            add(actionsPane, BorderLayout.SOUTH);

            NavigationHandler handler = new NavigationHandler();
            first.addActionListener(handler);
            previous.addActionListener(handler);
            next.addActionListener(handler);
            last.addActionListener(handler);

            gotoFirstPage();
        }

        protected void gotoFirstPage() {
            currentPage = pages.get(0);
            cardLayout.show(contentPane, currentPage);
        }

        protected void gotoPreviousPage() {
            int index = pages.indexOf(currentPage);
            index--;
            if (index >= 0) {
                currentPage = pages.get(index);
                cardLayout.show(contentPane, currentPage);
            }
        }

        protected void gotoNextPage() {
            int index = pages.indexOf(currentPage);
            index++;
            if (index < pages.size()) {
                currentPage = pages.get(index);
                cardLayout.show(contentPane, currentPage);
            }
        }

        protected void gotoLastPage() {
            currentPage = pages.get(pages.size() - 1);
            cardLayout.show(contentPane, currentPage);
        }

        protected class NavigationHandler implements ActionListener {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (e.getSource() == first) {
                    gotoFirstPage();
                } else if (e.getSource() == previous) {
                    gotoPreviousPage();
                } else if (e.getSource() == next) {
                    gotoNextPage();
                } else if (e.getSource() == last) {
                    gotoLastPage();
                }
            }
        }

    }

    public class FirstPage extends JPanel {

        public FirstPage() {
            setLayout(new BorderLayout());
            JLabel label = new JLabel("Page One");
            label.setHorizontalAlignment(JLabel.CENTER);
            add(label);
        }

    }

    public class SecondPage extends JPanel {

        public SecondPage() {
            setLayout(new BorderLayout());
            JLabel label = new JLabel("Page Two");
            label.setHorizontalAlignment(JLabel.CENTER);
            add(label);
        }

    }

    public class ThirdPage extends JPanel {

        public ThirdPage() {
            setLayout(new BorderLayout());
            JLabel label = new JLabel("Page Three");
            label.setHorizontalAlignment(JLabel.CENTER);
            add(label);
        }

    }
}

Example #3 - Model based

Or you could use a model based approach (which is probably more preferable), which defines the order in which components are displayed. For example