I've been making a program that reads from a file, identifies common "posts" in the file, and makes a summary of these. My problem is that the GUI-event that allows the user to specify the name and search-term of the post, does not interrupt the running of the program, like I want it to.
I can make it stop, but then the GUI will not be correctly displayed. I have tried some solutions, which will be specified at the bottom of the post.
EDIT: removed codedump and added something resembeling an SSCCE:
class SSCCE{
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
new Gui();
}
});
}
}
class Gui implements ActionListener{
boolean runn=true;
JFrame wind2;
JTextField nameF, searchtermF;
JButton done;
Gui(){
runEx();
}
public void runEx(){
int i =0;
while(runn){
if(i==10) break;
System.out.println("Open window and record information given! One at the time!!!");
System.out.println(" ");
giveName("test");
i++;
}
}
public void giveName(String s){
JLabel nameL = new JLabel("Give this post a name:");
JLabel searchL = new JLabel("What do you want the searchterm to be?");
wind2 = new JFrame("EazyMoney");
wind2.setLayout(new BorderLayout());
JPanel all = new JPanel();
all.setLayout(new GridLayout(2,2));
searchtermF = new JTextField(30);
nameF=new JTextField(30);
all.add(nameL);
all.add(nameF);
all.add(searchL);
all.add(searchtermF);
done = new JButton("Press when you have filled in the information!");
done.addActionListener(this);
String prn = "The post in question: " + s;
JLabel header = new JLabel(prn);
wind2.add(header, BorderLayout.NORTH);
all.setVisible(true);
wind2.add(all, BorderLayout.CENTER);
wind2.add(done, BorderLayout.SOUTH);
wind2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
wind2.pack();
wind2.setLocationRelativeTo(null);
wind2.validate();
wind2.setVisible(true);
}
public void actionPerformed(ActionEvent e){
System.out.println("Action recorded, new window can now be shown. All information stored.");
System.out.println(" ");
}
}
The solutions I have tried is:
A simple block, that does a while(true){} and sets the variable to true after the first instance of g.giveName() have been called. I used the ActionListener to call a method that then changed the variable to false again, when the necessary input was given. This resulted in a gray box, with nothing in it.
Making a cyclic barrier that did the same as the block above. Used a separate thread to call g.giveName() and then call the await() from the action listener. Same result as above.
Making readFile be run by a separate thread and call invokeAndWait() on the g.giveName() function. Gave cannot call invokeAndWait() from the EDT-thread, even though it was run from a new thread.
I can not give examples of the code used in instances above, as I have tried a lot of different solutions and do not have it any more. Please take into account that it might have been implemented wrong, and thus might be a valid answer to my question, even though I could not seem to get it to work!
Final note: all work can be found here, if you wish to test the code:
https://github.com/Robiq/EazyMoneyWork
The way to avoid blocking the EDT if you need to execute something else on the same thread it is to temporarily create a new event queue. Here is some example code. In this case it blocks the current thread waiting for some other event to be signalled but you could replace this with whichever long running process is required.
First check if you are running on the EDT: SwingUtilities.isEventDispatchThread. Then if you are:
EventQueue tempEventQueue = new EventQueue();
Toolkit.getDefaultToolkit().getSystemEventQueue().push(tempEventQueue);
try {
wait();
} catch (InterruptedException ex) {
// stop waiting on interrupt
} finally {
tempEventQueue.pop();
}
Something similar to this is how modal dialogs work in Swing. However in general it's not good practice. Much better is to understand which events to listen for to perform specific actions. In your case the user event should not 'stop' your program - it should disable inappropriate components until the user has responded and then re-enable them.
Related
Related to my previous question: Call repaint from another class in Java?
I'm new to Java and I've had a look at some tutorials on SwingWorker. Yet, I'm unsure how to implement it with the example code I gave in the previous question.
Can anyone please explain how to use SwingWorker with regards to my code snippet and/or point me towards a decent tutorial? I have looked but I'm not sure I understand yet.
Generally, SwingWorker is used to perform long-running tasks in Swing.
Running long-running tasks on the Event Dispatch Thread (EDT) can cause the GUI to lock up, so one of the things which were done is to use SwingUtilities.invokeLater and invokeAndWait which keeps the GUI responsive by which prioritizing the other AWT events before running the desired task (in the form of a Runnable).
However, the problem with SwingUtilities is that it didn't allow returning data from the the executed Runnable to the original method. This is what SwingWorker was designed to address.
The Java Tutorial has a section on SwingWorker.
Here's an example where a SwingWorker is used to execute a time-consuming task on a separate thread, and displays a message box a second later with the answer.
First off, a class extending SwingWorker will be made:
class AnswerWorker extends SwingWorker<Integer, Integer>
{
protected Integer doInBackground() throws Exception
{
// Do a time-consuming task.
Thread.sleep(1000);
return 42;
}
protected void done()
{
try
{
JOptionPane.showMessageDialog(f, get());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
The return type of the doInBackground and get methods are specified as the first type of the SwingWorker, and the second type is the type used to return for the publish and process methods, which are not used in this example.
Then, in order to invoke the SwingWorker, the execute method is called. In this example, we'll hook an ActionListener to a JButton to execute the AnswerWorker:
JButton b = new JButton("Answer!");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
new AnswerWorker().execute();
}
});
The above button can be added to a JFrame, and clicked on to get a message box a second later. The following can be used to initialize the GUI for a Swing application:
private void makeGUI()
{
final JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new FlowLayout());
// include: "class AnswerWorker" code here.
// include: "JButton" b code here.
f.getContentPane().add(b);
f.getContentPane().add(new JButton("Nothing"));
f.pack();
f.setVisible(true);
}
Once the application is run, there will be two buttons. One labeled "Answer!" and another "Nothing". When one clicks on the "Answer!" button, nothing will happen at first, but clicking on the "Nothing" button will work and demonstrate that the GUI is responsive.
And, one second later, the result of the AnswerWorker will appear in the message box.
Agree:
Running long-running tasks on the Event Dispatch Thread (EDT) can cause the GUI to lock up.
Do not agree:
so one of the things which were done is to use SwingUtilities.invokeLater and invokeAndWait which keeps the GUI responsive..
invokeLater still runs the code on the EDT, and can freeze your UI!! Try this:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
At least I, cannot move my mouse once I click the button which triggers the actionPerformed with the above code. Am I missing something?
I have a jframe i want to display while my main frame is running. i want to pause my main code, until the user does the necessary actions on the other frame. I've read a lot of solutions but i need to see it done for my code to understand and grasp it fully. i do not want to use jdialog like I've seen listed as an answer before. My main goal is to understand better threading so that i can use what i learn in different cases.
With the code I've created, when running the thread, only just the frame loads, none of the other features are there on the frame. (the frame is simple it has a label, a list the user selects from, and a button to basically return the chosen list value.) its like the thread is cut off from completing or something.
here is my class calling the screen:
public class myThread implements Runnable {
String result = null;
public void run() {
MessageScreen ms = new MessageScreen();
ms.setVisible(true);
}
public String getResult() {
return result;
}
public void setResult(String AS) {
result = AS;
}
}
in my main code, a method is called that is returning a String[] value, with this method at some point i have the following code calling the new thread to get the value necessary to return in the original main method:
myThread mt = new myThread();
Thread t = new Thread(mt);
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
myreturn = new String[] {"true", mt.getResult()};
without listing the whole code for the second frame, when the user presses the button, and at the end of the listener tied to the button press the i want to close the frame and return a string that was selected from the list:
jf.dispose();
myt.setResult(AdminSelection);
in the frame class, i have the following instance variables declared:
String AdminSelection = null;
myThread myt;
i hope this is enough information for someone to help me out and understand whats gone wrong here.
The function join() waits until the end of the run() method, when you do t.join(), your thread is already or almost ended. This is because in your run() method there is nothing that blocks the thread until the user has clicked the confirm button. And is better like this!
There is no sense to create a thread here, you should use a callback, or more generally in Java, a listener. You can take a look at Creating Custom Listeners.
But, especially if you want to pause your main code, you should use a (modal) JDialog which is made for this! Don't try to block the UI by yourself, you could block the UI thread (handled by Swing/AWT) by mistake. Creating a JDialog is better because everything is already made for this usage on the UI thread.
Also, you must know that create a Thread is really long, use a Thread when you really need it.
I've made my own kind of statusbar. With a statusbareditor I can set some message on the bar and it disappears after 10 sec. Because my GUI may not be blocked, this statusbareditor works on a second thread. This works fine. But after editing the statusbar I set a new panel on the form. This new panel only appears after 10 secs. This is strange because the statusbareditor works on a different thread.
public void HandleLoggedIn(Person account) {
StatusbarEditor reportThread = new StatusbarEditor(labelStatusbar, "Aangemeld als "
+ account.toString() + ".");
reportThread.start();
asideform = new Asideform();
asideform.AddFollower(this);
this.add(asideform, BorderLayout.WEST);
}
and the statusbareditor-class:
public class StatusbarEditor extends Thread{
private JLabel statusbar;
private String text;
public StatusbarEditor(JLabel statusbarlabel, String report){
statusbar = statusbarlabel;
text = report;
}
#Override
public void run() {
statusbar.setText(text);
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
Logger.getLogger(StatusbarEditor.class.getName()).log(Level.SEVERE, null, ex);
}
if(statusbar.getText().equals(text)){
statusbar.setText("");
}
}
}
thanks!
I can only imagine what goes wrong there:
asideform = new Asideform();
asideform.AddFollower(this);
this.add(asideform, BorderLayout.WEST);
Is this part updating your UI? If yes, you must take in consideration that your thread has most likely not come as far as you need it. Processing time is distributed approximately random as long as nothing else is defined.
EDIT
One more Thing:
private JLabel statusbar;
Doesn't this belong to your UI class?
without seeing "AsideForm" class it's hard to say but my guess is this is a race condition.
perhaps you need to prepare your asideForm object before starting the thread so the events will take place in the expected order.
I am having trouble updating a jlabel in a method. here is my code:
JLabel curStatus = new JLabel("");
JButton jbtnSubmit;
public static void main(String[] args) {
test gui = new test();
gui.startGUI();
// gui.setCurStatus("testing!"); << seems to work here,
//but when i call it from another class, it doesn't want to run.
}
// Set up the GUI end for the user
public void startGUI() {
// These are all essential GUI pieces
new JTextArea("");
final JFrame jfrm = new JFrame("my program");
jfrm.setLayout(new FlowLayout());
jfrm.setSize(300, 300);
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jbtnSubmit = new JButton("Submit");
jfrm.add(jbtnSubmit);
jfrm.add(curStatus);
jfrm.setVisible(true);
}
public void setCurStatus(String inCurStatus) {
curStatus.setText(inCurStatus);
curStatus.setVisible(true);
}
what is happening, is that the label, curStatus is not appearing. for example, here is a call:
gui1.setCurStatus("Now running diagnostics... Please wait!");
Your problem appears to be one of misplaced references.
Here is how you create your GUI:
public static void main(String[] args) {
test gui = new test();
gui.startGUI();
// gui.setCurStatus("testing!"); << seems to work here,
// but when i call it from another class, it doesn't want to run.
}
You create your "test" object (which should be named "Test" by the way to conform to Java naming conventions) inside of your main method. Since it is declared inside of main, this variable has scope only inside of main and is visible no where else.
You then tell us that you are calling the method like so:
gui1.setCurStatus("Now running diagnostics... Please wait!");
The gui1 variable refers to a test class object but it likely refers to a different object than the test object that is being displayed since the original displayed test object is only refered to by a variable local to the main method.
To solve this, you must make sure to call setCurStatus on the currently displayed test object. How to do this depends on the rest of your code, something you've refused to show us despite our requests for you to do so.
Edit: Based on your latest bit of posted code (which still won't compile for me since it is missing a method, createTasksFile(), my assumptions are correct, you are calling setCurStatus(...) on a gui object that is not the displayed one:
public static String[] runDiagnostics() throws IOException {
gui gui1 = new gui(); // (A)
gui1.setCurStatus("Now running diagnostics... Please wait!");
On line (A) you create a new gui object and call setCurStatus on it, but it is not the GUI object that is being displayed but a completely different and unrelated object. It's only relation is that it is an object of the same class as the one being displayed but that's it. The solution is to get a reference to the displayed GUI and call this method on that object, and that object only.
Also, Robin's assumptions are correct, in that even if you fix this, you're going to be stuck with a Swing concurrency issue. The JLabel won't update because the Swing thread is trying to open a file:
public static String[] runDiagnostics() throws IOException {
gui gui1 = new gui();
gui1.setCurStatus("Now running diagnostics... Please wait!");
int i = 0;
int errorsI = 0;
File f = new File("tasks.txt");
String[] errors = { "", "", "", "", "" };
// try to create the file three times
do {
f.createNewFile();
i++;
} while (!f.exists() && i < 3);
So we're both right. The solution to this is to open your file on a background thread, a SwingWorker would work nicely here.
Edit 2
So to fix the reference problem, pass a reference of the gui into the runDiagnostics method using a gui parameter. Then call the setCurStatus method on this parameter. For example:
public static String[] runDiagnostics(gui gui1) throws IOException {
//!! gui gui1 = new gui(); // !! no longer needed
gui1.setCurStatus("Now running diagnostics... Please wait!");
You would have to pass the GUI in when calling the method:
//!! results = taskBckg.runDiagnostics();
results = taskBckg.runDiagnostics(gui);
Also, please edit all your code so that it follows Java naming conventions. All class names should begin with a capital letter. This makes it much easier for others to understand what your code is doing.
I will have a guess as well based on the message you are trying to display, since the question lacks some essential information. Based on the
"Now running diagnostics... Please wait!"
message, I will assume you are running diagnostics and trying to update the UI on the same thread. The code you posted contains no obvious mistakes which would explain why your call
gui1.setCurStatus("Now running diagnostics... Please wait!");
would not update the label contents.
What you have to do is all explained in the Swing concurrency tutorial. The main point is that you update the UI on the Event dispatch thread, and you never perform heavy calculations on that thread since that will block the UI, leading to a terrible user experience. Heavy calculations should be done on a worker thread, for example by using the SwingWorker class, and only the update of the UI (for example for reporting progress) should happen on the EDT.
With this information and links you should be able to find all relevant information. Also on this site you will find multiple examples on how to use SwingWorker to perform background calculations and updating the UI, like for example my answer on a previous question
Related to my previous question: Call repaint from another class in Java?
I'm new to Java and I've had a look at some tutorials on SwingWorker. Yet, I'm unsure how to implement it with the example code I gave in the previous question.
Can anyone please explain how to use SwingWorker with regards to my code snippet and/or point me towards a decent tutorial? I have looked but I'm not sure I understand yet.
Generally, SwingWorker is used to perform long-running tasks in Swing.
Running long-running tasks on the Event Dispatch Thread (EDT) can cause the GUI to lock up, so one of the things which were done is to use SwingUtilities.invokeLater and invokeAndWait which keeps the GUI responsive by which prioritizing the other AWT events before running the desired task (in the form of a Runnable).
However, the problem with SwingUtilities is that it didn't allow returning data from the the executed Runnable to the original method. This is what SwingWorker was designed to address.
The Java Tutorial has a section on SwingWorker.
Here's an example where a SwingWorker is used to execute a time-consuming task on a separate thread, and displays a message box a second later with the answer.
First off, a class extending SwingWorker will be made:
class AnswerWorker extends SwingWorker<Integer, Integer>
{
protected Integer doInBackground() throws Exception
{
// Do a time-consuming task.
Thread.sleep(1000);
return 42;
}
protected void done()
{
try
{
JOptionPane.showMessageDialog(f, get());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
The return type of the doInBackground and get methods are specified as the first type of the SwingWorker, and the second type is the type used to return for the publish and process methods, which are not used in this example.
Then, in order to invoke the SwingWorker, the execute method is called. In this example, we'll hook an ActionListener to a JButton to execute the AnswerWorker:
JButton b = new JButton("Answer!");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
new AnswerWorker().execute();
}
});
The above button can be added to a JFrame, and clicked on to get a message box a second later. The following can be used to initialize the GUI for a Swing application:
private void makeGUI()
{
final JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new FlowLayout());
// include: "class AnswerWorker" code here.
// include: "JButton" b code here.
f.getContentPane().add(b);
f.getContentPane().add(new JButton("Nothing"));
f.pack();
f.setVisible(true);
}
Once the application is run, there will be two buttons. One labeled "Answer!" and another "Nothing". When one clicks on the "Answer!" button, nothing will happen at first, but clicking on the "Nothing" button will work and demonstrate that the GUI is responsive.
And, one second later, the result of the AnswerWorker will appear in the message box.
Agree:
Running long-running tasks on the Event Dispatch Thread (EDT) can cause the GUI to lock up.
Do not agree:
so one of the things which were done is to use SwingUtilities.invokeLater and invokeAndWait which keeps the GUI responsive..
invokeLater still runs the code on the EDT, and can freeze your UI!! Try this:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
At least I, cannot move my mouse once I click the button which triggers the actionPerformed with the above code. Am I missing something?