I'm writing a program that completes the following tasks in sequence:
Collects user input from a JPanel
Uses the input to copy dependencies from the program directory into a new project directory
Uses the input to construct an interactive graph in the project directory
I have a separate class for each task and a main class that calls each object in sequence.
My problem is that the main class evaluates step 2 before step 1 is complete. Because the user has not yet closed the JPanel when the main class calls object 2, the user input is not collected before step 2 starts and the program crashes.
What I need is a way to signal to class 2 that the JPanel in class 1 has been closed. This way step 2 begins after the input fields have been collected in step 1.
Is there a way to have the window closing in class 1 trigger an action in class 2? If not, what would be the best way to solve this problem?
"Is there a way to have the window closing in class 1 trigger an action in class 2? If not, what would be the best way to solve this problem?"
As Boris the Spider pointed out, you should be using a model dialog. You're probably using a frame. You should read up on Modality to learn its behavior and features. Also take some time to look at How to make Dialogs. In short, turning the dialog's modality on (which is defaulted on JOptionPane static showXxx methods and can be set on JDialog either through setModalityType or passed through the constructor), the flow will "block" until the dialog is closed.
Below is an example. It may be overcomplicated for such a simple task (as it could easily be accomplished with a JOptionPane), but it shows how to use a JDialog. Look as the ShowDialogActionListener class. The dialog is set visible and flow is not continued in the actionPerformed until the dialog is closed, which is when the Input is obtained from the dialog.
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class DialogDemo {
private JFrame frame = new JFrame();
public DialogDemo() {
JButton button = new JButton("Open Dialog");
button.addActionListener(new ShowDialogActionListener());
frame.setLayout(new GridBagLayout());
frame.add(button);
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
class ShowDialogActionListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
InputDialog dialog = new InputDialog(frame, true);
System.out.println("Opened dialog.....");
long start = System.currentTimeMillis();
dialog.setVisible(true);
System.out.println("Dialog closed after "
+ (System.currentTimeMillis() - start) + " ms");
Input input = dialog.getInput();
ServiceOne service = new ServiceOne();
service.serviceMethod(input);
}
}
class ServiceOne {
public void serviceMethod(Input input) {
System.out.println(input.getInput());
}
}
class InputDialog extends JDialog {
private Input input;
public InputDialog(JFrame parent, boolean modal) {
super(parent, modal);
JPanel panel = new JPanel(new GridLayout(0, 1));
final JTextField field = new JTextField(20);
JButton okButton = new JButton("OK");
panel.add(field);
panel.add(okButton);
okButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String text = field.getText();
input = new Input();
input.setInput(text);
InputDialog.this.dispose();
}
});
setLayout(new GridBagLayout());
add(panel);
setSize(250, 250);
setLocationRelativeTo(parent);
}
public Input getInput() {
return input;
}
}
class Input {
private String input = "default";
public void setInput(String input) {
this.input = input;
}
public String getInput() {
return input;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new DialogDemo();
}
});
}
}
As I said earlier, the same could easily have been accomplished with
String input = JOptionPane.showInputDialog("Enter a Message");
The above would also block flow execution.
The trick to communicating events between classes is the wait() and notify() methods.
Suppose you're executing a main method. At some point main invokes another class, say a gui. Here you want main to pause and wait for certain events in the gui to complete before main proceeds with the rest of its actions.
This is accomplished by synchronizing code blocks between the two classes and telling main to wait() until the gui notifies it to proceed, notify(). For example:
main
public static void main(String[] args) throws Exception {
GUI gui = new GUI();
// Do some things
doSomething();
doSomthingElse();
// Make sure we wait until gui input has been collected before proceeding
synchronized(gui) {
try {
gui.wait();
}
catch(InterruptedException e){
e.printStackTrace();
}
}
// Do some things using the gui input we've been waiting for
doSomeMoreThings();
}
gui
// The gui method we want to synchronize
public void collectInput() {
synchronized(this) {
// Collect fields
name = nameField.getText();
age = ageField.getText();
date = dateField.getText();
// Notify waiter that our business is complete
notify();
}
}
Related
I am quite new to Java and only after researching and googling and reading many answers, I am posting this. I am kinda lost. A little guidance would be of great help. The following is a method from a class that implements the "ActionListener" interface. What I am trying to do is this: There is a button which one clicked should open a new window with two options in the form of two Radio Buttons. I need to know the Radio Button which was selected for further use in my code. I declared, the "scoreOption" variable as a class variable and static, and then attempt to update it in the "actionPerformed" abstract method. But when I refer to it (after the method call), the value stays the same - null, or whatever I set it to initially. Here is the code:
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Scanner;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextArea;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class StartEvents implements ActionListener {
StartPanel startingPanel;
static String scoreOption;
public StartEvents(StartPanel startPanel) {
startingPanel = startPanel;
}
// Scoring System Window - 1
public void scoringSystem() {
startingPanel.scoringSystem.addActionListener(new ActionListener () {
#Override
public void actionPerformed(ActionEvent e) {
Panel scoringSystemPanel = new Panel();
JFrame scoreSystemFrame = scoringSystemPanel.frame(150, 250, "Scoring System", 2, true);
JPanel scoreSystemPanel = scoringSystemPanel.panel(Color.lightGray);
JButton confirmSelection = scoringSystemPanel.button(40, 20, "Confirm");
JRadioButton scoreSystem1 = scoringSystemPanel.radioButton("Option 1: Same Points Per Hit");
scoreSystem1.setActionCommand("Option 1");
JRadioButton scoreSystem2 = scoringSystemPanel.radioButton("Option 2: Unique Points Per Hit");
scoreSystem2.setActionCommand("Option 2");
ButtonGroup scoreSys = new ButtonGroup();
scoreSys.add(scoreSystem1);
scoreSys.add(scoreSystem2);
scoreSystemFrame.getContentPane().add(scoreSystemPanel);
scoreSystemPanel.add(scoreSystem1);
scoreSystemPanel.add(scoreSystem2);
scoreSystemPanel.add(confirmSelection);
// Get Selection Event
// Option 1
scoreSystem1.addActionListener(new ActionListener () {
#Override
public void actionPerformed(ActionEvent e) {
if (scoreSystem1.isSelected()) {
scoreOption = scoreSystem1.getActionCommand();
}
}
});
// Option 2
scoreSystem2.addActionListener(new ActionListener () {
#Override
public void actionPerformed(ActionEvent e) {
if (scoreSystem2.isSelected()) {
scoreOption = scoreSystem2.getActionCommand();
}
}
});
// Confirm Event
confirmSelection.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
scoreSystemFrame.dispose();
}
});
}
});
}
Main Game Class where the method scoringsystem is called.
import java.util.ArrayList;
public class Game {
public static void main(String[] args) {
StartPanel startingPanel = new StartPanel();
startingPanel.makeStartPanel();
StartEvents starter = new StartEvents(startingPanel);
starter.rulesButton();
starter.exitButton();
starter.highScoresButton();
ArrayList<Integer> dimensions = starter.boardSizeSelector();
// Problem Zone
System.out.println(StartEvents.scoreOption);
starter.scoringSystem();
System.out.println(StartEvents.scoreOption);
// The two values of scoreOption should be different
String[] playPanelDetails = {"970", "Player 1", "450"};
// Final Start of the Game
starter.startGameButton(playPanelDetails, dimensions);
}
}
Furthermore, could you please let me know regarding the following questions:
Implementing "ActionListener" within another "ActionListener" is recommended? Good Practice?
Can there only be one declaration of the "actionPerformed" method or can it be overloaded too?
Is it possible to get a return value from "actionPerformed" method?
I would be really grateful if even some hints could be provided. I really tried a lot and only then posting it here. Thank you very much in advance.
Small Edit: When I "System.out.println" the "actioncommand" there itself, it does work perfectly, printing in the console. But not when I try to update the class variable and then try to print it after the method call. Dunno if this helps.
JFrames are not modal -- you create one and display it, it does not block the code flow, and so you are extracting the value of scoreOption right as the JFrame is being displayed and before the user has had any chance to change it. You need to use a modal dialog such as a JDialog that is created as a modal dialog or use a JOptionPane (which is actually just a modal JDialog under the hood). This will block the flow of code so that you extract the data only after it has been changed by the user.
An example that proves the point:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class FooGui01 extends JPanel {
private String frameTest = "";
private String dialogTest = "";
private JFrame mainFrame = new JFrame("Main GUI");
private JFrame subFrame;
private JDialog dialog;
public FooGui01() {
JButton showFrameBtn = new JButton("Show JFrame");
showFrameBtn.addActionListener(e -> {
changeTest1WithJFrame();
System.out.println("frameTest: " + frameTest);
});
JButton showDialogBtn = new JButton("Show JDialog");
showDialogBtn.addActionListener(e -> {
changeTest2WithModalDialog();
System.out.println("dialogTest: " + dialogTest);
});
JPanel panel = new JPanel();
panel.add(showDialogBtn);
panel.add(showFrameBtn);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.add(panel);
mainFrame.pack();
mainFrame.setLocationByPlatform(true);
mainFrame.setVisible(true);
}
public void changeTest1WithJFrame() {
if (subFrame == null) {
subFrame = new JFrame("Frame");
JButton button = new JButton("Press me");
button.addActionListener(e -> {
frameTest = "Hello World and frameTest";
subFrame.setVisible(false);
});
JPanel panel = new JPanel();
panel.add(button);
subFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
subFrame.add(panel);
subFrame.pack();
subFrame.setLocationByPlatform(true);
}
subFrame.setVisible(true);
}
public void changeTest2WithModalDialog() {
if (dialog == null) {
dialog = new JDialog(mainFrame, "Dialog", Dialog.ModalityType.APPLICATION_MODAL);
JButton button = new JButton("Press me");
button.addActionListener(e -> {
dialogTest = "Hello World and dialogTest";
dialog.setVisible(false);
});
JPanel panel = new JPanel();
panel.add(button);
dialog.add(panel);
dialog.pack();
dialog.setLocationByPlatform(true);
}
dialog.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new FooGui01());
}
}
If you run the code, when you show the sub JFrame, the test text is displayed immediately in the console before the dialog has been dealt with. If you press the button to show the dialog, the test text display is delayed until after the button has been pushed, changing the text.
Pressing the frame button twice will finally show the correct text since the text was set by the first time it was displayed.
A JDialig is just like a JFrame. That is you add components to it like you do any frame.
The difference is that you can make a JDialog modal. This means that when you use:
dialog.setVisible(true);
System.out.println("here");
The code after the setVisible(...) statement will not be executed until the dialog is closed. It also means you can't click on the parent JFrame until the dialog is closed.
An easy way to create a modal JDialog is to use a JOptionPane. It has some predefined methods that make prompting for user input easy.
For example in your case you could do something like:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SSCCE extends JPanel
{
private int scoringOption = -1;
public SSCCE()
{
JButton button = new JButton("Change Points Option");
add(button);
button.addActionListener((e) -> displayOptionDialog());
}
private void displayOptionDialog()
{
Window window = SwingUtilities.windowForComponent( this );
// Custom button text
Object[] options = {"Option 1: Same Points Per Hit", "Option 2: Unique Points Per Hit"};
scoringOption = JOptionPane.showOptionDialog(
window,
"Select your scoring option:",
"Scoring Option",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
options,
null);
System.out.println( scoringOption );
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SSCCE());
frame.pack();
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args) throws Exception
{
java.awt.EventQueue.invokeLater( () -> createAndShowGUI() );
}
}
The above is also an example of an "MRE". The code is simple and contained in a single class that you can copy/paste/compile and test.
Read the section from the Swing tutorial on How to Use Dialogs for more examples of using a JOptionPane.
If you really want to use radio buttons, then you can create a panel with the radio buttons and display them on the option pane using the showConfirmDialog(...) method. When the dialog closes you would then need to get the action command from the ButtonModel of the ButtonGroup.
See: how to set & manage the layout of JOptionPane for a basic example of this approach to get you started.
Update: I've decided the simplest thing to do at the moment would be to use separate JPane's and not JFrame's for the sub-menu's. I'll create them all together and set the others to invisible, and toggle that way. The menus aren't that complex that this would be too much of a problem.
I am creating a GUI that opens another JFrame window from a button click in another. I am just not sure of the right way to approach closing the main window when one of the buttons is clicked, but not closing the whole program. Neither am I sure how to get the second window visible (the line of code I tried from another example isn't working). The second frame that is brought up will give the user options to do things and will actually call another program/class to run on a button clicked within it (the result of one of the options is a long program so I think I need to run it on another thread.). After the program has finished running, the user will have the option to return to the main menu, which would close the second menu (and kill it), or exit the program (and thus kill the main menu and clean everything up). From the main menu, they will also have the option to close the program, where everything will be cleaned up. This is what I have so far:
Main GUI:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
class GUIMain implements ActionListener {
GUIMain(){
JFrame jFrm = new JFrame("Data Mining Application");
jFrm.setSize(800,600);
jFrm.setLayout(new BorderLayout());
jFrm.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
prepareGUI(jFrm.getContentPane());
jFrm.pack();
jFrm.setVisible(true);
}
private void prepareGUI(final Container pane){
JPanel mainPanel = new JPanel(new GridLayout(3,2,50,50));
JButton b1 = new JButton("Pre-processing");
b1.addActionListener(this);
mainPanel.add(b1);
pane.add(mainPanel,BorderLayout.CENTER);
}
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new GUIMain();
}
});
}
#Override
public void actionPerformed(ActionEvent e) {
switch (e.getActionCommand()){
case "Pre-processing":
PreProcessingGUI window = new PreProcessingGUI();
window.getFrame.setVisible(true); //not working
break;
// etc
default:
break;
}
}
}
The class and JFrame that is called:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class PreProcessingGUI implements ActionListener {
PreProcessingGUI(){
JFrame jFrm = new JFrame("Pre-processing");
jFrm.setSize(800,600);
jFrm.setLayout(new BorderLayout());
jFrm.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
prepareGUI(jFrm.getContentPane());
jFrm.pack();
}
private void prepareGUI(final Container pane) {
//do stuff
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
PreProcessingGUI window = new PreProcessingGUI();
// Not surewhat to do here either as the program is not recognising the getFrame method
//window.getFrame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
// do stuff
}
}
Well I don't work much with Swing but I can help you a bit:
When you try to show the second window in GUIMain.actionPerformed you seem to try to get the frame with a public variable having a method (getFrame).
window.getFrame.setVisible(true);
This variable doesn't exist! It is not defined anywhere. There is no magic here!
You should implement a getFrame() method in PreProcessingGUI and use it in instead of your variable.
In GUIMain.actionPerformed:
window.getFrame().setVisible(true);
In PreProcessingGUI
public class PreProcessingGUI implements ActionListener {
private JFrame jFrm; //You asssing is as you the constructor
PreProcessingGUI(){
jFrm = new JFrame("Pre-processing");
...
}
public getFrame(){
return jFrm;
}
...
In addition to that, I would say you should consider using JDialog (and optionally make it modal) instead of a JFrame.
I'm reading Thinking in Java and the author stresses that main method shouldn't call swing methods. As an example of that practice he presents the following piece of code (available on his webpage):
//: gui/SubmitSwingProgram.java
import javax.swing.*;
import java.util.concurrent.*;
public class SubmitSwingProgram extends JFrame {
JLabel label;
public SubmitSwingProgram() {
super("Hello Swing");
label = new JLabel("A Label");
add(label);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300, 100);
setVisible(true);
}
static SubmitSwingProgram ssp;
public static void main(String[] args) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
public void run() { ssp = new SubmitSwingProgram(); }
});
TimeUnit.SECONDS.sleep(1);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ssp.label.setText("Hey! This is Different!");
}
});
}
} ///:~
The gui object is then created and initialized through invokeLater method making it thread safe. But few pages later the author presents the following code:
//: gui/Button2.java
// Responding to button presses.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import static net.mindview.util.SwingConsole.*;
public class Button2 extends JFrame {
private JButton
b1 = new JButton("Button 1"),
b2 = new JButton("Button 2");
private JTextField txt = new JTextField(10);
class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
String name = ((JButton)e.getSource()).getText();
txt.setText(name);
}
}
private ButtonListener bl = new ButtonListener();
public Button2() {
b1.addActionListener(bl);
b2.addActionListener(bl);
setLayout(new FlowLayout());
add(b1);
add(b2);
add(txt);
}
public static void main(String[] args) {
run(new Button2(), 200, 150);
}
} ///:~
where SwingConsole is:
//: net/mindview/util/SwingConsole.java
// Tool for running Swing demos from the
// console, both applets and JFrames.
package net.mindview.util;
import javax.swing.*;
public class SwingConsole {
public static void
run(final JFrame f, final int width, final int height) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
f.setTitle(f.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(width, height);
f.setVisible(true);
}
});
}
} ///:~
So contrary to the previous example an object implementing JFrame is created and initialized within the main method / main thread.
My question is then:
(1) Is the second example wrong or is the first one exaggerated?
(2) Is it enough that I call swing methods through invokeLater only after the setVisible call and before that statement it is safe to call swing methods within main thread?
The second example is wrong. Swing components must be created and used from the event dispatch thread. See https://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html.
Quote from the javadoc:
Calls to an application's main method, or methods in Applet, are not invoked on the event dispatching thread. As such, care must be taken to transfer control to the event dispatching thread when constructing and showing an application or applet.
(emphasis mine)
The example with the main() method might be a bit misleading. The rule AFAIK is that only the main thread is allowed to execute Swing updates/redraws etc. and the main() method per definitionem is the main thread. All other threads are required to schedule stuff via SwingUtilities.invokeLater(...). It might be that even for the main thread it improves latencies since you might do time consuming stuff in the main thread and schedule GUI refreshes foe whenever the main thread is less busy.
EDIT: Proven Wrong
I have a couple of classes but I am only posting this Main class I have. Most of this code was taken from my professor's sample program. My problem is I want to run a check ONCE the window button is closed. The 'check' is going to see if a Boolean variable 'saved' from another class is True or False, and act accordingly. THEN, close the window. So far I have this, but I am getting an error from the JFrameL. How do I use the WindowClosing method from another class? I see that I am not supposed to call it on its own?
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
public class Main {
public static JFrameL frame;
public static void main(String[] args) {
// Create a window.
frame = new JFrameL("Checking Account Actions");
// Set the size of the window.
frame.setSize(450, 350);
frame.setAlwaysOnTop(true);
// Specify what happens when the close button is clicked.
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
CheckingAccountActions panel = new CheckingAccountActions();
frame.getContentPane().add(panel);
frame.pack();
// Display the window
frame.setVisible(true);
}
public class JFrameL extends JFrame {
public JFrameL(String title) {
super(title);
FrameListener listener = new FrameListener();
addWindowListener(listener);
}
private class FrameListener extends WindowAdapter {
public void windowClosing(WindowEvent e) {
System.out.println("WindowListener method called: windowClosed.");
if(!CheckingAccountActions.saved) {
String message = "The data in the application is not saved.\n"
+ "Would you like to save it before exiting the application?";
int confirm = JOptionPane.showConfirmDialog (null, message);
if (confirm == JOptionPane.YES_OPTION)
CheckingAccountActions.chooseFile(2);
}
Main.frame.setVisible(false);
System.exit(0);
}
}
}
}
Error message I am receiving on line 2 from main():
"No enclosing instance of type Main is accessible. Must qualify the allocation with an enclosing instance of type Main (e.g. x.new A() where x is an instance of Main)."
You appear to be trying to call instance methods in a static way. Don't do this.
I wouldn't extend JFrame for what you are trying to do.
I'd create my own WindowListener class, one that has a valid reference to the object that knows whether or not a file save is needed and whether it has been done.
e.g.,
class MyWindowAdapter extends WindowAdapter {
private CheckingAccountActions checkActions;
public MyWindowAdapter(CheckingAccountActions checkActions) {
this.checkActions = checkActions;
}
// in your window closing method
// check the state of checkActions first before doing anything
public void windowClosing(WindowEvent e) {
// note -- don't check for saved in a static way
// use a method on the instance.
if(!checkActions.getSaved()) {
// etc...
}
JFrame frame = (JFrame)e.getSource();
frame.setVisible(false);
// Main.frame.setVisible(false);
System.exit(0);
}
}
Then in main:
public static void main(String[] args) {
frame = new JFrame("Checking Account Actions");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
CheckingAccountActions panel = new CheckingAccountActions();
MyWindowAdapter winAdapter = new MyWindowAdapter(panel);
frame.addWindowListener(winAdapter);
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
}
Edit
This smells of static over-use: CheckingAccountActions.saved.
I suspect what you need to do is move panel so you can access it later.
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
public class Main {
public static JFrameL frame;
public static CheckingAccountActions panel;
public static void main(String[] args) {
// Create a window.
frame = new JFrameL("Checking Account Actions");
// Set the size of the window.
frame.setSize(450, 350);
frame.setAlwaysOnTop(true);
// Specify what happens when the close button is clicked.
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
panel = new CheckingAccountActions();
frame.getContentPane().add(panel);
frame.pack();
// Display the window
frame.setVisible(true);
}
public class JFrameL extends JFrame {
public JFrameL(String title) {
super(title);
FrameListener listener = new FrameListener();
addWindowListener(listener);
}
private class FrameListener extends WindowAdapter {
public void windowClosing(WindowEvent e) {
System.out.println("WindowListener method called: windowClosed.");
if(!panel.saved) {
String message = "The data in the application is not saved.\n"
+ "Would you like to save it before exiting the application?";
int confirm = JOptionPane.showConfirmDialog (null, message);
if (confirm == JOptionPane.YES_OPTION)
panel.chooseFile(2);
}
Main.frame.setVisible(false);
System.exit(0);
}
}
}
I have an array of JButtons which form a keypad interface. After six numbers are entered I want to disable the keypad so that no further numbers can be entered by the user.
I have written the code and the buttons do disable until the mouse hovers above any of them, then the buttons seem to re-enable themselves and run actionEvents added to them.
The full code is available here.
Possible things that I think are wrong.
There is some sort of MouseListener which is ignoring when I set button.setEnabled(false);
I haven't separated attributes from the buildGUI(); correctly, I only did this anyway so that the inner class could access them.
Possibly something to do with the gridLayout as disabling the buttons seems to work for my services JPanel buttons.
The problem lies in how you instantiated your Frame (CashMachine), not (directly) with its implementation.
You are calling buildGUI twice, one in the object's constructor, and then in the Driver class that instantiates the object. As a result, you are creating (and laying out) two sets of buttons.
When the buttons of the first set were eventually disabled, your mousing activity was revealing the second set of buttons. And a flaw in your ActionListener implementation can cause inputCount to take on values greater than 6, so buttons in the second set were not eventually disabled like those from the first set.
buildGUI should be private; it should be called in the CashMachine constructor, and not by your Driver class.
Conversely, in my opinion, CashMachine.setVisible should be called by the Driver class, and not by the CashMachine constructor.
The code works just fine I guess.
One possible source of confusion in your program is mixing number keys with control keys, Clear and Enter. Consider handling number keys separately with a single listener, as suggested in the NumberButton class shown below. Then you can handle the Clear and Enter buttons as desired. Also, using a List<NumberButton> makes the enable and disable loops easier.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridLayout;
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.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class KeyPadPanel extends JPanel implements ActionListener {
private static final int MAX = 6;
private final List<NumberButton> numbers = new ArrayList<NumberButton>();
private final JTextArea text = new JTextArea(1, MAX);
private final JButton clear = new JButton("Clear");
private final JButton enter = new JButton("Enter");
public KeyPadPanel() {
super(new BorderLayout());
JPanel display = new JPanel();
text.setEditable(false);
display.add(text);
this.add(display, BorderLayout.NORTH);
JPanel pad = new JPanel(new GridLayout(4, 4));
for (int i = 0; i < 10; i++) {
NumberButton n = new NumberButton(i);
numbers.add(n);
if (i > 0) {
pad.add(n);
}
}
pad.add(clear);
pad.add(numbers.get(0));
pad.add(enter);
clear.addActionListener(this);
enter.addActionListener(this);
this.add(pad, BorderLayout.CENTER);
}
#Override
public void actionPerformed(ActionEvent e) {
text.setText("");
enableButtons();
}
private void enableButtons() {
for (NumberButton n : numbers) {
n.setEnabled(true);
}
}
private void disableButtons() {
for (NumberButton n : numbers) {
n.setEnabled(false);
}
}
private class NumberButton extends JButton implements ActionListener {
public NumberButton(int number) {
super(String.valueOf(number));
this.addActionListener(this);
}
#Override
public void actionPerformed(ActionEvent e) {
NumberButton b = (NumberButton) e.getSource();
if (text.getText().length() < MAX) {
text.append(b.getText());
}
if (text.getText().length() == MAX) {
disableButtons();
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new KeyPadPanel());
f.pack();
f.setVisible(true);
}
});
}
}
Examining the class files was helpful! The problem is in the Driver class:
the buildGUI() method is being called 2 times: once in the constructor of CashMachine and second in the main method after calling the constructor.
public static void main(String args[])
{
CashMachine cashmachine = new CashMachine();
cashmachine.buildGUI();
}
This way you end up with the double number of buttons, that is, a pair of buttons at each position. But only one of each is being disabled.
Just remove the call to buildGUI from main (or from the constructor).
(I would change buildGUI to private as it should not be called from outside the class...)