I have a java program that load a text file as input, read its content, modify some strings and then prints the result to a textarea. Due to several seconds required by this operation i would like to show a JProgressBar during this activity in order to inform the user that the execution is in progress and when the activity is completed close the dialog containing the JprogressBar and print the results.
Here is the code:
JButton btnCaricaFile = new JButton("Load text file");
panel.add(btnCaricaFile);
btnCaricaFile.setIcon(UIManager.getIcon("FileView.directoryIcon"));
btnCaricaFile.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
//JFileChooser choice = null;
final JFileChooser choice = new JFileChooser(userDir +"/Desktop");
int option = choice.showOpenDialog(GUI.this);
if (option == JFileChooser.APPROVE_OPTION) {
final JDialog dialog = new JDialog(GUI.this, "In progress", true);
JProgressBar progressBar = new JProgressBar(0, 100);
progressBar.setIndeterminate(true);
dialog.getContentPane().add(BorderLayout.CENTER, progressBar);
dialog.getContentPane().add(BorderLayout.NORTH, new JLabel("Elaborating strings..."));
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.setSize(300, 75);
dialog.setLocationRelativeTo(GUI.this);
Thread t = new Thread(new Runnable() {
public void run() {
dialog.setVisible(true);
File file = choice.getSelectedFile();
lista.clear();
textArea.setText("");
lista = loadFile.openFile(file);
for(int i=0; i<lista.size(); i++) {
textArea.append(lista.get(i)+"\n");
}
dialog.setVisible(false);
}
});
t.start();
}
}
});
For this purpose i'm using a JDialog as container for the JProgressBar executed by an appropriate thread. The problem is that the progress bar is shown for an infinite time and is not printed anything to the textarea.
Could you help me to solve this?
Thanks
Yes, you're creating a background thread for your file reading, good, but you're also making Swing calls from within this same background thread, not good, and this is likely tying up the Swing event thread inappropriately. The key is to keep your threading separate -- background work goes in the background thread, and Swing work goes only in the Swing thread. Please read Lesson: Concurrency in Swing fore more on this.
Myself, I would create and use a SwingWorker<Void, String>, and use the worker's publish/process method pair to send Strings to the JTextArea safely.
For example, something like...
final JDialog dialog = new JDialog(GUI.this, "In progress", true);
JProgressBar progressBar = new JProgressBar(0, 100);
progressBar.setIndeterminate(true);
dialog.getContentPane().add(BorderLayout.CENTER, progressBar);
dialog.getContentPane().add(BorderLayout.NORTH, new JLabel("Elaborating strings..."));
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.setSize(300, 75);
dialog.setLocationRelativeTo(GUI.this);
lista.clear();
SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
#Override
public Void doInBackground() throws Exception {
// all called *off* the event thread
lista = loadFile.openFile(file);
for (int i = 0; i < lista.size(); i++) {
publish(lista.get(i));
}
return null;
}
#Override
protected void process(List<String> chunks) {
// called on the event thread
for (String chunk : chunks) {
textArea.append(chunk + "\n");
}
}
// called on the event thread
public void done() {
dialog.setVisible(false);
// should call get() here to catch and handle
// any exceptions that the worker might have thrown
}
};
worker.execute();
dialog.setVisible(true); // call this last since dialog is modal
Note: code not tested nor compiled
Related
I have to set two Dialogs and i want to Stop the first one and then start the second. Can anyone please help me to fix it
JOptionPane msg = new JOptionPane("your score is: " + getScore(), JOptionPane.INFORMATION_MESSAGE);
final JDialog dlg = msg.createDialog("Game Over");
dlg.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
dlg.dispose();
}
}).start();
dlg.setVisible(true);
the second Dialog would be the same like
JOptionPane message = new JOptionPane("Highscore: " + getHighscore(), JOptionPane.INFORMATION_MESSAGE);
final JDialog dialog = message.createDialog("Game Over");
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
now i want to start this Dialog after the first will be closed.
Recommendations:
For the sake of Swing thread safety, use a Swing Timer rather than directly using a background thread.
Make it a non-repeating timer.
Inside the timer's ActionListener, close/dispose of the current dialog and open the 2nd.
e.g., (code not tested)
final JDialog myModalDialog = ....;
final JDialog mySecondDialog = ....;
int timerDelay = 3000;
Timer timer = new Timer(timerDelay, e -> {
myModalDialog.dispose();
mySecondDialog.setVisible(true);
});
timer.setRepeats(false);
timer.start();
myModalDialog.setVisible(true);
Alternatively: use a single dialog and swap views using a CardLayout tutorial
I have a JButton which has an ActionListener, which does its job as many times as I click it. Below is my code:
mouseListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JButton source = (JButton) e.getSource();
source.setEnabled(false);
try {
RequestCommon.ctbCookie = jtf.getText();
System.out.println(RequestCommon.ctbCookie);
HttpURLConnection connection = HttpURLConnectionBuilder.getConnection(RequestCommon.login, RequestCommon.getCtb888Headers());
String connectionOuput = HttpURLConnectionBuilder.getConnectionOuput(connection);
System.out.println(connectionOuput);
new Player(new BufferedInputStream(new FileInputStream(new File("sounds/8.mp3")))).play();
} catch (IOException e1) {
e1.printStackTrace();
} catch (JavaLayerException e1) {
e1.printStackTrace();
}
source.setEnabled(true);
}
}
jb1.addActionListener(mouseListener);
I want it so that no matter how many times I click while the job is running it won't execute again. When the job is done, if I click again, the job will run again. I don't know how to do that, please tell me if you know, thanks!
Long running code should NOT execute on the Event Dispatch Thread (EDT). You need to start a separate Thread to do you HTTP request.
The easiest way to do this is to use a SwingWorker. You can disable the button before you start the worker and then the worker has a done() method that is invoked and you can enable the button.
Read the section from the Swing tutorial on Concurrency in Swing for more information about the EDT and a working example of a Swing worker.
Edit:
People seem confused about event handling. The listeners for an event are invoked before the next event is handled. So in the case of "double clicking" on the button. The button is disable on the first click and the long running task is started. The second click is then received on the disable button so the ActionListener is not invoked.
Here is some old code I have lying around which was written before a SwingWorker existed. The basic logic for the "Start in New Thread" button is:
disable the button so it can't be click while processing is happening
simulate a long running task by looping 10 times and sleeping
enable the button so the task can be done again
Here is the code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/*
* A couple of notes about long running tasks and GUI updates:
*
* 1) all GUI painting should be done in the event thread
* 2) GUI painting is not done until the event thread processing is done
*
* This means that long running code (database access, file processing ...)
* should not be done in the event thread. A new thread can be created for
* these tasks.
*
* Most Swing methods are not thread safe. If the long running task needs
* to update the GUI for any reason then the SwingUtilities class
* should be used to add code to the event thread.
*
* See the Swing tutorial on "Using Threads" for more information
* http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html
*/
public class InvokeLaterTest extends JFrame
implements ActionListener, Runnable
{
JLabel status;
JButton eventThread;
JButton newThread;
JButton stop;
Thread thread;
int i;
boolean stopProcessing;
public InvokeLaterTest()
{
status = new JLabel( "Ready to Process:" );
status.setHorizontalAlignment( JLabel.CENTER );
getContentPane().add(status, BorderLayout.NORTH);
eventThread = new JButton( "Start in Event Thread" );
eventThread.addActionListener( this );
getContentPane().add(eventThread, BorderLayout.WEST);
newThread = new JButton( "Start in New Thread" );
newThread.addActionListener( this );
getContentPane().add(newThread, BorderLayout.EAST);
stop = new JButton( "Stop Processing" );
stop.addActionListener( this );
getContentPane().add(stop, BorderLayout.SOUTH);
}
public void actionPerformed(ActionEvent e)
{
// Code is executing in Event thread so label will not be updated
// and the Stop button will not be enabled.
if (e.getSource() == eventThread)
{
stopProcessing = false;
run();
}
// Code is executing in a new thread so label will be updated
else if (e.getSource() == newThread)
{
stopProcessing = false;
thread = new Thread( this );
thread.start();
}
else
{
stopProcessing = true;
status.setText("Processing Stopped");
setButtons( true );
}
}
public void run()
{
setButtons( false );
for (i = 1; i < 10; i++)
{
if ( stopProcessing ) return;
System.out.println("ProcessingFile: " + i);
// SwingUtilities makes sure code is executed in the event thread.
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
status.setText("Processing File: " + i);
status.paintImmediately(status.getBounds());
}
});
// simulate log running task
try { Thread.sleep(1000); }
catch (Exception e) {}
}
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
status.setText("Finished Processing");
setButtons( true );
}
});
}
private void setButtons(boolean value)
{
eventThread.setEnabled( value );
newThread.setEnabled( value );
}
public static void main(String[] args)
{
JFrame frame = new InvokeLaterTest();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.show();
}
}
A SwingWorker is similar to the above logic but:
you need to disable the button outside the SwingWorker
the worker will create the Thread for you and execute the code
when the worker is finished the done() method of the worker is invoked so you can enable the button.
You should use if and check if the button is enabled before executing your code.
JButton source = (JButton) e.getSource();
if(source.isEnabled()) {
.
.
.
execute your code
JButton source = (JButton) e.getSource();
if (source.isEnabled()) {
Executors.newSingleThreadScheduledExecutor().execute(() -> {
source.setEnabled(false);
--your code here--
source.setEnabled(true);
}
);
}
};
Added a completedTime variable to hold the timestamp as when the action is complete, and every event has the time when it is generated, compare and return if it is less than the completed time
long completedTime;
mouseListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JButton source = (JButton) e.getSource();
long timeStamp = e.getWhen();
if (timeStamp < completedTime) {
System.out.println("returned");
return;
}
// source.setEnabled(false);
try {
RequestCommon.ctbCookie = jtf.getText();
System.out.println( RequestCommon.ctbCookie );
HttpURLConnection connection = HttpURLConnectionBuilder.getConnection(RequestCommon.login, RequestCommon.getCtb888Headers());
String connectionOuput = HttpURLConnectionBuilder.getConnectionOuput(connection);
System.out.println(connectionOuput);
new Player(new BufferedInputStream(new FileInputStream(new File("sounds/8.mp3")))).play();
} catch (IOException e1) {
e1.printStackTrace();
} catch (JavaLayerException e1) {
e1.printStackTrace();
}
// source.setEnabled(true);
completedTime = System.currentTimeMillis();
}
};
I have written the following code to generate please wait JDialog while generation of decision tree but it opens up and appears to be blank
public JDialog pleasewait()
{
JDialog dialog = new JDialog();
JLabel label = new JLabel("Please wait...");
label.setIcon(new javax.swing.ImageIcon(getClass().getResource("/decision_tree_runner/load.gif"))); // NOI18N
dialog.setBackground(Color.BLACK);
dialog.setLocationRelativeTo(null);
dialog.setTitle("Please Wait...");
dialog.add(label);
dialog.pack();
return dialog;
}
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
JDialog dialog = pleasewait();
dialog.repaint();
dialog.setVisible(true);
FypProject fyp_project = new FypProject();
try {fyp_project.main_fypproject();} catch (SQLException ex) {}
dialog.setVisible(false);
}
It is likely that fyp_project.main_fypproject() is a long running/blocking call, which when called from within the context of the Event Dispatching Thread, will stop it from been able to process new events, including the repaint request.
Consider using something like a SwingWorker, opening the dialog first, execute the worker and when it's done method is called, close the dialog
Take a look at Concurrency in Swing and Worker Threads and SwingWorker for more details
I think it has to do with where in the dialog the JLabel gets added. The lack of our layout manager makes this difficult.
Try adding this before you add the JLabel:
dialog.setLayout(new GridLayout());
and remove:
dialog.pack();
i did the following steps
created separate form for pleasewait
created thread for fyp_project.main_fypproject()
passed object of form to thread class
in run method setvisible() option of pleasewait to false
public class thread_for_pleasewait implements Runnable{
Thread t ;
please_wait_form pwf;
decision_tree dt;
FypProject fyp_project = new FypProject();
#Override
public void run()
{
String[] args = null;
try {
fyp_project.main_fypproject();
pwf.setVisible(false);
dt.setVisible(true);
} catch (SQLException ex) {
Logger.getLogger(thread_for_pleasewait.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void start(please_wait_form pwf,decision_tree dt)
{
t = new Thread(this);
t.start();
this.pwf=pwf;
this.dt=dt;
}
}
I want to show the Processing Icon on the button once it is clicked to make understand the user that application is loading the page, he has to wait until it is completed.
Following is the Wait Cursor I got for the below code:
The code I developed to meet my above requirement is as follows:
public static boolean waitCursorIsShowing;
public Cursor waitCursor, defaultCursor;
waitCursor = new Cursor(Cursor.WAIT_CURSOR);
defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR);
waitCursorIsShowing = true;
if (waitCursorIsShowing)
{
JFrame frame.setCursor(waitCursor);
-----------------------------------------------
/* code to load the page/ perform the business logic */
-----------------------------------------------
waitCursorIsShowing = false;
frame.setCursor(defaultCursor);
}
This code is working well according to my requirement. But when I want to show the other Processing Image Icon on the button when it is clicked, the result I am getting is in reverse order. I mean when the button is clicked and page is loading, no Processing Image Icon is shown on the button, but when action is performed and button is released after Page load, then the application is showing the Processing Image Icon on the button.
Following is the Processing Image Icon I am getting for the below code:
Please help me where I am coding incorrect. Following is the modified code.
public static boolean waitCursorIsShowing;
public Cursor waitCursor, defaultCursor;
waitCursor = new Cursor(Cursor.WAIT_CURSOR);
defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR);
waitCursorIsShowing = true;
if (waitCursorIsShowing)
{
URL url = getClass().getResource("/Images/loader8.gif");
b4.setText("Loading");
b4.setIcon(new ImageIcon(url));
-----------------------------------------------
/* code to load the page/ perform the business logic */
-----------------------------------------------
waitCursorIsShowing = false;
b4.setIcon(new ImageIcon());
b4.setText("Submit");
}
b4 is the button on which I want to get the Processing Image Icon.
How to modify my code to customize the Icon on the button when it is clicked and application is loading the Page?
Remember that events are all processed on the event dispatch thread, which is the same thread that handles painting. If you do your page loading on that thread, then the frame will not be able to repaint until the loading is complete. Instead, do the loading on a background thread, and use a SwingUtilities.invokeLater() call to set the icon back when you are done.
You can use SwingWorker:
final JFrame frame = new JFrame("Example");
final JButton button = new JButton("Please, press me!");
frame.getContentPane().add(button, BorderLayout.NORTH);
final JTextPane pane = new JTextPane();
frame.getContentPane().add(pane);
final Cursor defaultCursor = Cursor.getDefaultCursor();
final Cursor busyCursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
final ImageIcon loadingIcon = new ImageIcon(ImageIO.read(getClass().getResource("/Images/loader8.gif")));
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
button.setIcon(loadingIcon);
frame.setCursor(busyCursor);
SwingWorker<String, Object> worker = new SwingWorker<String, Object>() {
#Override
public String doInBackground() throws InterruptedException {
// do some work...
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 25; i++) {
sb.append((char)(i + 65));
Thread.sleep(25);
}
return sb.toString();
}
#Override
protected void done() {
try {
//set result of doInBackground work
pane.setText(get());
frame.setCursor(defaultCursor);
button.setIcon(null);
} catch (Exception ignore) {
}
}
};
worker.execute();
}
});
frame.setSize(new Dimension(400, 300));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
So in this chunk of code:
//Actions performed when an event occurs.
public void actionPerformed(ActionEvent event)
{
String command = event.getActionCommand();
//If btnConvertDocuments is clicked, the FileConverter method is called and the button is then disabled [so as to prevent duplicates].
if (command.equals("w"))
{
new Thread(new Runnable()
{
public void run()
{
FileConverter fc = new FileConverter();
}
}).start();
btnConvertDocuments.setEnabled(false);
//Validation message ensuring completion of the step.
JOptionPane.showMessageDialog(this, "Step 1 Complete!", "Validation", JOptionPane.INFORMATION_MESSAGE);
}
It seems like the message dialog window pop-ups way too fast, before the FileConverter method isn't even finished being called. I was wondering if the placement of JOptionPane was correct, or if there was a way to delay a message until the method finished processing?
You can use the SwingWorker.
Have a look here, java tutorial.
SwingWorker worker = new SwingWorker<Void, Void>() {
#Override
public Void doInBackground() {
FileConverter fc = new FileConverter();
return null;
}
#Override
public void done() {
JOptionPane.showMessageDialog(this, "Step 1 Complete!", "Validation", JOptionPane.INFORMATION_MESSAGE);
}
};
You should use a Swing Timer with a delay, instead of using your own Thread and Runnable for this.
You can use Swing timers in two ways:
To perform a task once, after a delay.
For example, the tool tip manager uses Swing timers to determine when to show a tool tip and when to hide it.
To perform a task repeatedly.
For example, you might perform animation or update a component that displays progress toward a goal.
An example from the documentation:
int delay = 1000; //milliseconds
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
//...Perform a task...
}
};
Timer myTimer = new Timer(delay, taskPerformer);
myTimer.setRepeats(false);
myTimer.start();