JPanels and handling events between them - java

I'm creating a GUI which has the following panels:
The Main frame which holds all the panels in the program:
public class Main extends JFrame {
private Signup signupForm;
private Login login;
private UserScreen user;
private JPanel cards;
private CardLayout cl;
private static final int INNER_FRAME_WIDTH=800;
private static final int INNER_FRAME_HEIGHT=800;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Main frame = new Main();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Main() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, INNER_FRAME_HEIGHT, INNER_FRAME_WIDTH);
getContentPane().setLayout(new GridLayout(0, 1, 0, 0));
cards = new JPanel();
getContentPane().add(cards);
cl = new CardLayout();
cards.setLayout(cl);
login = new Login();
cards.add(login,"login");
signupForm = new Signup();
cards.add(signupForm,"signup");
ActionListener listen = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
try{
//check for a user with the parameters from userName and password fields . On success create a UserScreen for the current user.
//This means that you need to send the User object to the UserScreen c'tor. It should look something like this. Might need to change UserScreen accordingly.
user = new UserScreen();
cards.add(user,"user");
cl.show(cards,"user");
}
catch (Exception exception){ //TODO: Change to exception thrown from Client constructor
//TODO: Have the exception popup the relevant screen.
}
}
};
login.loginBtn.addActionListener(listen); << Example of setting a button's actionListener from Main.
setLoginListeners();
setSignupListeners();
}
}
A Login panel - This is the screen you encounter when you open the program.
A Signup panel - You get to this screen when pressing a 'signup' button in the login panel.
A User's screen - A screen that display's a user's info.
I've encountered an issue which I'm not sure how to handle:
After clicking the 'Login' button in the Login panel, I want to switch to the new User's screen (UserScreen object).
Since the class Login doesn't have access to the UserScreen object (only the Main frame does), I had to set the button's actionListener from the Main class which seemed like a terrible solution (since it required me to give the Main access to many fields from the Login panel so it can check if the user with the given username and password exists etc). Is there no better way?
Is there a better solution than the one I've got?

You should implement the model-view-controller pattern, the interaction between different views should always be through a controller.
http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

Related

How do I wait for the input from the GUI back to main?

