10

I have a quick question.

I'm getting a little bit of experience with Swing and the easiest way to do this was to draw up a reasonably big GUI.

As part of the GUI, I want to have Forward and Back Buttons. The Approach I'm trying to take is to implement methods that will push the current JPanel to a stack and retrieve the previous value (Be that in a forwards or reverse direction (hence 2 stacks)). I can't get it to work though. Perhaps I'm going about it completely the wrong way or maybe a stack can't be used int the way I'm using it. In either case, it's really bugging me. I imagine there are probably easier ways like a card layout but I think this approach should work and that's what's so annoying.

It may be worth noting that I'm using a JFrame "base class" and changing the central JPanel depending on the screen. The nav bar is constant as a part of the "base class" however

The code of this "base class":

public class Main_Frame extends JFrame{
    static JPanel nav_bar_panel;
    JButton home;
    JButton back;
    JButton forward;
    JPanel currentPanel;

    static Stack<JPanel> previousPanels;
    static Stack<JPanel> forwardPanels;

    public Main_Frame(){
        super("DEMO");
        setSize(800,600);
        setLayout(new BorderLayout());
        setVisible(true);

        add(nav_bar(), BorderLayout.NORTH);
        currentPanel = init_display();
        add(currentPanel, BorderLayout.CENTER);

        previousPanels = new Stack<JPanel>();
        forwardPanels  = new Stack<JPanel>();
    }

    private JPanel nav_bar(){
        ButtonPressHandler handler = new ButtonPressHandler();

        nav_bar_panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
        back = new JButton("Back");
        back.addActionListener(handler);
        home = new JButton("Home");
        home.addActionListener(handler);
        forward = new JButton("Forward");
        forward.addActionListener(handler);

        nav_bar_panel.add(back);
        nav_bar_panel.add(home);
        nav_bar_panel.add(forward);

        return nav_bar_panel;
    }

    private JPanel init_display(){
        Home_Panel home_panel = new Home_Panel();

        return home_panel;
    }

    public void change_display(JPanel myPanel){
        invalidate();
        remove(currentPanel);
        previousPanels.push(currentPanel);
        currentPanel = myPanel;
        add(currentPanel);
        validate();
    }

    public void previous_display(){
        if(!previousPanels.empty()){
            invalidate();
            remove(currentPanel);
            forwardPanels.push(currentPanel);
            currentPanel = previousPanels.pop();
            add(currentPanel);
            validate();
        }
    }

    public void forward_display(){
        if(!forwardPanels.empty()){
            invalidate();
            remove(currentPanel);
            previousPanels.push(currentPanel);
            currentPanel = forwardPanels.pop();
            add(currentPanel);
            validate();
        }
    }

    private class ButtonPressHandler implements ActionListener 
       {
          public void actionPerformed( ActionEvent event )
          {
              if(event.getSource() == back){
                  previous_display();
                  System.out.print("You selected back");
              } else if(event.getSource() == forward){
                  forward_display();
                  System.out.print("You selected forward");
              }
          } // end method actionPerformed
       } // end private inner class TextFieldHandler

}
stephenfin
  • 1,447
  • 3
  • 20
  • 41
  • 2
    What is wrong with `CardLayout`? – khachik Apr 13 '11 at 20:08
  • Instead of using two stacks, why not use a `List` containing all panels? Maintain an index pointing to the panel currently being shown. If the index is 0 then there are no previous panels. If it is size() - 1 then there are no next panels. This is of course assuming you want to create your own solution instead of using a `CardLayout`. – Jonas Kongslund Apr 13 '11 at 20:19
  • Sounds good Jonas. I will probably switch to cardlayout once I get this going. I couldn't rest in the knowledge that I couldn't figure it out though :D – stephenfin Apr 13 '11 at 20:24
  • @Jonas - I dont think that would work because it is assuming a constantly linear sequence of screen changes, but depending on any links in the GUI you might be able to go in loops and circles before getting to your screen and when I use a "Back" button I expect it to follow the same path since I presumably followed it myself for a good reason. – gnomed Apr 13 '11 at 20:29
  • Yep, I came to the same conclusion. It can't handle both directions unfortunately – stephenfin Apr 13 '11 at 20:54

3 Answers3

15

Here's an example using CardLayout.

enter image description here

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

/** @see http://stackoverflow.com/questions/5654926 */
public class CardPanel extends JPanel {

