This is my first time trying to use Swing components for a GUI, I am trying to implement a solution that allows a user to run 2 asynchronous remote commands.
The two commands in a nutshell are :
execute command on remote server
download data
My problem is that though I've employed a SwingWorker, my main menu still freezes. Moreover, the SwingWorker is passed through and the second command is execute prior to the first command being added.
I've gone through many, many examples and explanations online, and I feel like I'm still missing some crucial intuition that would allow me to make this work.
What is the problem with my swing worker, in that it will still freeze the GUI and isn't waiting until after completion to do the rest of the code?
Edit: Ultimately I want to also create a progress bar, which shows the amount of time left in the task before completion.
GUI Class - Basic GUI with Buttons
public TestGUI(){
//code to show Gui with buttons
//code also contains mouse listener:
btn.addMouseListener(new MouseAdapter() { // listener
#Override
public void mousePressed(MouseEvent e){
SampleClass.doRemoteTask();
}
});
}
Sample Class - Run Remote Command using Swing Worker in Background
public SampleClass(){
public static void doRemoteTask(){
//create sample worker
final SampleWorker sampleworker = new SampleWorker(command, session);
//add change listener to wait for state change.
sampleworker.addPropertyChangeListener(new PropertyChangeListener(){
#Override
public void propertyChange(PropertyChangeEvent event) {
if(StateValue.DONE == sampleworker.getState()){
try {
Integer i = sampleworker.get();
} catch (InterruptedException | ExecutionException e) {
System.out.println("Could not get");
e.printStackTrace();
}
};
}
});
//do other stuff after the state DONE
sampleworker.execute(); //sample worker is a long task. This is why it was created in the Swing Worker.
}
}
Sample Worker Class - Run Command in Background
protected Integer doInBackground() throws Exception {
byte[] tmp=new byte[1024];
int j = 0;
publish("Start");
while(true){
//do stuff
if(j % 100 == 0){
setProgress(j);
}
//write to System.out then exit
break;
}
try{Thread.sleep(1000);}catch(Exception ee){}
}
publish("Completed");
return j;
}
Related
basically, I have this code which was initially working with console i/o now I have to connect it to UI. It may be completely wrong, I've tried multiple things although it still ends up with freezing the GUI.
I've tried to redirect console I/O to GUI scrollpane, but the GUI freezes anyway. Probably it has to do something with threads, but I have limited knowledge on it so I need the deeper explanation how to implement it in this current situation.
This is the button on GUI class containing the method that needs to change this GUI.
public class GUI {
...
btnNext.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
controller.startTest(index, idUser);
}
});
}
This is the method startTest from another class which contains instance of Question class.
public int startTest() {
for (int i = 0; i < this.numberofQuestions; i++) {
Question qt = this.q[i];
qt.askQuestion(); <--- This needs to change Label in GUI
if(!qt.userAnswer()) <--- This needs to get string from TextField
decreaseScore(1);
}
return actScore();
}
askQuestion method:
public void askQuestion() {
System.out.println(getQuestion());
/* I've tried to change staticaly declared frame in GUI from there */
}
userAnswer method:
public boolean userAnswer() {
#SuppressWarnings("resource")
Scanner scanner = new Scanner(System.in);
if( Objects.equals(getAnswer(),userInput) ) {
System.out.println("Correct");
return true;
}
System.out.println("False");
return false;
}
Thanks for help.
You're correct in thinking that it related to threads.
When you try executing code that will take a long time to process (eg. downloading a large file) in the swing thread, the swing thread will pause to complete execution and cause the GUI to freeze. This is solved by executing the long running code in a separate thread.
As Sergiy Medvynskyy pointed out in his comment, you need to implement the long running code in the SwingWorker class.
A good way to implement it would be this:
public class TestWorker extends SwingWorker<Integer, String> {
#Override
protected Integer doInBackground() throws Exception {
//This is where you execute the long running
//code
controller.startTest(index, idUser);
publish("Finish");
}
#Override
protected void process(List<String> chunks) {
//Called when the task has finished executing.
//This is where you can update your GUI when
//the task is complete or when you want to
//notify the user of a change.
}
}
Use TestWorker.execute() to start the worker.
This website provides a good example on how to use
the SwingWorker class.
As other answers pointed out, doing heavy work on the GUI thread will freeze the GUI. You can use a SwingWorker for that, but in many cases a simple Thread does the job:
Thread t = new Thread(){
#Override
public void run(){
// do stuff
}
};
t.start();
Or if you use Java 8+:
Thread t = new Thread(() -> {
// do stuff
});
t.start();
At first see my GUI:
Background:
I am trying to use some functions (CRUD) of MongoDB collection by a GUI.
At first user has to choose an existing Database from the very first ComboBox. When user chooses an option private void jComboBoxDBNamePopupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) function would load all the collections under that database. Here blog is chosen.
Then user would choose a collection among the existing collections from second ComboBox. When user chooses a collection private void jComboBoxCollectionNamePopupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) function would call a function named refreshTable() to load all the Documents under that collection. Here posts collection is chosen.
While choosing option from second ComboBox, if the chosen collection have more than thousand Documents, it would ask user to confirm whether he actually wants to load the Documents or not as it might take time or could be a memory issue.
It confirmation would be done through a JOptionPane.showConfirmDialog(...).
Problem:
While choosing collection posts, it shows the Dialog Box. But clicking on Yes or No does not gives any response. But why?
The buttons are red underlined in the picture.
Code:
My public boolean refreshTable() function is:
public boolean refreshTable() {
collections = db.getCollection((String) jComboBoxCollectionName.getSelectedItem());
if(collections.count()>1000){
int ret = JOptionPane.showConfirmDialog(this, "The table contains more than thousand row.\nThis may slow down the process and could cause Memory error.Are you sure to continue?","Too Large Collection ("+collections.count()+" Rows)",YES_NO_OPTION, INFORMATION_MESSAGE);
if(ret!=YES_OPTION) return true;
}
//Some irrelevant codes
return false;
}
Study:
I have searched it on Google and could not solve the issue. The followings are some questions on StackOverflow, but I could not figure out solution from them.
JOptionPane Confirm Dialog Box
JOptionPane YES/No Options Confirm Dialog Box Issue -Java
JoptionPane ShowConfirmDialog
Project Repository:
My Project repository is here. You could have a look if needed.
This is probably because JOptionPane methods should be called on the event dispatch thread (EDT) of Swing while you are inovoking it on a different thread.
You should try calling refreshTable by using SwingUtilities utility methods, eg:
SwingUtilities.invokeLater(() -> refreshTable());
A guess only since we don't have a minimal example program from you -- but if this JOptionPane is called amidst long-running or CPU-intensive code, and if the code is run on the Swing event thread, it will freeze the Swing event thread and thus freeze your GUI. If you're not taking care to call long-running or CPU-intensive code within background threads, you will want to do so, such as by use of a SwingWorker.
I looked at your code, and you're starting your GUI on the EDT:
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
// this will be run on the EDT
new UserInterface().setVisible(true);
}
});
and so Jack's recommendation is unnecessary, but you're making all your database calls on the EDT and are not following Swing threading rules, something that will freeze your pogram, and so my recommendations are what you need to follow. You will first need to learn the basics of Swing threading, and so I recommend that you look at this tutorial: Lesson: Concurrency in Swing
You could get by with two SwingWorkers and two JPropertyChangeListeners:
A SwingWorker, say called GetCollectionsWorker, that extends SwingWorker<Collections, Void>. I have no idea what the first generic parameter should be other than it should be whatever type your collections variable is. This worker would simply return db.getCollection(selection); from its doInBackground() method.
A SwingWorker, say called CreateTableModelWorker, that extends SwingWorker<DefaultTableModel, Void> and that is passed collections into its constructor and that creates your DefaultTableModel from the data held by Collections.
A PropertyChangeListener, say called GetCollectionsListener, that listens on the CreateTableModelWorker, for when it is done, in other words its newValue is SwingWorker.StateValue.DONE, and then that asks the user if he wants to continue, and if so, this calls the second SwingWorker.
A PropertyChangeListener, say called CreateTableModelWorker, that listens to the CreateTableModelWorker for when it is done, and that puts the table model into the JTable.
For what it's worth, I'd implement somewhere along these lines in the code below. Again, the big unknown for me is what type the collections variable represents, and for that reason, the first SwingWorker's generic parameter would need to be fixed and changed from Collections to whatever it is that you're using:
// change to a void method
public void refreshTable() {
String selection = (String) jComboBoxCollectionName.getSelectedItem();
// SwingWorker to get collections
GetCollectionsWorker getCollectionsWorker = new GetCollectionsWorker(selection);
getCollectionsWorker.addPropertyChangeListener(new GetCollectionsListener());
getCollectionsWorker.execute(); // run worker on background thread
}
// FIXME: Generic type Collections is wrong -- need to use correct type, whatever type collections is
private class GetCollectionsWorker extends SwingWorker<Collections, Void> {
private String selection;
public GetCollectionsWorker(String selection) {
this.selection = selection;
}
#Override
protected Collections doInBackground() throws Exception {
// do database work here in a background thread
return db.getCollection(selection);
}
}
// class that listens for completion of the GetCollectionsWorker worker
class GetCollectionsListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
// all this is done on the EDT
if (SwingWorker.StateValue.DONE == evt.getNewValue()) {
// if worker is done, first get worker from listener
GetCollectionsWorker worker = (GetCollectionsWorker) evt.getSource();
try {
// then extract the data that it's returning
collections = worker.get();
// then offer user option of continuing or not
if (collections.count() > 1000) {
int ret = JOptionPane.showConfirmDialog(UserInterface.this,
"The table contains more than thousand row.\nThis may slow down the process and could cause Memory error.Are you sure to continue?",
"Too Large Collection (" + collections.count() + " Rows)", YES_NO_OPTION, INFORMATION_MESSAGE);
if (ret != YES_OPTION) {
return;
}
}
// our next worker, one to create table model
CreateTableModelWorker createModelWorker = new CreateTableModelWorker(collections);
// be notified when it is done
createModelWorker.addPropertyChangeListener(new CreateModelListener());
createModelWorker.execute(); // run on background thread
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
// worker to create table model on background thread
class CreateTableModelWorker extends SwingWorker<DefaultTableModel, Void> {
private Collections collections;
public CreateTableModelWorker(Collections collections) {
this.collections = collections;
}
#Override
protected DefaultTableModel doInBackground() throws Exception {
documents = collections.find().into(new ArrayList<Document>());
Set<String> colNames = new HashSet<>();
for (Document doc : documents) {
for (String key : doc.keySet()) {
colNames.add(key);
}
}
columns = colNames.toArray();
Object[][] elements = new Object[documents.size()][columns.length];
int docNo = 0;
for (int i = 0; i < columns.length; i++) {
if (((String) columns[i]).equalsIgnoreCase("_id")) {
_idcol = i;
break;
}
}
for (Document doc : documents) {
for (int i = 0; i < columns.length; i++) {
if (doc.containsKey(columns[i])) {
elements[docNo][i] = doc.get(columns[i]);
}
}
docNo++;
}
DefaultTableModel model = new DefaultTableModel(elements, columns);
return model;
}
}
private class CreateModelListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
// all this is done on the EDT
if (SwingWorker.StateValue.DONE == evt.getNewValue()) {
// if worker is done, first get worker from listener
CreateTableModelWorker worker = (CreateTableModelWorker) evt.getSource();
try {
DefaultTableModel model = worker.get();
jTableResultTable.setModel(model);
UserInterface.this.model = model;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
I hava a GUI that has a button that need to be pressed periodically in the sense it should pause in between. But i am not able to do that. I have tried with Thread.sleep(). Below is the code.
protected Object doInBackground() throws Exception {
while(true){
btnSend.doClick();
try {
Thread.sleep(2000);
continue;
} catch (InterruptedException e1) {
}
}
return null;
}
Can anyone tell me where i am going wrong and how to solve it?
You shouldn't use a SwingWorker for this since you should not call doClick() off the event thread. If al you want to do is make this call intermittently, then simply use a Swing Timer for calls that need to be done intermittently and on the event thread.
int timerDelay = 2000;
new Timer(timerDelay, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
btnSend.doClick();
}
}).start();
Edit
You ask in comment:
But i need to update another UI on that button click event. If i dont use swingworker the other UI will freeze. I have to use swingworker. Is it possible to do so?
You've got it wrong, I fear. The button click and GUI code should all be on the EDT. Any background non-GUI code that is generated by the button click's ActionListener should be done in a SwingWorker but not the click itself.
e.g.,
elsewhere
btnSend.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
new SwingWorker<Void, Void>() {
public Void doInBackground() throws Exception {
//.....
return null;
}
}
}
});
If you need more specific advice regarding your situation, then you'll want to consider creating and posting a Minimal, Complete, and Verifiable Example Program where you condense your code into the smallest bit that still compiles and runs, has no outside dependencies (such as need to link to a database or images), has no extra code that's not relevant to your problem, but still demonstrates your problem.
I have problem while working with JFrame, which get freezes while
running the code continuously. Below is my code:
On clicking on btnRun, I called the function MainLoop():
ActionListener btnRun_Click = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
MainLoop();
}
};
Implementation of MainLoop():
void MainLoop()
{
Hopper = new CHopper(this);
System.out.println(Hopper);
btnRun.setEnabled(false);
textBox1.setText("");
Hopper.getM_cmd().ComPort = helpers.Global.ComPort;
Hopper.getM_cmd().SSPAddress = helpers.Global.SSPAddress;
Hopper.getM_cmd().Timeout = 2000;
Hopper.getM_cmd().RetryLevel = 3;
System.out.println("In MainLoop: " + Hopper);
// First connect to the validator
if (ConnectToValidator(10, 3))
{
btnHalt.setEnabled(true);
Running = true;
textBox1.append("\r\nPoll Loop\r\n"
+ "*********************************\r\n");
}
// This loop won't run until the validator is connected
while (Running)
{
// poll the validator
if (!Hopper.DoPoll(textBox1))
{
// If the poll fails, try to reconnect
textBox1.append("Attempting to reconnect...\r\n");
if (!ConnectToValidator(10, 3))
{
// If it fails after 5 attempts, exit the loop
Running = false;
}
}
// tick the timer
// timer1.start();
// update form
UpdateUI();
// setup dynamic elements of win form once
if (!bFormSetup)
{
SetupFormLayout();
bFormSetup = true;
}
}
//close com port
Hopper.getM_eSSP().CloseComPort();
btnRun.setEnabled(true);
btnHalt.setEnabled(false);
}
In the MainLoop() function, the while loop is running continuesly until the Running is true problem is that if i want to stop that while loop i have to set Running to false which is done at another button btnHalt:
ActionListener btnHalt_Click = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
textBox1.append("Poll loop stopped\r\n");
System.out.println("Hoper Stopped");
Running = false;
}
};
but btnHalt is not responding, whole frame is get freeze, also not
showing any log in the textarea.
Swing is a single thread framework. That is, there is a single thread responsible for dispatching all the events to all the components, including repaint requests.
Any action which stops/blocks this thread will cause your UI to "hang".
The first rule of Swing, NEVER run any blocking or time consuming tasks on the Event Dispatching Thread, instead, you should use a background thread.
This runs you smack into the second rule of Swing. Never create, modify or interact with any UI component outside of the EDT.
There are a number of ways you can fix this. You could use SwingUtilities.invokeLater or a SwingWorker.
SwingWorker is generally easier, as it provides a number of simple to use methods that automatically re-sync there calls to the EDT.
Take a read through Concurrency in Swing
Updated
Just so you understand ;)
Your MainLoop method should not be executed within the context of the EDT, this is very bad.
Also, you should not be interacting with any UI component from any thread other the then the EDT.
First off I've been working with Java's Concurrency package quite a bit lately but I have found an issue that I am stuck on. I want to have and Application and the Application can have a SplashScreen with a status bar and the loading of other data. So I decided to use SwingUtilities.invokeAndWait( call the splash component here ). The SplashScreen then appears with a JProgressBar and runs a group of threads. But I can't seem to get a good handle on things. I've looked over SwingWorker and tried using it for this purpose but the thread just returns. Here is a bit of pseudo code. and the points I'm trying to achieve.
Have an Application that has a SplashScreen that pauses while loading info
Be able to run multiple threads under the SplashScreen
Have the progress bar of the SplashScreen Update-able yet not exit until all threads are done.
Launching splash screen
try {
SwingUtilities.invokeAndWait( SplashScreen );
} catch (InterruptedException e) {
} catch (InvocationTargetException e) { }
Splash screen construction
SplashScreen extends JFrame implements Runnable{
public void run() {
//run threads
//while updating status bar
}
}
I have tried many things including SwingWorkers, Threads using CountDownLatch's, and others. The CountDownLatch's actually worked in the manner I wanted to do the processing but I was unable to update the GUI. When using the SwingWorkers either the invokeAndWait was basically nullified (which is their purpose) or it wouldn't update the GUI still even when using a PropertyChangedListener. If someone else has a couple ideas it would be great to hear them. Thanks in advance.
I actually got ready to post better code to help out and found my solution. I thank you for all who helped.
For running a series of operations in the background and reporting progress, use SwingWorker.
The background method does the background processing.
Use the publish method to post periodic status updates.
Override the process method to handle the updates (process always executes on the EDT).
progressBar = new JProgressBar();
sw = new SwingWorker<Boolean,Integer>() {
protected Boolean doInBackground() throws Exception {
// If any of the operations fail, return false to notify done()
// Do thing 1
publish(25); // 25% done
// Do thing 2
publish(50); // 50% done
// Do thing 3
publish(75); // 75% done
// Do thing 4
return true;
}
protected void process(List<Integer> chunks) {
for (Integer i : chunks)
progressBar.setValue(i);
}
protected void done() {
try {
boolean b = get();
if (b)
progressBar.setValue(100); // 100% done
else
// Notify the user processing failed
}
catch (InterruptedException ex) {
// Notify the user processing was interrupted
}
catch (ExecutionException ex) {
// Notify the user processing raised an exception
}
}
};
Addendum:
This can be extended to multiple tasks, it just requires changing how you approach setting the progress bar. Here's what comes to mind:
Have an array of completion counter, one per task.
int[] completions = new int[numTasks];
Arrays.fill(completions,0);
Start the SwingWorkers, each passed an index number. The process or done methods then call something like this to update the overall progress bar.
void update(int index, int percComplete) {
completions[index] = percComplete;
int total = 0;
for(int comp: completions)
total += comp/numTasks;
overallPB.setValue(total);
}
Optionally, display a JProgressBar per task.
Addendum 2:
If the tasks vary in completion time (eg, cache hit vs cache miss), you may want to investigate ProgressMonitor. It's a progress dialog that only appears if the task takes more than some (configurable, default 500ms) amount of time.
No need to call the frame inside invokeAndWait but you should update progress bar state like this.
try {
SwingUtilities.invokeAndWait( new Runnable() {
public void run() {
//update state of the progress bar here
}
});
} catch (InterruptedException e) {
} catch (InvocationTargetException e) { }