I want to be able to pass user input from my GUI to one of my classes. However, the input is not passed over and immediately checks the if statement.
How do I get program to wait for the input and only checks after the button is clicked?
Main class
public class MainTest {
public static void main(String[] args) {
String weaponCategory;
//Create Java GUI
GUITest window = new GUITest();
if(window.getCategory() != "")
{
System.out.println("test");
}
}
}
GUITest class
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GUITest implements ActionListener{
private JFrame frmInventorysystem;
private JPanel frameBottom;
private JComboBox equipList;
private String category = "";
private JButton confirmBtn, cancelBtn;
/**
* Create the application.
*/
public GUITest()
{
frmInventorysystem = new JFrame();
frmInventorysystem.setTitle("InventorySystem");
frmInventorysystem.setBounds(100, 100, 450, 300);
frmInventorysystem.getContentPane().setLayout(new BorderLayout(0, 0));
frmInventorysystem.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
/*JFrame inside another JFrame is not recommended. JPanels are used instead.
* Creating a flow layout for the bottom frame
*/
frameBottom = new JPanel();
frameBottom.setLayout(new FlowLayout());
//creates comboBox to find out which of the three items player is looking to insert
String[] weaponCategories = {"Weapon", "Armor", "Mod"};
equipList = new JComboBox(weaponCategories);
frmInventorysystem.getContentPane().add(equipList, BorderLayout.NORTH);
//Converting BorderLayout.south into a flow layout
frmInventorysystem.getContentPane().add(frameBottom, BorderLayout.SOUTH);
confirmBtn = new JButton("Confirm");
confirmBtn.addActionListener(this);
frameBottom.add(confirmBtn);
cancelBtn = new JButton("Cancel");
cancelBtn.addActionListener(this);
frameBottom.add(cancelBtn);
frmInventorysystem.setVisible(true);
}
public void actionPerformed(ActionEvent e)
{
//creates new windows to sort equipment when confirmBtn is clicked
if(e.getSource() == confirmBtn)
{
if(equipList.getSelectedItem().equals("Weapon"))
{
//GUIWeaponCategory weapon = new GUIWeaponCategory();
category = equipList.getSelectedItem().toString();
}
}
//Exits when cancelBtn is clicked
if(e.getSource() == cancelBtn)
{
System.exit(0);
}
}
public String getCategory()
{
return category;
}
public void setCategory(String a)
{
category = a;
}
}
GUITest launches as expected.
However, the first println is missing.
How would I go about doing this?
What concepts or pieces of code am I missing?
EDIT1: Added a couple more details to make the program reproducible and complete.
EDIT2: Making the code more readable for easy understanding.
There are some changes to make on your program
Remove extends JFrame as stated in my comments above, see Extends JFrame vs. creating it inside the program
Place your program on the EDT, see point #3 on this answer and the main method for an example on how to do that.
You're confused about how the ActionListeners work, they wait until you perform certain action in your program (i.e. you press Confirm button) and then do something. "Something" in your program means: Print the selected item and check if it's a weapon then, do something else.
So, in this case, you don't need to return back to main to continue with your program, main only serves to initialize your application and nothing else. You need to think in the events and not in a sequential way. That's the tricky and most important part.
You need to change your programming paradigm from console applications and do-while that everything happens in a sequential manner rather than events that are triggered when the user does something with your application.
For example:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GUITest implements ActionListener {
private JFrame frmInventorysystem;
private JPanel frameBottom;
private JComboBox equipList;
private JButton confirmBtn, cancelBtn;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new GUITest()); //Java 8+ if using an earlier version check the point #2 in this answer and modify the code accordingly.
}
/**
* Create the application.
*/
public GUITest() {
frmInventorysystem = new JFrame();
frmInventorysystem.setTitle("InventorySystem");
frmInventorysystem.setBounds(100, 100, 450, 300);
frmInventorysystem.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frmInventorysystem.getContentPane().setLayout(new BorderLayout(0, 0));
/*
* JFrame inside another JFrame is not recommended. JPanels are used instead
* Creating a flow layout for the bottom frame
*/
frameBottom = new JPanel();
frameBottom.setLayout(new FlowLayout());
// creates comboBox to find out which of the three items player is looking to
// insert
String[] weaponCategories = { "Weapon", "Armor", "Mod" };
equipList = new JComboBox(weaponCategories);
frmInventorysystem.getContentPane().add(equipList, BorderLayout.NORTH);
// Converting BorderLayout.south into a flow layout
frmInventorysystem.getContentPane().add(frameBottom, BorderLayout.SOUTH);
confirmBtn = new JButton("Confirm");
confirmBtn.addActionListener(this);
frameBottom.add(confirmBtn);
cancelBtn = new JButton("Cancel");
cancelBtn.addActionListener(this);
frameBottom.add(cancelBtn);
frmInventorysystem.setVisible(true);
}
public void actionPerformed(ActionEvent e) {
// creates new windows to sort equipment when confirmBtn is clicked
if (e.getSource() == confirmBtn) {
String category = equipList.getSelectedItem().toString(); //Get the selected category
doSomething(category); //Pass it as a parameter
}
// Exits when cancelBtn is clicked
if (e.getSource() == cancelBtn) {
frmInventorysystem.dispose();
}
}
// Do something with the category
private void doSomething(String selectedEquipment) {
System.out.println(selectedEquipment);
if (selectedEquipment.equals("Weapon")) {
System.out.println("It's a weapon!"); //You can open dialogs or do whatever you need here, not necessarily a print.
} else {
System.out.println("Not a weapon");
}
}
}
Notice that I removed inheritance, I'm not returning back to main and still printing the selected item and checking if it's a weapon or not.
I also exit the application in a safer way.
This is a sample output:
Weapon
It's a weapon!
Armor
Not a weapon

JFrame: Waiting for button press on form to receive values in class and continue

