I have a server call from the UI. It has response time is little high. So I was thinking to display a progress bar during data loading from the server. I have tried the following code using this approach to show the progress bar. Some where I am doing wrong I am not seeing the progress bar when I call the calculateResult() method on button click. I no need to display any percentage on the progress bar. It just needs to show that data is loading.
// The following code I have tried.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class MyProgressBarTest extends JFrame {
private static final long serialVersionUID = 1L;
private static JProgressBar progressBar;
public static void main(String[] args) {
MyProgressBarTest obj = new MyProgressBarTest();
obj.createGUI();
}
public void createGUI() {
JPanel panel = new JPanel();
JButton button = new JButton("Progress");
progressBar = new JProgressBar();
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
MyCustomProgressBarDialog progressBarObj = new MyCustomProgressBarDialog(progressBar);
progressBarObj.createProgressUI();
MyActionPerformer actionObj = new MyActionPerformer(progressBar);
actionObj.execute();
progressBarObj.setVisible(false);
}
});
// panel.add(progressBar);
panel.add(button);
add(panel);
setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
setLocationRelativeTo(null);
// pack();
setSize(200, 300);
setVisible(true);
}
}
class MyActionPerformer extends SwingWorker<String, Object> {
JProgressBar fProgressBar;
public MyActionPerformer(JProgressBar progressBar) {
this.fProgressBar = progressBar;
this.fProgressBar.setVisible(true);
this.fProgressBar.setIndeterminate(true);
}
protected String doInBackground() throws Exception {
calculateResult();
return "Finished";
}
protected void done() {
fProgressBar.setVisible(false);
}
public void calculateResult() {
for (int i = 0; i < 500000; i++) {
System.out.println("Progress Bar: " + i);
}
}
}
class MyCustomProgressBarDialog extends JDialog {
private static JProgressBar progressBar;
public MyCustomProgressBarDialog(JProgressBar progressBar) {
this.progressBar = progressBar;
}
public void createProgressUI() {
add(progressBar);
setLocationRelativeTo(null);
setSize(50, 20);
setVisible(true);
}
}
The reason your progress bar disappears immediately is your ActionListener
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
MyCustomProgressBarDialog progressBarObj = new MyCustomProgressBarDialog(progressBar);
progressBarObj.createProgressUI();
MyActionPerformer actionObj = new MyActionPerformer(progressBar);
actionObj.execute();
progressBarObj.setVisible(false);
}
});
The actionObj.execute(); method is not blocking (good thing or it would be useless) meaning that immediately after you start the SwingWorker with that call you will execute the progressBarObj.setVisible(false); statement.
This causes the progress bar dialog to disappear.
I can think of 2 solutions for this
Pass the dialog to the SwingWorker as well and call setVisible( false ) on the dialog in the done method of the SwingWorker
A SwingWorker fires PropertyChangeEvents which allow you to determine how far it progressed. You can use such a listener to hide the dialog when the calculations are finished
You can't see your progress bar because you are not adding it to your panel
A simple:
panel.add(PROGRESS_BAR);
panel.add(button);
will do the trick.
I hope it helped. Cheers
JProgressBar is a Component. It must be added to a Container in order to be visible. You haven't added yours anywhere.
Related
I am currently practicing OOP with Java.
I have created a GUI project via WindowBuilder with Eclipse IDE and below is the result.
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Example window = new Example();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public Example() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 450, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JProgressBar progressBar = new JProgressBar();
frame.getContentPane().add(progressBar, BorderLayout.CENTER);
}
What I am trying to do is to connect the JProgressBar to another class that has the actual task, to show the progress.
For example, if the other class contains the following code:
int i = 0;
while(i <= 100) {
progressBar.setValue(i);
i++;
}
how should I change the progressBar.setValue(i); part?
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section. Pay particular attention to the Concurrency in Swing section.
Here's the simplest working example I could create. As you can see in the picture, I caught the JProgressBar in the middle.
Each time you press the button, the progress bar will count from 0 to 100, one unit every 100 milliseconds.
In order to access the progress bar, you have to make it a class field or variable. You can then access the class field with a setter. Getters and setters are a basic Java concept. You can see another example of a plain Java getter/setter class in my JProgressBarModel class.
I used a Swing Timer to add a delay to the updating of the progress bar so you can see the bar update and simulate an actual long-running task. The actual work takes place in the WorkListener class. Because the code is inside an ActionListener, the Swing update of the progress bar takes place on the Event Dispatch Thread.
Here's the complete runnable code. I made all the additional classes inner classes so I could post the code as one block.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class JProgressBarExample implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new JProgressBarExample());
}
private JProgressBar progressBar;
private final JProgressBarModel model;
public JProgressBarExample() {
this.model = new JProgressBarModel();
}
#Override
public void run() {
JFrame frame = new JFrame("Progress Bar Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createMainPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createMainPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
progressBar = new JProgressBar();
panel.add(progressBar);
JButton button = new JButton("Start Process");
button.addActionListener(event -> {
model.setIndex(0);
setValue();
Timer timer = new Timer(100, new WorkListener(this, model));
timer.start();
});
panel.add(button);
return panel;
}
public void setValue() {
progressBar.setValue(model.getIndex());
}
public class WorkListener implements ActionListener {
private final JProgressBarExample view;
private final JProgressBarModel model;
public WorkListener(JProgressBarExample view, JProgressBarModel model) {
this.view = view;
this.model = model;
}
#Override
public void actionPerformed(ActionEvent event) {
Timer timer = (Timer) event.getSource();
int index = model.getIndex() + 1;
model.setIndex(index);
view.setValue();
if (index >= 100) {
timer.stop();
}
}
}
public class JProgressBarModel {
private int index;
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
}
One option is to do it similar to the frame part. You Example class has a field variable that could be directly accessible to your other code.
A better way would be to have a private field for the JProgressBar and a getProgressBar() method.
But currently you are using a method variable that is forgotten when initialize() returns.
I am trying to follow the Java best practices by not doing long tasks on the main thread (EDT). So I am planning to use a swingWorker with Modal Dialog. This way the modal dialog blocks the user for doing anything until that task is done and I can update status on the dialog while the process is taking place.
Now the problem is that with the modal dialog, it not only blocks the user but also nothing after setVisible gets called
So if I do
dialog.setVisible(true);
new SwingWorkerTask().execute(); //This does not get called
and if I do
new SwingWorkerTask().execute();
dialog.setVisible(true); // Well what the point of setting visible after the fact.
So How do I block user action and show a dialog while task is taking place?
Thank you
It is only a chicken/egg if you make it such. You can construct all Swing objects on EDT and then let your SwingWorker (or any other thread) govern all updates by instructing EDT to execute them via SwingUtilities.invokeLater(Runnable).
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class RudeProgressBar extends JFrame {
private JButton button;
public RudeProgressBar() {
setTitle("Rude Progress Bar");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout());
button = new JButton("Do teh work");
add(button, BorderLayout.SOUTH);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JDialog dialog = new JDialog(RudeProgressBar.this, true);
dialog.setTitle("Doing teh work");
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
final JProgressBar progressBar = new JProgressBar(0, 100);
dialog.setLayout(new BorderLayout());
dialog.add(progressBar);
dialog.setSize(100, 100);
dialog.setLocationRelativeTo(RudeProgressBar.this);
MyTask task = new MyTask(dialog);
task.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer)evt.getNewValue());
}
}
});
task.execute();
}
});
setSize(200, 200);
setLocationRelativeTo(null);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new RudeProgressBar().setVisible(true);
}
});
}
private class MyTask extends SwingWorker<Void, Void> {
private final JDialog dialog;
public MyTask(JDialog dialog) {
this.dialog = dialog;
}
#Override
protected Void doInBackground() throws Exception {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
dialog.setVisible(true);
}
});
int progress = 0;
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
setProgress(progress += 20);
}
return null;
}
#Override
protected void done() {
dialog.setVisible(false);
dialog.dispose();
}
}
}
If you are worried that the invokeLater implementation (inside SwingWorker.doInBackground) might get executed after SwingWorker.done, simply put the code in done into another invokeLater. By doing this, you queue your Runnable implementations for EDT to execute them in certain order. The queuing will happen even if this method is called from EDT itself.
Note that if you take a look at SwingWorker implementation, you'll see that it relies on javax.swing.Timer to execute done() and the Timer itself calls invokeLater, so calling it inside done again amounts to doing nothing. Nothing will be wrong if you do it, however.
You can try SwingUtilities.invokeLater and SwingUtilities.invokeAndWait instead of swingWorker.
Also, this topic may be useful.
I want to use the text property in the button property field. this is the code i have tried but it does not work.
private void btnOneActionPerformed(java.awt.event.ActionEvent evt) {
String btnOneText = btnOne.getText( );
txtDisplay.setText(btnOneText);
}
I suggest you to read the oracle official tutorials that have good examples. How to use Buttons.
I made you an example of what you have to do.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class TextFieldTest {
private JPanel panel;
public TextFieldTest(){
panel = new JPanel();
final JTextField textfield = new JTextField(10);
final JButton button = new JButton("Press me");
//here i add the action listener, that will listen the input event
button.addActionListener(new ActionListener(){
//this is anonymous class
#Override
public void actionPerformed(ActionEvent evt){
String text = button.getText();
textfield.setText(text);
}
});
panel.add(textfield);
panel.add(button);
}
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("Textfield example");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLocationByPlatform(Boolean.TRUE);
frame.add(new TextFieldTest().panel);
//Display the window.
frame.pack();
frame.setVisible(Boolean.TRUE);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
See it's more simpler than using a gui-editor, then you understand what you do. It's better to do this first and later when you understand what you are doing use the netbeans gui-editor.
I'm trying to make an application which shows a JProgressBar only while it is performing actions. My problem is, when the program is first opened, I set the JProgressBar visibility to false, then to true when an action is being performed and after it is done, back to false. It seems like it would work, and it does, just not when I make it not visible by default. If the visibility is true by default then it works well, but that's not quite what I want. How could I make it so that it isn't visible until I set it to be visible?
SSCCE just incase my question wasn't clear enough:
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
public class SmileBack {
private JFrame frame;
private JPanel panel, container;
private JButton loadButton;
private JProgressBar progressBar;
public static void main(String[] arguments) {
new SmileBack().constructFrame();
}
public void constructFrame() {
frame = new JFrame("RSTracker");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(getContentPane());
frame.pack();
frame.setVisible(true);
}
public JPanel getContentPane() {
panel = new JPanel(new BorderLayout());
progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
//progressBar.setVisible(false); // doesn't work when this is uncommented
loadButton = new JButton("Load memberlist");
loadButton.setEnabled(true);
loadButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
new Thread(new Runnable() {
#Override
public void run() {
progressBar.setVisible(true);
// do my stuff here...
try {
Thread.sleep(2000); // just for the sake of example
} catch (InterruptedException e) {
e.printStackTrace();
}
progressBar.setVisible(false);
}
}).start();
}
});
container = new JPanel(new FlowLayout());
container.add(loadButton);
container.add(progressBar);
panel.add(container);
return panel;
}
}
Ignore the name, I was listening to that song while creating this. :)
This is probably not the way it should be designed, but this code fixes the problem while still using the natural size (pack()) needed to display the button and progress bar. This is achieved by setting the progress bar to invisible after pack is called, but before setting the GUI visible.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
public class SmileBack {
private JFrame frame;
private JPanel panel, container;
private JButton loadButton;
private JProgressBar progressBar;
public static void main(String[] arguments) {
new SmileBack().constructFrame();
}
public void constructFrame() {
frame = new JFrame("RSTracker");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(getContentPane());
// after this, everything is instantiated;
frame.pack();
setProgressBarVisibility(false);
frame.setVisible(true);
}
public void setProgressBarVisibility(boolean visible) {
progressBar.setVisible(visible);
}
public JPanel getContentPane() {
panel = new JPanel(new BorderLayout());
progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
loadButton = new JButton("Load memberlist");
loadButton.setEnabled(true);
loadButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
new Thread(new Runnable() {
#Override
public void run() {
progressBar.setVisible(true);
// do my stuff here...
try {
Thread.sleep(2000); // just for the sake of example
} catch (InterruptedException e) {
e.printStackTrace();
}
progressBar.setVisible(false);
}
}).start();
}
});
container = new JPanel(new FlowLayout());
container.add(loadButton);
container.add(progressBar);
panel.add(container);
return panel;
}
}
any events in your case (from Runnable#Thread) doesn't invoke EventDispashThread you have to wrapp that into invokeLater, otherwise since JProgressBar will be visible but after long running taks ended shouldn't be hidden
1) all changes to the GUI must be done on EDT
2) you can invoke EDV from Swing's Listeners, SwingWorker's methods process and done and invoke changes to the GUI by using invokeLater (in special cases invokeAndWait)
3) Runnable#Thread by default doesn't invoke Swing's Methods nor EDT, there must be output to the GUI wrapped into invokeLater (in special cases invokeAndWait), more in the Concurency in Swing, inc. thread safe methods as are setText(), append() etc.
In your thread call the progressBar.setVisible(true); inside SwingUtilities.invokeAndWait().
The code you have posted works perfectly well. The problem is when you call frame.pack(), the frame resizes to fit all visible component. When you have progressBar visibility set to false, the frame ignores this component and sizes accordingly. So when progressBar.setVisible(true) is called later, the component is shown but the frame is not big enough for you to see the component. If you just drag and increase the size of the frame, you can see the progressBar
I suggest that you provide explicit frame size like frame.setSize(200,400) and dont call frame.pack().
I have a GUI which is quite heavy to build/initialize on the platform on which it runs.. Therefore I want to update progress while it initializes..
I have a small undecorated JDialog containing a JLabel and a JProgressBar which I want to update at specific places during initialization, however, because the event dispatch thead (as per Swing rules) is used to build/initialize the GUI, the progress is of course not updated until the EDT is idle again (i.e. initialization is finished)..
The JProgressBar I have gotten to redraw using "paintImmediately", but I can't seem to make it work properly for the JLabel and the dialog itself.. Is there any simple recommended/proven method to accomplish this?
cheers...
EDIT: Adding an example of what it is I'm trying to do; greatly simplified, of course.
private JLabel progressLabel;
private JProgressBar progressBar;
public static int main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showProgressDialog();
progressLabel.setText("construct 1");
constructSomeHeavyGUI();
progressLabel.setText("construct 2");
progressBar.setValue(33);
constructSomeMoreHeavyGUI();
progressLabel.setText("construct 3");
progressBar.setValue(67);
constructEvenMoreHeavyGUI();
progressLabel.setText("done");
progressBar.setValue(100);
hideProgressDialog();
showHeavyGUI();
}
});
}
the repaints caused by the calls to progressBar.setValue()/progressLabel.setText() above will of course get queued as long as the EDT is busy and result in a repaint after we are all done instead of updating along the way..
I would suggest that by using SwingWorker , then you can update the JProgressBar correctly on EDT and without any freeze or isuees with Concurency in Swing,
there is another option by using Runnable#thread, but then you have to wrapp all output to the GUI into invokeLater();
for example:
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
public class TestProgressBar {
private static void createAndShowUI() {
JFrame frame = new JFrame("TestProgressBar");
frame.getContentPane().add(new TestPBGui().getMainPanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowUI();
}
});
}
private TestProgressBar() {
}
}
class TestPBGui {
private JPanel mainPanel = new JPanel();
public TestPBGui() {
JButton yourAttempt = new JButton("WRONG attempt to show Progress Bar");
JButton myAttempt = new JButton("BETTER attempt to show Progress Bar");
yourAttempt.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
yourAttemptActionPerformed();
}
});
myAttempt.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
myAttemptActionPerformed();
}
});
mainPanel.add(yourAttempt);
mainPanel.add(myAttempt);
}
private void yourAttemptActionPerformed() {
Window thisWin = SwingUtilities.getWindowAncestor(mainPanel);
JDialog progressDialog = new JDialog(thisWin, "Uploading...");
JPanel contentPane = new JPanel();
contentPane.setPreferredSize(new Dimension(300, 100));
JProgressBar bar = new JProgressBar(0, 100);
bar.setIndeterminate(true);
contentPane.add(bar);
progressDialog.setContentPane(contentPane);
progressDialog.pack();
progressDialog.setLocationRelativeTo(null);
Task task = new Task("Your attempt");
task.execute();
progressDialog.setVisible(true);
while (!task.isDone()) {
}
progressDialog.dispose();
}
private void myAttemptActionPerformed() {
Window thisWin = SwingUtilities.getWindowAncestor(mainPanel);
final JDialog progressDialog = new JDialog(thisWin, "Uploading...");
JPanel contentPane = new JPanel();
contentPane.setPreferredSize(new Dimension(300, 100));
final JProgressBar bar = new JProgressBar(0, 100);
bar.setIndeterminate(true);
contentPane.add(bar);
progressDialog.setContentPane(contentPane);
progressDialog.pack();
progressDialog.setLocationRelativeTo(null);
final Task task = new Task("My attempt");
task.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equalsIgnoreCase("progress")) {
int progress = task.getProgress();
if (progress == 0) {
bar.setIndeterminate(true);
} else {
bar.setIndeterminate(false);
bar.setValue(progress);
progressDialog.dispose();
}
}
}
});
task.execute();
progressDialog.setVisible(true);
}
public JPanel getMainPanel() {
return mainPanel;
}
}
class Task extends SwingWorker<Void, Void> {
private static final long SLEEP_TIME = 4000;
private String text;
public Task(String text) {
this.text = text;
}
#Override
public Void doInBackground() {
setProgress(0);
try {
Thread.sleep(SLEEP_TIME);// imitate a long-running task
} catch (InterruptedException e) {
}
setProgress(100);
return null;
}
#Override
public void done() {
System.out.println(text + " is done");
Toolkit.getDefaultToolkit().beep();
}
}
EDIT:
1) you showed another issues, why do you create lots of Top-Level Containers on Fly/Runtime, create only required numbers of Containers and re-use that by removeAll()
2) here is probably what you needed, all those JProgressBars in the JTable are pretty accesible and configurable
3) this is your paintImmediately(), that really reason why not painting any of Progress to the JLabel but using JProgressBar#setValue(int);
instead,
It's possible that constructSome*HeavyGUI() really takes long enough to matter, but it's more likely that filling in the data model(s) is the problem. Instead, construct and show the empty GUI elements and launch one or more SwingWorker instances to marshal each element's data. There are related examples here and here.
Addendum: If the problem is instantiating components, and not loading data models, you can chain the calls to invokeLater(), as suggested in a comment below. If you're instantiating that many components, consider the flyweight pattern. JTable is a familiar example.
Move the long running code in a separate thread and use SwingUtilities.invokeAndWait or invokeLater to update GUI.
Either use SwingUtilities.invokeLater(...) as suggested by #StanislavL, or use SwingWorker.
See also:
Worker Threads and SwingWorker