Implementing back/forward buttons in Swing
Asked Answered
P

3

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

}
Perique answered 13/4, 2011 at 20:2 Comment(5)
What is wrong with CardLayout?Kingston
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.Harl
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 :DPerique
@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.Escaut
Yep, I came to the same conclusion. It can't handle both directions unfortunatelyPerique
A
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);
    }
}
Animate answered 13/4, 2011 at 21:24 Comment(4)
See also this related example.Animate
setPreferredSize() used for demonstration purposes only.Animate
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?Heinrick
This example extends JPanel to add a name, but Component::getName is null until you invoke setName().Animate
I
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.

Interment answered 14/4, 2011 at 0:2 Comment(0)
C
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();
}
Clothespress answered 13/4, 2011 at 20:21 Comment(3)
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?Escaut
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.Clothespress
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 :(Perique

© 2022 - 2024 — McMap. All rights reserved.