In my program I have a wizard based layout. Implemented by CardLayout. So there is a set of classes that extend JPanels. I want to have buttons in each panel to navigate to other panels. fro example, when the program is showing panel one, I want to have a button to show panel 2.
I tired to create a method in main cardlayout panel holder so any other class can change the showing panel by this method, but it does not works and a stackoverflow error come up.
Here are my classes
Base Frame:
public class Base {
JFrame frame = new JFrame("Panel");
BorderLayout bl = new BorderLayout();
public Base(){
frame.setLayout(bl);
frame.setSize(800, 600);
frame.add(new LeftBar(), BorderLayout.WEST);
frame.add(new MainPanel(), BorderLayout.CENTER);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
new Base();
}
}
Main class that holds sub panels:
public class MainPanel extends JPanel {
private CardLayout cl = new CardLayout();
private JPanel panelHolder = new JPanel(cl);
public MainPanel() {
NewSession session = new NewSession();
ChooseSource chooseSource = new ChooseSource();
panelHolder.add(session, "Session");
panelHolder.add(chooseSource, "ChooseSource");
cl.show(panelHolder, "Session");
add(panelHolder);
}
public void showPanel(String panelIdentifier){
cl.show(panelHolder, panelIdentifier);
}
}
Sub panel 1
public class NewSession extends JPanel {
MainPanel ob2 = new MainPanel();
public NewSession(){
JButton newSessionBTN = new JButton("Create A New Session");
newSessionBTN.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
System.out.println("HI");
ob2.showPanel("ChooseSource");
}
});
add(newSessionBTN);
}
}
Sub panel 2
public class ChooseSource extends JPanel {
public ChooseSource(){
JLabel showMe = new JLabel("Show Me");
JButton back = new JButton("Back");
//MainPanel ob = new MainPanel();
back.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
//ob.showPanel("start");
}
});
add(back);
add(showMe);
}
}
As you can see I have button in each sub panel and those buttons must show the other panel after clicking. In later they will also transfer the data from one to another.
ERROR:
Exception in thread "main" java.lang.StackOverflowError
at java.awt.Component.setFont(Component.java:1899)
at java.awt.Container.setFont(Container.java:1748)
at javax.swing.JComponent.setFont(JComponent.java:2751)
at javax.swing.LookAndFeel.installColorsAndFont(LookAndFeel.java:208)
at javax.swing.plaf.basic.BasicPanelUI.installDefaults(BasicPanelUI.java:66)
at javax.swing.plaf.basic.BasicPanelUI.installUI(BasicPanelUI.java:56)
at javax.swing.JComponent.setUI(JComponent.java:663)
at javax.swing.JPanel.setUI(JPanel.java:153)
at javax.swing.JPanel.updateUI(JPanel.java:126)
at javax.swing.JPanel.<init>(JPanel.java:86)
at javax.swing.JPanel.<init>(JPanel.java:109)
at javax.swing.JPanel.<init>(JPanel.java:117)
at InnerPanels.NewSession.<init>(NewSession.java:21)
at StrongBaseLayout.MainPanel.<init>(MainPanel.java:22)
The error is longer than this, by repeating last two lines.
How can I make it working?
Also I had another idea to have a next and previous buttons at the bottom of the page to switch panels. But am not sure which one is optimal. Any idea?
Whenever you see an unexpected StackOverflowError always look for the presence of inadvertent recursion, and in fact, that's exactly what you have going on here since MainPanel creates a NewSession object which then creates a new MainPanel object which then creates a new NewSession object which then creates a new MainPanel object .... repeating ad infinitum or until stack memory (hence the stack overflow) runs out.
here:
public class NewSession extends JPanel {
MainPanel ob2 = new MainPanel(); // *****
and here:
public class MainPanel extends JPanel {
private CardLayout cl = new CardLayout();
private JPanel panelHolder = new JPanel(cl);
public MainPanel() {
NewSession session = new NewSession(); // *****
Don't do that. Instead take care to create one and only one of each object. Use setter methods or constructor parameters to help you do this.
For example, change to this:
public class NewSession extends JPanel {
MainPanel ob2;
NewSession(MainPanel mainPanel) {
this.ob2 = mainPanel;
and this:
public class MainPanel extends JPanel {
private CardLayout cl = new CardLayout();
private JPanel panelHolder = new JPanel(cl);
public MainPanel() {
NewSession session = new NewSession(this);
Regarding:
Also I had another idea to have a next and previous buttons at the bottom of the page to switch panels. But am not sure which one is optimal. Any idea?
I'm not sure what you mean here. Define "optimal".
Related
I set JTextField "rfid" to setEnabled(false) in MainGUI class and created method setRfidEnabled to be able to enable textfield from another class called CardLayout.
When I try to call it from CardLayout by button event listener it does nothing, I mean to textfield, because System.out.print("LOL"); works fine. MainGUI contains JFrame and by button calls another JFrame in CardLayout class.
When I initialize MainGUI class, it has Thread[Thread-2,6,main], but when I call CardLayout it becomes Thread[AWT-EventQueue-0,6,main], same as CardLayout itself. I tried to make "rfid" volatile, no success.
---Edited code---
MainGUI:
public class MainGUI {
JTextField rfid;
JButton button;
final JFrame frame;
final JPanel pane;
LayoutChanger layout = new LayoutChanger();
public MainGUI() {
rfid = new JTextField("", 10);
button = new JButton("CardLayoutSwitch");
frame = new JFrame("Main GUI Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout(5,5));
pane = new JPanel(new GridLayout(5, 5));
frame.add(pane,BorderLayout.CENTER);
pane.add(rfid);
pane.add(button);
rfid.setEnabled(false);
button.setEnabled(true);
frame.pack();
frame.setVisible(true);
frame.setLocationRelativeTo(null);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed (ActionEvent e){
layout.changeLayout(1);
}
});
}
public void setRfidEnabled() {
System.out.println("LOL");
rfid.setEnabled(true);
button.setEnabled(false);
}
}
LayoutChanger class:
public class LayoutChanger {
public static void main(String[] args) {
MainGUI gui = new MainGUI();
}
public void changeLayout(int i){
if (i == 1) {
CardLayout card = new CardLayout();
}
}
}
CardLayout class:
public class CardLayout {
JFrame frame;
JButton manual;
final JPanel pane;
MainGUI gui = new MainGUI();
public CardLayout() {
manual = new JButton("UID MANUAL");
frame = new JFrame("Card Scan Panel");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLayout(new BorderLayout(5, 5));
pane = new JPanel(new BorderLayout(5, 5));
manual.setPreferredSize(new Dimension(50, 25));
frame.add(pane, BorderLayout.CENTER);
pane.add(manual);
frame.pack();
frame.setVisible(true);
frame.setLocationRelativeTo(null);
manual.addActionListener(new ActionListener() {
#Override
public void actionPerformed (ActionEvent e){
gui.setRfidEnabled();
}
});
}
}
As stated in the comments above by #matt
Every time you click on manual button, you're creating a new MainGUI().
You need to create a single instance, either in your constructor or in the ActionListener and ask if you already have an instance of it (i.e. a Singleton) and use it.
If you decide to use the first one, declare gui as a global variable:
MainGUI gui = new MainGUI();
And on your ActionListener have it changed as:
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(currentThread());
gui.setRfidEnabled();
//frame.dispose();
}
Then you have a single instance of it.
Also as stated by #Sergiy you don't really need all those threads
Here are some examples on how to use ActionListeners:
I'm trying to make a button to count characters in a text field
AppletViewer bugged and trying to involve a timer
Calculator returns 0.0 to all questions asked
Java - My action event doesn't work
Drawing shapes on a JForm java
Animated Sprites with Java Swing This one includes a Timer (Another thread that handles the animation but doesn't block the EDT)
As you can see in all the above examples, none of them required another Thread to handle the actions, the one that uses a thread is only for performing the animation and not to react to user clicks.
Recommended tutorial: How to use Actions
I am new to Swing and cannot find a page that helps me understand JTabbedPane. I cannot find a way to control the layout of components of the tabbed panels. I can layout each of my panels correctly as separate GUIs but not in a tabbed pane like I need to do. I would like to use the BorderLayout not FlowLayout.
Also, you can see I'm trying to use colors to keep track of my panels and their components. I cannot set the background of the JTabbedPane. It is still the default grey. Can someone tell me why this is?
Thank you for any advice you can give.
What I have so far appears to follow a 'flow layout' despite any changes I've tried
(Methods have been removed or nearly removed to keep code shorter)
public class GUIFrame extends JFrame {
public GUIFrame(String title) {
JFrame frame = new JFrame(title);
Container c = frame.getContentPane();
buildGUI(c);
setFrameAttributes(frame);
}
private void buildGUI(Container c) {
c.setLayout(new BorderLayout());
c.setBackground(Color.BLACK);
JTabbedPane tabs = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.WRAP_TAB_LAYOUT);
tabs.setBackground(Color.YELLOW);
c.add("Center", tabs);
tabs.addTab("Specialty", new SpecialtyPanel());
tabs.addTab("Treatment", new TreatmentPanel());
tabs.addTab("Doctor", new DoctorPanel());
tabs.addTab("Patient", new PatientPanel());
}
private void setFrameAttributes(JFrame f) {
f.setSize(500, 500);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String args[]) {
MedicalSystemIO test = new MedicalSystemIO();
new GUIFrame("Tabbed Title");
}
public class SpecialtyPanel extends JPanel implements ActionListener {
JTextField jteInput = null;
DefaultListModel<String> model = new DefaultListModel<String>();
JList<String> list = new JList(model);
JScrollPane pane = new JScrollPane(list);
public SpecialtyPanel() {
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createLineBorder(Color.black));
buildGUI(panel);
}
private void buildGUI(JPanel panel) {
JPanel jpaInput = createInputPanel();
JPanel jpaProcess = createProcessPanel();
JPanel jpaOutput = createOutputPanel();
//panel.setLayout(new BorderLayout());
add("North", jpaInput);
add("Center", jpaProcess);
add("South", jpaOutput);
}
private JPanel createInputPanel() {
JPanel jpaInput = new JPanel();
jpaInput.setBackground(Color.RED);
return jpaInput;
}
private JPanel createProcessPanel() {
JPanel jpaProcess = new JPanel();
jpaProcess.setBackground(Color.BLUE);
return jpaProcess;
}
private JPanel createOutputPanel() {
JPanel jpaOutput = new JPanel();
jpaOutput.add(pane);
return jpaOutput;
}
The SpecialtyPanel is shown that way (flow layout) as you are putting the components on it in the wrong way:
No need for passing a new panel into the buildGUI method as you want to put them directly on the SpecialtyPanel which already is a JPanel,
you commented out the setting of the BorderLayout and
you used the wrong notation of passing the layout constraints in the add methods.
Your constructor and build method should look like this:
public SpecialtyPanel() {
buildGUI();
}
private void buildGUI() {
setBorder(BorderFactory.createLineBorder(Color.black));
JPanel jpaInput = createInputPanel();
JPanel jpaProcess = createProcessPanel();
JPanel jpaOutput = createOutputPanel();
setLayout(new BorderLayout());
add(jpaInput, BorderLayout.NORTH);
add(jpaProcess, BorderLayout.CENTER);
add(jpaOutput, BorderLayout.SOUTH);
}
To have the panel another color than gray you have to color the component that is put on the tabbed pane as it covers the whole space. Add the desired color to the buildGUI method, e.g.:
private void buildGUI(JPanel panel) {
// ...
setBackground(Color.YELLOW);
}
As a JPanel is opaque by default (that means not transparent), you need to set panels on top (except those which you colored explicitly) to be transparent. In case of SpecialtyPanel:
private JPanel createOutputPanel() {
JPanel jpaOutput = new JPanel();
jpaOutput.add(pane);
jpaOutput.setOpaque(false); // panel transparent
return jpaOutput;
}
I am fairly new to oo, I have created a class which is most of the interface of my program, I have put it all together in a class. I then want to add my Panel class to my main class so my panels are attached to my Frame:
This is what I have tried, I am not receiving any errors, when I run my program but the panels are not displaying:
Panel Class:
public class PanelDriver extends JPanel {
public JPanel p1, myg;
public PanelDriver() {
JPanel p1 = new JPanel();
p1.setBackground(Color.CYAN);
// Graphicsa myg = new Graphicsa();
JTextArea txt = new JTextArea(5,20);
txt.setText("test");
p1.add(txt);
}
}
Main class:
public class GraphicMain {
public static void main(String[] args) {
JFrame frame = new JFrame("My Program");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 600);
PanelDriver panels = new PanelDriver();
frame.getContentPane().add(panels);
GridLayout layout = new GridLayout(1,2);
}
you need a super call (because you extend JPanel you don't need to create a new one) and a layout in you Panel class like this:
public class CustomerTest extends JPanel {
public CustomerTest() {
super();
this.setBackground(Color.CYAN);
this.setLayout(new BorderLayout());
JTextArea txt = new JTextArea();
txt.setText("test");
this.add(txt);
this.setVisible(true);
}
}
and then in your main class use this, to set the frame visible and display the content. you have to set the layout for the frame after you created it:
JFrame frame = new JFrame("My Program");
GridLayout layout = new GridLayout(1, 2);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 600);
CustomerTest panels = new CustomerTest();
frame.getContentPane().setLayout(layout);;
frame.add(panels);
frame.setVisible(true);
Your PanelDriver class creates a p1 JPanel, but doesn't add it to anything.
At least add it to the PanelDriver itself :
this.add(p1);
Note that as your code is, the frame isn't even displaying, look at the answer by #XtremeBaumer to fix that part.
I used a plug-in of Eclipse to create the class diagram of this code:
public class ButtonGrid
{
private static int difficulty, moveleft, Counter, treasure_x , treasure_y;
private static String message;
JTextField tf = new JTextField();
public static JTextField tf2 = new JTextField();
JFrame frame = new JFrame(); //creation of the main game window
JPanel panel = new JPanel();
JPanel panel2 = new JPanel(new FlowLayout());
JPanel panel3 = new JPanel();
JLabel hint = new JLabel("Hint:");
JButton[][] grid; //grid buttons
public ButtonGrid (int width, int length)
{
}
ActionListener al = new ActionListener() //Action listener for the buttongrid
{
public void actionPerformed(ActionEvent e)
{
}
};
ActionListener al2 = new ActionListener() // Action listener for the reset button
{
public void actionPerformed (ActionEvent e)
{
}
}
};
public static void main (String[] args)
{
}
I cut some useless parts to reduce the size. The diagram that Eclipse draw is this one:
Do you think it's correct? I'm wondering because i thougth the ActionListeners were considered sub-classes, and also the ActionListener in the main method is not showed, but maybe it's just me not understanding how class diagrams work.
It looks right to me. The ActionListeners you have defined are anonymous classes for your protected attributes a1, and a2. Basically what the anonymous classes are doing is subclassing the ActionListener class. These new, unnamed classes are set to a1, and a2. That is why they show up the way they do in the class diagram. Also the reason that the one in your main method isn't showing up, is that anonymous ActionListener is a local variable to your main function.
Here is some information that Oracle: has about anonymous classes (http://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html)
Hope this help, good luck with your programming.
Your diagram seems correct. None of the variables you create inside the methods will appear in this diagram. Only the variables you define on the top (or outside the methods but inside the class definition) will appear in the diagram:
private static int difficulty, moveleft, Counter, treasure_x , treasure_y;
private static String message;
JTextField tf = new JTextField();
public static JTextField tf2 = new JTextField();
JFrame frame = new JFrame(); //creation of the main game window
JPanel panel = new JPanel();
JPanel panel2 = new JPanel(new FlowLayout());
JPanel panel3 = new JPanel();
JLabel hint = new JLabel("Hint:");
JButton[][] grid; //grid buttons
ActionListener al = new ActionListener() //Action listener for the buttongrid
{
//defintion of this ActionListner
};
ActionListener al2 = new ActionListener() // Action listener for the reset button
{
//definition of this ActionListener
};
ActionListener is actually an interface:
http://docs.oracle.com/javase/7/docs/api/java/awt/event/ActionListener.html
You must define it or else you can't use it. A subclass is a class that has a parent class:
http://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html
I don't what the problem is? I try to switch the two seperate classes extends JPanel with the cardLayout by using JButton and I don't know am I used the correct code...
Here is my coding.
CardLayoutMenu
public class CardLayoutMenu extends JFrame implements ActionListener{
CardLayout cardLayout = new CardLayout();
private JPanel p1 = new JPanel(cardLayout);
final String MAIN = "MAIN";
final String OPTION = "OPTION";
MainPanel mainPanel = new MainPanel();
OptionPanel optionPanel = new OptionPanel();
private Object object;
public CardLayoutMenu(Object object) {
this.object = object;
}
public CardLayoutMenu(){
setLayout(new BorderLayout());
setTitle("Card Layout Menu");
setSize(300,300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
setLocationRelativeTo(null);
add(p1);
p1.add(mainPanel, MAIN);
p1.add(optionPanel, OPTION);
}
public void actionPerformed(ActionEvent e){
try{
cardLayout.show(p1, OPTION);
}catch(Exception ex){
System.out.println("" + ex);
}
}
}
Here is my MainPanel
public class MainPanel extends JPanel{
private JButton jbtOption = new JButton("Option");
public MainPanel() {
setLayout(new FlowLayout());
add(jbtOption);
jbtOption.addActionListener(new CardLayoutMenu(this));
}
}
Then my OptionPanel, use the JButton jbtBack to go back the MainPanel
public class OptionPanel extends JPanel{
private JButton jbtBack = new JButton("Back");
public OptionPanel() {
setLayout(new FlowLayout());
add(jbtBack);
}
}
This code here will cause an infinite recursion:
public MainPanel() {
setLayout(new FlowLayout());
add(jbtOption);
jbtOption.addActionListener(new CardLayoutMenu(this));
}
Since this constructor is ultimately called from the CardLayoutMenu class, you'll have a CardLayoutMenu object that creates a MainPanel object which creates a CardLayoutMenu object that creates a MainPanel object which creates a CardLayoutMenu object that creates a MainPanel object which creates a ... well, I think that you get the picture.
One basic rule I strongly urge on you is to not make your GUI classes implement Listener interfaces as it is asking the class to do too much and often leads to confusing code such as yours. This is sort of fine in small example programs, but I wish that it wasn't used as it encourages newbies to continue to do this sort of thing. Instead consider creating an ActionListener object and pass this listener to any class that needs a button that needs to tell the CardLayout to change views. You can pass this listener into these classes via a constructor or setter method parameter.