Trying to start program on JButton press - java

I am trying to make a simple program that will ask the user a question and then compare their answer with a stored correct answer. The problem seems to be that the main loop of the program is not running when I click the Ready JButton.
I tried changing the main method to another non-default name and adding a call to it in the actionPerformed() method, and it did seem to execute the loop at least once, but then led to not being able to close the applet without the task manager once I clicked the button. I don't know if that means it hit an endless loop or what.
I'm sure there is more to fix in this code besides this issue, but I cannot make any progress on that without clearing this up first. If there is a problem with the way I went about creating the GUI please let me know. I tried to base it off of an assignment I did that worked just fine, so I don't know whether or not that is the issue.
Any help provided is appreciated.
Here is the code:
import java.awt.*;
import javax.swing.*;
public class Langarden_App extends JApplet{
private int width = 800, height = 600;
private LangardenPanel langPanel;
public void init(){
langPanel = new LangardenPanel();
getContentPane().add(langPanel);
setSize(width, height);
}
}
And the class with the logic
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.*;
public class LangardenPanel extends JPanel{
private static JButton submit;
private static JButton ready = new JButton("Ready");
private static JLabel instruct;
private JTextField input = new JTextField(100);
private static String inString;
private static ArrayList<String> questions;
private static ArrayList<String> answers;
private static Random rand = new Random();
public LangardenPanel(){
questions = new ArrayList<String> (Arrays.asList("¿Qué es la palabra para 'inveirno' en ingles?", "¿Qué es la forma de 'yo' del verbo 'venir'?"));
answers = new ArrayList<String> (Arrays.asList("winter", "voy"));
instruct = new JLabel("Welcome to Langarden! Press Submit to begin. You have one minute and three attempts for each question.");
submit = new JButton("Submit");
this.setLayout(new BorderLayout());
add(BorderLayout.SOUTH, ready);
add(BorderLayout.NORTH, instruct);
add(BorderLayout.CENTER, input);
ready.addActionListener(new SubListener());
}
public static void main(String[] args){
try{
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e){
}
System.out.println("Setting text");
instruct.setText("Alright! Let's Go!");
try{
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e){
}
do{
System.out.println("Running loop");
int qnum = rand.nextInt(questions.size());
instruct.setText(questions.get(qnum));
for (int i=0; i<3; i++){
try {
TimeUnit.SECONDS.sleep(60);
} catch (InterruptedException e) {
}
if(checkAnswer(qnum, inString)){
instruct.setText("Correct!");
break;
} else {
instruct.setText("Try again...\n" + questions.get(qnum));
}
}
instruct.setText("The correct answer was " + answers.get(qnum));
try{
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e){
}
questions.remove(qnum);
answers.remove(qnum);
instruct.setText("Would you like to continue? Enter No and click Submit to exit.");
} while (!inString.equalsIgnoreCase("No") && questions.size() != 0);
instruct.setText("Congratulations, you have completed this module!");
}
private static boolean checkAnswer(int qnum, String inString) {
if (answers.get(qnum).equalsIgnoreCase(inString))
return true;
return false;
}
private class SubListener implements ActionListener{
public void actionPerformed(ActionEvent e){
System.out.println("Button Pressed!");
if(e.getSource() == ready){
add(BorderLayout.SOUTH, submit);
submit.addActionListener(new SubListener());
} else {
inString = input.getText();
}
}
}
}

Get rid of the main method. If this is running as an applet, then the program has no business having a main method.
Make all fields non-static. Yes all.
Get rid of the while-true loop. If this runs, this will squelch your Swing event thread rendering your GUI frozen and helpless. Use a Swing Timer instead as a "pseudo" loop. Please check out the Swing Timer Tutorial for more on this.
Any time you add a component to a container, you should call revalidate() and repaint() on that same container so that the container's layout managers can layout the new component, and so that the OS can repaint over any "dirty" pixels.
Rather than add a new JButton as you're doing, much better is to swap components using a CardLayout. The tutorial can be found here: CardLayout tutorial.

Related

Implementing GUI with Java application