I'm making a simple game. I have a class called GraphicalInterface, and a JFrame class FrmMain which extends GraphicalInterface with some values that I want to send from FrmMain to GraphicalInterface when a JButton btnNewGame in FrmMain is clicked. However, when I open a FrmMain object to choose the settings, I need the subsequent code to wait until btnNewGame is clicked and the FrmMain closed.
I have searched around including StackOverflow but I haven't found any relevant solutions. The best idea I have right now is to use a dialog, but I'm not confident that it would be appropriate for my menu or how I should implement it in this case, having never used it before.
Furthermore, I want to use the solution for this problem for another JFrame called FrmGame which should also send values to GraphicalInterface on certain inputs, but not close itself when it does so.
Here is the relevant code for GraphicalInterface, a subclass of an abstract class IOInterface.
public class GraphicalInterface extends IOInterface{
protected FrmMain mainMenu;
protected FrmGame gameInterface;
private Settings settings;
#Override
public void initialise(){
round = new Round(ioInterface);
round.initialise(getSettings());
board = round.getBoard();
turnCount = round.getTurnCount();
turnCount++;
round.setTurnCount(turnCount);
displayBoard();
}
#Override
public Settings getSettings() {
openMainMenu();
return settings;
}
#Override
public void playTurn() {
while (!round.getGameOver()) {
round.guess();
turnCount++;
}
}
private void openMainMenu(){
if (mainMenu == null){
mainMenu = new FrmMain();
}
mainMenu.setVisible(true);
mainMenu.toFront();
//I need the code to wait here for the user to pick their settings on
//FrmMain and press btnNewGame before the settings below are retrieved from it
int pegCount = mainMenu.sldPegs.getValue();
int colourCount = mainMenu.sldColours.getValue();
int mode = mainMenu.lstMode.getSelectedIndex();
settings = new Settings(pegCount, colourCount, mode);
settings.calculateRows();
}
}
And here is the code from FrmMain
public class FrmMain extends GraphicalInterface{
private JPanel contentPane;
JButton btnNewGame;
public FrmMain() {
setTitle("Mastermind");
setResizable(false);
initialiseComponents();
}
private void updateToolTips() {
int mode = lstMode.getSelectedIndex();
if (mode == 0 || mode == 1)
lblToolTipA.setText("Human Codebreaker");
else lblToolTipA.setText("AI Codebreaker");
if (mode == 1 || mode == 3)
lblToolTipB.setText("AI Codemaker");
else lblToolTipB.setText("Human Codemaker");
}
private void btnNewGameClick(){
this.dispose();
}
private void initialiseComponents() {
try {
UIManager.setLookAndFeel(
"com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
}
catch (Throwable e) {
e.printStackTrace();
}
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 321, 387);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
btnNewGame = new JButton("New Game");
btnNewGame.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent arg0) {
btnNewGameClick();
}
});
btnNewGame.setFont(new Font("Segoe UI", Font.PLAIN, 13));
btnNewGame.setBounds(155, 188, 137, 25);
contentPane.add(btnNewGame);
}
}
Here is a picture of my incomplete FrmMain.
So, how should I retrieve the values from the JFrame into the main GUI class GraphicalInterface, suspending the code until the New Game button is clicked?
Note:
Currently, FrmMain extends up through several classes which finally imports javax.swing.JFrame. I don't know if FrmMain needs to extend GraphicalInterface or if it should simply extends JFrame directly.
GraphicalInterface used to implement an interface IOInterface. Now, IOInterface is an abstract class so I could use fields which GraphicalInterface and another class could inherit.
In generally, if you need your code to wait while you collect information from the user you should make use of a modal dialog, it allows you to collect a small amount of information from the user, while it blocks the codes execution at the point the dialog was made visible, until it is closed.
Take a look at How to Make Dialogs for more details
You will need to design a solution which allows your code to get the information from the other form but also determine when the user has cancelled or closed the window.
Generally speaking, you should not be extending from top level containers like JFrame or JDialog and instead, basing your UI forms on something like JPanel, this way, you can add them to what ever container you want to.

Java Swing - .dispose() method not closing JFrame

I'm writing a simple game that is based on two JFrames. The user is only displayed on JFrame at a time. To get to the other JFrame, the user clicks a button. The button in the new JFrame will have an overriden method that should navigate the user back to the old JFrame.
I've found success doing this however the .dispose() method doesn't seem to close the new frame when the user tries to navigate back to the old frame.
Here's a snippet of my code (original JFrame class):
public class TicTacToe extends JFrame implements ActionListener{
....
public class gameModeListener implements ActionListener
{
#Override public void actionPerformed(ActionEvent e)
{
TicTacToeSingle singlePlayer = new TicTacToeSingle();
singlePlayer.setVisible(true);
dispose();
}
}
}
And from the other JFrame class:
public class TicTacToeSingle extends TicTacToe{
private int i;
private int j;
JButton boardArray[][];
Random random_generator = new Random();
int randomI;
int randomJ;
public TicTacToeSingle()
{
super("Single Player");
gameMode.setText("Two Player"); //gameMode is the button that has it's actionlistener method overriden. It navigates the user to and back from JFrame to JFrame
gameMode.addActionListener(new gameModeListener());
....
}
....
public class gameModeListener implements ActionListener
{
#Override public void actionPerformed(ActionEvent e)
{
TicTacToe twoPlayer = new TicTacToe("Two Player");
twoPlayer.setVisible(true);
dispose();
}
}
Your help is greatly appreciated :)
Well when u have used Object of the class and u are calling just Dispose(); then how compiler came to know that for this particular class object should be dispose on that call.?
so for giving reference u need to use this try this:
TicTacToe twoPlayer = new TicTacToe("Two Player");
twoPlayer.setVisible(true);
twoPlayer.dispose();
if still there is prob let me know.

JFrame intercept exit on close to save data