    private static final Random random = new Random();
    private static final JPanel cards = new JPanel(new CardLayout());
    private final String name;

    public CardPanel(String name) {
        this.name = name;
        this.setPreferredSize(new Dimension(320, 240));
        this.setBackground(new Color(random.nextInt()));
        this.add(new JLabel(name));
    }

    @Override
    public String toString() {
        return name;
    }

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

            @Override
            public void run() {
                create();
            }
        });
    }

    private static void create() {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        for (int i = 1; i < 9; i++) {
            CardPanel p = new CardPanel("Panel " + String.valueOf(i));
            cards.add(p, p.toString());
        }
        JPanel control = new JPanel();
        control.add(new JButton(new AbstractAction("\u22b2Prev") {

            @Override
            public void actionPerformed(ActionEvent e) {
                CardLayout cl = (CardLayout) cards.getLayout();
                cl.previous(cards);
            }
        }));
        control.add(new JButton(new AbstractAction("Next\u22b3") {

            @Override
            public void actionPerformed(ActionEvent e) {
                CardLayout cl = (CardLayout) cards.getLayout();
                cl.next(cards);
            }
        }));
        f.add(cards, BorderLayout.CENTER);
        f.add(control, BorderLayout.SOUTH);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • 5
    See also this related [example](http://stackoverflow.com/questions/6432170/how-to-change-ui-depending-on-combo-box-selection/6432291#6432291). – trashgod Jun 22 '11 at 20:43
  • [`setPreferredSize()`](http://stackoverflow.com/q/7229226/230513) used for demonstration purposes only. – trashgod Jul 27 '14 at 11:05
  • Hey man. I'm using your code. It's nice, but I struggle to set the frame title on every pressing navigation button. Below `cl.previous(cards);` I put `System.out.println("card name: " + (cards.getName()));` but it returns null. How to reference this? – Peter.k May 16 '17 at 21:50
  • This example extends `JPanel` to add a `name`, but [`Component::getName`](http://docs.oracle.com/javase/8/docs/api/java/awt/Component.html) is `null` until you invoke `setName()`. – trashgod May 17 '17 at 00:06
7

the idea of making whatever I get reusable is a good one. Pity Swing didn't have this functionality built in though

Check out Card Layout Actions which is may attempt at making the Card Layout a little easier to use for something like this.

camickr
  • 321,443
  • 19
  • 166
  • 288
2

The way I usually do it is as follows:

  1. I've got a StepManager class (write it once, use it forever) which handles all logic related to the steps. It got methods like next(), previous(), reset(), isFirst() and isLast().

  2. I've then got 'Next' and 'Previous' buttons with appropriate actions (or whatever you choose to use to listen for user interaction).

  3. The code related to the 'Next' button calls stepManager.next() to retrieve the index for the next step. Then (when I've got the next step) I simply invoke (another method) showStep(int index) to display the actual step user interface corresponding to the current step index.

Each step is a separate JPanel (Step01, Step02, Step03...).

public void showStep(int index) {
    ContentPanel.removeAll();
    ContentPanel.setLayout(new BorderLayout());

    switch (index) {
        case 0:
            ContentPanel.add(Step01, BorderLayout.CENTER);
            break;

        case 1:
            ContentPanel.add(Step02, BorderLayout.CENTER);
            break;

        case 2:
            ContentPanel.add(Step03, BorderLayout.CENTER);
            break;

        case 3:
            ContentPanel.add(Step04, BorderLayout.CENTER);

        }

    ContentPanel.validate();
    ContentPanel.repaint();
}
sbrattla
  • 5,274
  • 3
  • 39
  • 63
  • I think this might need a little clarification. For instance, how can you use a switch statement if there is a dynamically expanding number of steps to keep track of? And how are steps added to your StepManager class? – gnomed Apr 13 '11 at 20:27
  • My assumption is that the number of steps is known (the original question did not mention a growing number of steps). The idea behind it was just that you isolate all logic related to step management. Anyway, my suggestion would not be able to accommodate dynamic step logic where user input determines the path. You could certainly have the StepManager return the JPanel corresponding to the current step (provide all steps to the StepManager through the constructor). I guess that would be a question of how one choose to implement the StepManager. – sbrattla Apr 13 '11 at 20:40
  • While the approach above won't quite do for my purposes, the idea of making whatever I get reusable is a good one. Pity Swing didn't have this functionality built in though :( – stephenfin Apr 13 '11 at 20:57