I'm quite the beginner when it comes to java & coding in general, so I apologise for any overly obvious questions asked. I've just completed part of an application which reads data from an SQL database, then sends some stuff to print to socket depending on what information is read. I'm now trying to learn swing and get a GUI working with the application. Currently I have 2 forms, the first is used to select a printer, then the second will (hopefully) work as a log/ console which tells the user what and when stuff is happening. I've got the code and the forms together in a project.
I was wanting to find out how I can make the class which has my code in run when a Jbutton is pressed on a GUI, as well as how I can stop it from running when a different JButton is pressed.
The code from the Swing Form (Form2.java) is as follows:
package com.company;
import javax.swing.*;
public class Form2
{
private JTextArea jtaConsole;
private JPanel Jframer;
private JButton stopButton;
private JButton startButton;
public Form2(String message)
{
JFrame frame = new JFrame("Print Application");
frame.setContentPane(this.Jframer);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setResizable(true);
frame.setVisible(true);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
jtaConsole.append(" Printer selected: " + message + "\n");
}
}
And the code from the class I want the JButton to run is as follows:
package com.company;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.sql.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ZebraCode
{
public static void main(String[] args)
{
{
while (true)
{
//SQL login.
String connectionString = "jdbc:sqlserver://:;database=;user=;password=!!;";
//Select Data.
String SQL = "SELECT TOP 2 [PK_PrintQueueID],[FK_PrinterID],[FK_BarcodeTypeID],[Barcode],[Quantity],[QueueDate],[ProcessedDate] FROM [Brad].[dbo].[PrintQueue] -- WHERE ProcessedDate IS NULL";
//Connection Variable & Time Settings.
Connection connection = null;
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
try
{
connection = DriverManager.getConnection(connectionString);
Statement stmt = connection.createStatement();
Statement stmt2 = null;
ResultSet rs = stmt.executeQuery(SQL);
while (rs.next())
{
// Get barcode value to split & Set date.
String FK_BarcodeTypeID = rs.getString("FK_BarcodeTypeID");
String barcode = rs.getString("Barcode");
String[] parts = barcode.split("-");
String part1 = parts[0];
String SQL2 = "UPDATE PrintQueue SET ProcessedDate = '" + dateFormat.format(date) + "' WHERE PK_PrintQueueID = '" + rs.getString("PK_PrintQueueID")+"'";
stmt2 = connection.createStatement();
stmt2.executeUpdate(SQL2);
// Action based on type of barcode.
if (FK_BarcodeTypeID.equals("1"))
{
// Type 128 barcode.
String zpl = "^XA^BY2,3,140^FT80,200^BCN,Y,N,N^FD>:" + rs.getString("Barcode") + "^FS^FT200,250^A0N,42,40^FH^FD" + part1 + "^FS^XZ";
printlabel(zpl);
System.out.println("New serialized barcode added.\nPrinting: " + (rs.getString("Barcode")));
System.out.println("Process date: " + dateFormat.format(date) + ".\n");
}
else
{
// Type 39 barcode.
String zpl = "CT~~CD,~CC^~CT~ ^XA~TA000~JSN^LT0^MNW^MTT^PON^PMN^LH0,0^JMA^PR4,4~SD15^JUS^LRN^CI0^XZ^XA^MMT^PW674^LL0376 ^LS0 ^BY2,3,151^FT84,249^BCN,,Y,N^FD>:" + rs.getString("Barcode") + "^FS ^PQ1,0,1,Y^XZ";
printlabel(zpl);
System.out.println("New un-serialized barcode added.\nPrinting: " + (rs.getString("Barcode")));
System.out.println("Process date: " + dateFormat.format(date) + ".\n");
}
}
} catch (SQLException e)
{
e.printStackTrace();
}
try
{
//Makes execution sleep for 5 seconds.
Thread.sleep(5000);
}
catch (InterruptedException ez)
{
}
}
}
}
//Printer Info.
public static void printlabel(String zpl)
{
try
{
Socket clientSocket;
clientSocket = new Socket("", );
DataOutputStream outToServer = new DataOutputStream(clientSocket.getOutputStream());
outToServer.writeBytes(zpl);
clientSocket.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
Any tutorials or direction as to how I can learn this would be appreciated.
Thanks!
You want to add an action listener.. here is an example. Below are two examples on how to do so using lambdas and not using one.
JButton button = new JButton("Click Me!");
// Without lambda
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
// Code to execture when clicked
}
});
//With lambda
button.addActionListener(e -> {
//code to execute when clicked
});
I'd also advise you to do a little reading, http://www.tutorialspoint.com/design_pattern/mvc_pattern.htm
Your question is a bit broad but let me offer some suggestions:
First off, you really don't want to have a JButton run the database code unchanged as doing this would be shoehorning a linear console program into an event-driven GUI, a recipe for disaster. Note that as written all your database code is held within a single static main method, and so there would be no way for the GUI to be able to control the running of that code. Either it runs or it doesn't, that's it, and no easy way for the database code to return its data to the GUI.
Instead first change that database code so that it is much more modular and OOP-friendly, including creating proper classes with state (instance fields) and behavior (instance methods), and getting almost all that code out of the static main method.
What I'm asking you to do is to create a proper model for your GUI, aka your view. Only after doing this would you have your GUI create a model object and call its methods on button push within your ActionListener. You will also want to call any long-running code within a background thread such as can be obtained with a SwingWorker.
Other issues:
You never initialize your JPanel or JTextArea variables, and so you're both adding a null variable as your JFrame's JPanel and calling methods on a null JTextArea variable, both of which will throw NullPointerExceptions.
Here's a part of code I developed to better understand Java gui. I'm also a begginer.
It's three classes: starter class, ongoing non gui processes, gui with the swingworker method. Simple, works, can safely update a lot of gui components from Swingworkers process method (passes a class instance as argument). Whole code so it can be copy/pasted:
package structure;
public class Starter {
public static void main(String[] args) {
Gui1 thegui = new Gui1();
}
}
LOGIC
package structure;
public class Logical {
String realtimestuff;
public String getRealtimestuff() {
return realtimestuff;
}
public void setRealtimestuff(String realtimestuff) {
this.realtimestuff = realtimestuff;
}
//MAIN LOGICAL PROCESS..
public void process(){
// do important realtime stuff
if (getRealtimestuff()=="one thing"){
setRealtimestuff("another thing");
}else{setRealtimestuff("one thing");
}
// sleep a while for readibility of label in GUI
//System.out.println(getRealtimestuff());
try {
Thread.sleep(250);
} catch (InterruptedException e) {
System.out.println("sleep interrupted");
return;
}
}
}
GUI CLASS with Swingworker
package structure;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JLabel;
import java.util.List;
import javax.swing.*;
public class Gui1 {
final class Dataclass{
String stringtosend;
public Dataclass(String jedan){
//super();
this.stringtosend = jedan;
}
}
// EDT constructor
JFrame frame;
public Gui1(){
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
initialize();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public void initialize() {
// JUST A FRAME WITH A PANEL AND A LABEL I WISH TO UPDATE
frame = new JFrame();
frame.setBounds(100, 100, 200, 90);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
frame.getContentPane().add(panel, BorderLayout.NORTH);
JLabel lblNovaOznaka = new JLabel();
panel.add(lblNovaOznaka);
frame.setVisible(true);
SwingWorker <Void, Dataclass> worker = new SwingWorker <Void, Dataclass>(){
#Override
protected Void doInBackground() throws Exception {
Logical logic = new Logical();
boolean looper = true;
String localstring;
while (looper == true){
logic.process();
localstring = logic.getRealtimestuff();
publish(new Dataclass(localstring));
}
return null;
}
#Override
protected void process(List<Dataclass> klasa) {
// do a lot of things in background and then pass a loto of arguments for gui updates
Dataclass xxx = klasa.get(getProgress());
lblNovaOznaka.setText(xxx.stringtosend);
}
};
worker.execute();
}
}

JTextArea.append not displaying

I'm trying to write the contents of an array to a JTextArea. I've tried everything I can think of, and I can't understand what isn't working here. I've stripped the unnecessary stuff out of my code, here's the two relevant classfiles:
Main class:
package irclogtest;
public class BriBotMain {
public static void main(String args[]) throws Exception {
boolean startup = true;
//frame test launch
BriDisplayGUI data = new BriDisplayGUI(startup);
data.irclog.append("BriBot Startup Successful!" + "\n");
//example access through function when startup is false (only in main class for sample code to demonstrate issue)
try {
BriDisplayGUI data2 = new BriDisplayGUI(false); //tells us which class we're accessing
String[] textForGUI = new String[2]; //tells us the array has 2 lines
textForGUI[0] = "this is the first line"; //set the first line of the array to this text
textForGUI[1] = "this is the second";
data2.arrayToDisplay(textForGUI); //appends contents of array to text window
}
catch(Exception e) {
System.out.println(e);
}
}
}
GUI display class:
package irclogtest;
import java.awt.event.*;
import java.io.IOException;
import javax.swing.*;
public class BriDisplayGUI extends JFrame implements ActionListener {
private static final long serialVersionUID = -7811223081379421773L;
String file_name = "C:/Bribot/logfile.txt";
//these lines create the objects we use
JFrame frame = new JFrame();
JPanel pane = new JPanel();
JButton pressme = new JButton("Click here");
JButton pressme2 = new JButton("Also here");
JTextArea irclog = new JTextArea( 20, 70);
JScrollPane scrollirc = new JScrollPane(irclog);
public BriDisplayGUI(boolean startup) { //startup function, opens and sets up the window
if(startup == true){
frame.setTitle("Bribot Test Frame"); frame.setBounds(100,100,840,420); //sets title of window, sets position and size of window
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //tells program to end on window close
frame.add(pane); //adds the main display pane to the window
//panel customization goes here
pressme.addActionListener(this);
pane.add(pressme);
pressme2.addActionListener(this);
pane.add(pressme2);
pressme.requestFocusInWindow();
irclog.setEditable(false);
scrollirc.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
pane.add(scrollirc);
irclog.setLineWrap(true);
irclog.setWrapStyleWord(true);
//pane.add(inputthing);
frame.setVisible(true);
} else {
System.out.println("Display Class Called");
}
}
public void arrayToDisplay(String[] text) throws IOException {
int i;
for ( i=0; i < text.length; i++) {
irclog.append( text[i] + "\n");
System.out.println( i + ": " + text[i]);
}
}
public void singleToDisplay(String text) throws IOException {
irclog.append(text + "\n");
System.out.println(text);
}
//basic event handler
public void actionPerformed(ActionEvent event) {
Object source = event.getSource();
if (source == pressme) {
} else if(source == pressme2) {
}
}
}
The first append works fine, but the second doesn't, and I can't figure out why (although the for loop does, as the contents of the array get written to console). I've searched quite a bit and nothing I've tried works. Can anyone point out the inevitable obvious oversight here? I'm the definition of a novice, so any advice is appreciated.
Thanks!
The default constructor doesn't do anything, meaning it doesn't construct any kind of UI or display, what would be, an empty frame.
You seem to be thinking the text will appear in data when you are appending it to data1
Try calling this(false) within the default constructor
A better solution would be to construct the core UI from the default constructor and from the "startup" constructor call this() and then modify the UI in what ever way this constructor needs
It seems like you write a new program. Why do you use Swing? Did you take a look at JavaFX?
I am not sure if that is going to fix your problem but you could try a foreach
for(String s : text){
irclog.append(s+"\n");
}

JFrame Freezing After Jbutton Pressed

I have been having a problem with my JFrame. I have added five JButtons for a user interface for an rpg program that I'm working on. When the "Status" button is pressed, the JFrame freezes, and nothing, not even EXIT_ON_CLOSE works. I want to know how to get the status button to work, and how to avoid this problem with any of the other buttons.
Here's the ButtonListener class:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class ButtonListeners {
public static final int WIDTH=360;
public static final int HEIGHT=360;
final static Monsters sk = new Monsters("Skeleton",120,20,30,5,50);
final static Monsters dm = new Monsters("Dark Mage",130,10,40,10,100);
final static Monsters kn = new Monsters("Knight",160,30,40,2,120);
final static Monsters sm = new Monsters("Slime",200,1,50,5,150);
final static Monsters go = new Monsters("golem",500,50,55,15,400);
final static Monsters dg = new Monsters("dragon",1000,35,100,25,600);
final static Monsters bk = new Monsters("Black Knight",2000,35,90,12,1000);
final static Monsters zm = new Monsters("Zombie",100,30,35,5,50);
public static void UI(){
final JFrame frame=new JFrame("Guantlet");
frame.setSize(800,600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final Player p = new Player();
frame.setSize(WIDTH,HEIGHT);
final JButton item = new JButton("Items");
final JButton status=new JButton("Status");
final JButton attack=new JButton("Attack");
final JButton defend = new JButton("Defend");
final JButton mStat = new JButton("Monster Status");
frame.setVisible(true);
frame.add(attack,BorderLayout.EAST);
frame.add(defend,BorderLayout.WEST);
frame.add(item, BorderLayout.NORTH);
frame.add(status, BorderLayout.SOUTH);
frame.add(mStat, BorderLayout.CENTER);
final ArrayList<Monsters> monOrder = new ArrayList<Monsters>();
monOrder.add(0,sk);
monOrder.add(1,zm);
monOrder.add(2,kn);
monOrder.add(3,sm);
monOrder.add(4,dm);
monOrder.add(5,go);
monOrder.add(6,dg);
monOrder.add(7,bk);
frame.setVisible(true);
JOptionPane.showMessageDialog(frame, "Welcome to the arena! Many opponents await.");
JOptionPane.showMessageDialog(frame,"A Skeleton draws near!");
class Button1Listener implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
boolean battle1 = true;
while(battle1){
if(e.getSource() == attack){// &&monOrder.contains(sk) && monOrder.contains(zm) && monOrder.contains(kn) && monOrder.contains(dm) && monOrder.contains(go) && monOrder.contains(dg)&& monOrder.contains(bk)) {
if(monOrder.contains(sk)){
sk.mHP=sk.mHP-sk.attacked(p);
sk.status();
sk.isAlive();
if(sk.isAlive()){
p.hp=p.hp-sk.attacking(p);
System.out.println("The Skeleton has "+sk.mHP+" health left");
System.out.println("You have "+"You have "+p.hp+ " health left");
p.status();
}else if(!sk.isAlive()){
monOrder.remove(0);
p.exp=p.exp+sk.exp;
p.levelUp();
JOptionPane.showMessageDialog(frame,"A Zombie emerges!");
}
//}
System.out.println(zm.mHP);
}
if(monOrder.contains(zm) && !monOrder.contains(sk)){
zm.mHP=zm.mHP-zm.attacked(p);
zm.status();
zm.isAlive();
if(zm.isAlive()){
p.hp=p.hp-zm.attacking(p);
System.out.println("The Skeleton has "+zm.mHP+" health left");
System.out.println("You have "+"You have "+p.hp+ " health left");
p.status();
}else if(!zm.isAlive()){
monOrder.remove(0);
p.exp=p.exp+zm.exp;
p.levelUp();
JOptionPane.showMessageDialog(frame,"A Dark Mage appears before you!");
}
}
break;
}
}
}
}
ActionListener b1L=new Button1Listener();
attack.addActionListener(b1L);
status.addActionListener(b1L);
status.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == status ){
JOptionPane.showMessageDialog(frame, "Maximum Health = " + p.maxHP+" \n Strength = " + p.str + "\n Speed = "+p.spd+"\n Experience to next level- "+(p.reqExp-p.exp));
Thread t = new Thread(new Runnable() {
public void run() {
// execute query here
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// update the ui here
final JFrame frame=new JFrame("Guantlet");
frame.setSize(800,600);
frame.add(attack,BorderLayout.EAST);
frame.add(defend,BorderLayout.WEST);
frame.add(item, BorderLayout.NORTH);
frame.add(status, BorderLayout.SOUTH);
frame.add(mStat, BorderLayout.CENTER);
}
});
}
});
t.start();
}
}
});
}
}
You've got a long-running bit of code here:
while(battle1){
//....
}
and since this is running in the Swing event thread, it is tying up the event thread, freezing your application. The solution is to avoid doing this, avoid tying up the event thread. Possible solutions depend on your needs including use of a Swing Timer for a game loop, use of a background Thread for long-running processes, or re-constructing your code so that this while loop isn't needed.
A quick review of your code suggests that perhaps you would want to go the Swing Timer route. If you Google Java Swing Timer tutorial you'll get decent info on using this.
As an aside, you are over-using the static modifier and should fix this. You should use this only sparingly and only for specific needs.
Aside number 2: look into the M-V-C or Model-View-Control design pattern as a way to separate your game logic out of your GUI. You've got them mixed together in a way that will make extending, improving and debugging your program difficult.
Why are you using while(true) an infinite loop whereas you have called break statement in a condition. what happened if condition is not matched? Please look it again as shown below.
while(battle1){
if(e.getSource() == attack){
...
break;
}
}
When status button is clicked then if(e.getSource() == attack) will never be matched and your program will go in infinite loop.