I created a window and want to intercept the exit with the method windowStateChanged to save the data before the application closes. However, it doesn't appear to be saving the data before it closes. How can I correct this?
see code below:
public class InventoryMainFrame extends JFrame implements WindowStateListener{
//set up the main window - instantiate the application
private InventoryInterface inventoryInterface; //panel that contains menu choices and buttons
public InventoryMainFrame(){ //main window
setTitle("Inventory System");
setSize (500,500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
//setLocationRelativeTo(null); //center window on the screen
inventoryInterface = new InventoryInterface(); //set up the panel that contains menu choices and buttons
add(inventoryInterface.getMainPane()); //add that panel to this window
pack();
setVisible(true);
//display window on the screen
}
public static void main(String[] args) {
//sets up front end of inventory system , instantiate the application
InventoryMainFrame aMainWindow = new InventoryMainFrame( );
}
#Override
public void windowStateChanged(WindowEvent w) {
//intercept the window close event so that data can be saved to disk at this point
if (w.getNewState()==WindowEvent.WINDOW_CLOSED){
//save the index file
try{
inventoryInterface.getInventory().saveIndexToFile();
System.out.println("saving");
dispose(); //dispose the frame
}
catch(IOException io){
JOptionPane.showMessageDialog(null,io.getMessage());
}
}
}
}
You should try registering a WindowAdapter and override its windowClosing method. For more information, see How to Write Window Listeners.

Fast Jbutton clicks results in no action

Hey guys, I have a problem with a code that I've been writing.
I have a JFrame that contains two buttons. Each of these buttons has an action. The problem I'm having is with a JButton called "btnDone" that's supposed to get back to a previous screen. If I I keep pushing the button repeatedly, eventually the "btnDone" would stop doing the logic it's supposed to do. My code is as follows:
For the frame:
public class ItemLocatorPnl extends JPnl
{
private static final long serialVersionUID = 1L;
private Pnl pnl;
private JButton btnDone;
private JButton btnRefreshData;
public void setPnl(Pnl pnl) {
this.pnl = pnl;
}
public ItemLocatorPnl(Pnl pnl)
{
super();
this.pnl=pnl;
initialize();
}
private void initialize()
{
this.setSize(300, 200);
JPanel jContentPane = new JPanel();
jContentPane.setLayout(new MigLayout());
// (1) Remove window frame
setUndecorated(true);
// (3) Set background to white
jContentPane.setBackground(Color.white);
// (5) Add components to the JPnl's contentPane
POSLoggers.initLog.writeDebug("ItemLocator: Adding icon");
jContentPane.add(wmIconLabel, "align left");
POSLoggers.initLog.writeDebug("ItemLocator: Adding global controls");
jContentPane.add(createUpperPanel(), "align right, wrap");
POSLoggers.initLog.writeDebug("ItemLocator: Adding main panel");
jContentPane.add(pnl,"width 100%,height 100%, span 3");
// (6) Attach the content pane to the JPnl
this.setContentPane(jContentPane);
}
private JPanel createUpperPanel()
{
JPanel upperPanel=new JPanel();
MigLayout mig = new MigLayout("align right", "", "");
upperPanel.setLayout(mig);
upperPanel.setBackground(Color.WHITE);
// Create the Done button
btnDone= GraphicalUtilities.getPOSButton("<html><center>Done</center></html>");
btnDone.addActionListener(new ButtonListener());
// Create the Refresh Data button
btnRefreshData = GraphicalUtilities.getPOSButton("<html><center>Refresh<br>Data</center></html>");
btnRefreshData.addActionListener(new ButtonListener());
//Addiing buttons to the Panel
upperPanel.add(btnRefreshData, "width 100:170:200, height 100!");
upperPanel.add(btnDone, "width 100:170:200, height 100!");
return upperPanel;
}
public class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
try {
if (e.getSource() == btnRefreshData) {
Actual.refreshData();
} else if (e.getSource() == btnDone) {
Actual.backToMainScreen();
}
}
catch (Exception ex)
{
}
}
}
}
This is the method that the btnDone button calls upon clicking:
public static void backToMainScreen()
{
frame.setVisible(false);
frame.dispose();
}
This is the code that displays the JFrame:
public static void displayItemLocatorFrame()
{
pnl = new Pnl();
frame = new Frame(pnl);
frame.setVisible(true);
pnl.getSearchCriteria().requestFocus();
}
Please note that the "frame" object is static, and all of my methods are static, and they exist in a static class called Actual.
So in short, I just want to make sure that no matter how many times a user clicks on the button, and no matter how fast the clicks were, the frame should act normally.
Any suggestions? (I tried synchronizing my methods with no luck..)
I would generally prefer to use an Action for what you're trying to do.
So your code might look like this:
btnDone = new JButton(new CloseFrameAction());
...
private class CloseFrameAction extends AbstractAction
{
public CloseFrameAction()
{
super("Done");
}
public void actionPerformed(ActionEvent e)
{
frame.dispose();
setEnabled(false);
}
}
Notice the setEnabled(false) line - this should disable the button and prevent the user clicking on it again. Obviously I don't know what your exact requirements are but this is the general approach I would take.
The problem was with using a static panel that was instantiated with the click of the button each time. Removing "static" has finally fixed my problem! Thanks everyone for the help.

Categories