Swing - Thread.sleep() stop JTextField.setText() working [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
using sleep() for a single thread
I'm having issues with JTextField.setText() when using Thread.sleep(). This is for a basic calculator I'm making. When the input in the input field is not of the correct format I want "INPUT ERROR" to appear in the output field for 5 seconds and then for it to be cleared. The setText() method did work when I just set the text once to "INPUT ERROR" and by printing out the text in between I found it does work with both that and the setText("") one after the other. The problem arises when I put the Thread.sleep() between them.
Here's a SSCCE version of the code:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.regex.Pattern;
import javax.swing.*;
public class Calc {
static Calc calc = new Calc();
public static void main(String args[]) {
GUI gui = calc.new GUI();
}
public class GUI implements ActionListener {
private JButton equals;
private JTextField inputField, outputField;
public GUI() {
createFrame();
}
public void createFrame() {
JFrame baseFrame = new JFrame("Calculator");
baseFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel contentPane = new JPanel();
BoxLayout layout = new BoxLayout(contentPane, BoxLayout.Y_AXIS);
contentPane.setLayout(layout);
baseFrame.setContentPane(contentPane);
baseFrame.setSize(320, 100);
equals = new JButton("=");
equals.addActionListener(this);
inputField = new JTextField(16);
inputField.setHorizontalAlignment(JTextField.TRAILING);
outputField = new JTextField(16);
outputField.setHorizontalAlignment(JTextField.TRAILING);
outputField.setEditable(false);
contentPane.add(inputField);
contentPane.add(outputField);
contentPane.add(equals);
contentPane.getRootPane().setDefaultButton(equals);
baseFrame.setResizable(false);
baseFrame.setLocation(100, 100);
baseFrame.setVisible(true);
}
/**
* When an action event takes place, the source is identified and the
* appropriate action is taken.
*/
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == equals) {
inputField.setText(inputField.getText().replaceAll("\\s", ""));
String text = inputField.getText();
System.out.println(text);
Pattern equationPattern = Pattern.compile("[\\d(][\\d-+*/()]+[)\\d]");
boolean match = equationPattern.matcher(text).matches();
System.out.println(match);
if (match) {
// Another class calculates
} else {
try {
outputField.setText("INPUT ERROR"); // This doesn't appear
Thread.sleep(5000);
outputField.setText("");
} catch (InterruptedException e1) {
}
}
}
}
}
}
I'm not actually using a nested class but I wanted it to be able to be contained in one class for you. Sorry about how the GUI looks but again this was to cut down the code. The the important section (if (e.getSource() == equals)) remains unchanged from my code. The simplest way to give an incorrect input is to use letters.
When you use Thread.sleep() you're doing it on the main thread. This freezes the gui for five seconds then it updates the outputField. When that happens, it uses the last set text which is blank.
It's much better to use Swing Timers and here's an example that does what you're trying to accomplish:
if (match) {
// Another class calculates
} else {
outputField.setText("INPUT ERROR");
ActionListener listener = new ActionListener(){
public void actionPerformed(ActionEvent event){
outputField.setText("");
}
};
Timer timer = new Timer(5000, listener);
timer.setRepeats(false);
timer.start();
}
As Philip Whitehouse states in his answer, you are blocking the swing Event Dispatch Thread with the Thread.sleep(...) call.
Given that you've taken the time to set up an ActionListener already, it would probably be easiest to use a javax.swing.Timer to control clearing the text. To do this, you could add a field to your GUI class:
private Timer clearTimer = new Timer(5000, this);
In the constructor for GUI, turn off the repeats feature, as you really only need a one-shot:
public GUI() {
clearTimer.setRepeats(false);
createFrame();
}
Then, actionPerformed can be modified to use this to start the timer/clear the field:
public void actionPerformed(ActionEvent e) {
if (e.getSource() == equals) {
inputField.setText(inputField.getText().replaceAll("\\s", ""));
String text = inputField.getText();
System.out.println(text);
Pattern equationPattern = Pattern.compile("[\\d(][\\d-+*/()]+[)\\d]");
boolean match = equationPattern.matcher(text).matches();
System.out.println(match);
if (match) {
// Another class calculates
} else {
clearTimer.restart();
outputField.setText("INPUT ERROR"); // This doesn't appear
}
} else if (e.getSource() == clearTimer) {
outputField.setText("");
}
}
You're doing a Thread.sleep() in the Swing main thread. This is NOT good practice. You need to use a SwingWorker thread at best.
What's happening is that it's running the first line, hitting Thread.sleep().
This prevents the (main) EDT thread from doing any of the repaints (as well as preventing the next line executing).
You should use a javax.swing.Timer to setup the delayed reaction and not put sleep() calls in the main thread.

ActionListener call blocks MouseClick event

I have a window with a MenuItem "maddbound3" with the following ActionListener:
maddbound3.addActionListener
(
new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
menu_addbound3();
}
}
);
When the menu is clicked this listener calls menu_addbound3() below:
void menu_addbound3()
{
while(getEditMode() != EditMode.NONE)
{
System.out.println("!... " + getEditMode());
synchronized(this)
{
try
{
wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
A MouseClicked event alters the value of the edit mode and issues a notifyAll() so that the while loop should exit. However, tests have shown that when the system is running through the while loop, the MouseClicked event never occurs on clicking the mouse.
Does the ActionListener block the MouseClicked event? How can I resolve this issue?
Thanks
Don't have a while(true) on the Swing event thread, and likewise don't call wait() on the Swing event thread -- you'll freeze the whole GUI making it completely unresponsive. You need to understand that the main Swing event thread or "event dispatch thread" is responsible for all Swing drawing and user interaction, and so if you tie it up with long-running or freezing code, you lock your entire GUI.
Instead, change the state of your program -- perhaps by setting a variable or two, and have the behavior of your program depend on this state. If you need more specific advice, please tell us what behavior you're trying to achieve, and we can perhaps give you a better way of doing it.
For more on the Swing event thread, please read: Lesson: Concurrency in Swing
Edit
You state:
When the user clicks the menu item I want to obtain information via a series of "discrete" mouse clicks from the window. Hence, on clicking the menu, the user would be prompted to "select a point in the window". So, what I need is for my ActionListener function (menu_addbound3) to then wait for a mouse click. Hence the wait/notify setup. A mouse click changes the edit_mode and notifyAll() causes the wait in the while loop to exit which then causes the while loop to exit and I can then prompt for my next bit of information within the menu_addbound3 function, repeating this as as I need to.
Thanks for the clarification, and now I can definitely tell you that you are doing it wrong, that you most definitely do not want to use the while loop or wait or notify. There are many ways to solve this issue, one could be to use some boolean or enum variables to give the program a state and then alter its behavior depending on the state. Your EditMode enum can be used in the MouseListener to let it know that its active, and then you could also give the MouseListener class a boolean variable windowPointSelected, set to false, and then only set it true after the first click has been made.
Edit 2
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class ProgState extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private static final Color EDIT_COLOR = Color.red;
private EditMode editMode = EditMode.NONE;
private boolean firstPointSelected = false;
private JMenuBar jMenuBar = new JMenuBar();
private JTextField firstPointField = new JTextField(15);
private JTextField secondPointField = new JTextField(15);
public ProgState() {
add(firstPointField);
add(secondPointField);
JMenu menu = new JMenu("Menu");
menu.add(new JMenuItem(new AbstractAction("Edit") {
#Override
public void actionPerformed(ActionEvent arg0) {
setEditMode(EditMode.EDITING);
setFirstPointSelected(false);
}
}));
jMenuBar.add(menu);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mEvt) {
if (getEditMode() == EditMode.EDITING) {
Point p = mEvt.getPoint();
String pStr = String.format("[%d, %d]", p.x, p.y);
if (!isFirstPointSelected()) {
firstPointField.setText(pStr);
setFirstPointSelected(true);
} else {
secondPointField.setText(pStr);
setEditMode(EditMode.NONE);
}
}
}
});
}
public void setEditMode(EditMode editMode) {
this.editMode = editMode;
Color c = editMode == EditMode.NONE ? null : EDIT_COLOR;
setBackground(c);
}
public EditMode getEditMode() {
return editMode;
}
public void setFirstPointSelected(boolean firstPointSelected) {
this.firstPointSelected = firstPointSelected;
}
public boolean isFirstPointSelected() {
return firstPointSelected;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public JMenuBar getJMenuBar() {
return jMenuBar;
}
private static void createAndShowGui() {
ProgState progState = new ProgState();
JFrame frame = new JFrame("EditMode");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(progState);
frame.setJMenuBar(progState.getJMenuBar());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum EditMode {
NONE, EDITING
}
From the discussion it seems that having your class assume a number of states is the best way to proceed. We can achieve this by one or more enum variables. The reason I found this so hard to grasp initially is that I couldn't see the benefit of having all of ones code in the MouseClicked function. This is ugly and unmanageable at best.
However, using multiple enums and splitting processing into a number of external functions, we do indeed achieve a nice system for what we want.

Categories

Resources