I want to use the swing worker thread to update my GUI in swing. pls any help is appreciated.I need to update only the status of 1 field using the thread i.e setText().
I just answer similar question on another forum for a question about SwingWorker:
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;
public class Main extends JFrame
{
private JLabel label;
private Executor executor = Executors.newCachedThreadPool();
private Timer timer;
private int delay = 1000; // every 1 second
public Main()
{
super("Number Generator");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300, 65);
label = new JLabel("0");
setLayout(new FlowLayout());
getContentPane().add(label, "Center");
prepareStartShedule();
setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new Main();
}
});
}
private void prepareStartShedule()
{
timer = new Timer(delay, startCycle());
timer.start();
}
private Action startCycle()
{
return new AbstractAction()
{
#Override
public void actionPerformed(ActionEvent e)
{
executor.execute(new MyTask());
}
};
}
private class MyTask extends SwingWorker<Void, Integer>
{
#Override
protected Void doInBackground() throws Exception
{
doTasksInBackground();
return null;
}
private void doTasksInBackground()
{
publish(generateRandomNumber());
}
private int generateRandomNumber()
{
return (int) (Math.random() * 101);
}
#Override
protected void process(List<Integer> chunks)
{
for(Integer chunk : chunks) label.setText("" + chunk);
}
}
}
ps: #trashgod helps me a month ago to understand how to deal with SwingWorker (Can't get ArrayIndexOutOfBoundsException from Future<?> and SwingWorker if thread starts Executor), so thanks to him.
EDIT: The code is corrected. Thanks #Hovercraft Full Of Eels
Related
I have made a very simple code to show it here, i have a button that should show a JDialog to check the progress status, i am using the invoke late to go through EDT and my loop isn't in the run method, so why isn't my bar updating ?
here is the code
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class JBarEx extends JFrame {
private JTextField progStatus = new JTextField("Undefined");
private JButton dialogBtn = new JButton("Show Progression dialog");
final JDialog dlg = new JDialog((JFrame) null, "prog Title", false);
final JProgressBar dpb = new JProgressBar(0, 100);
public JBarEx() {
JPanel pan = new JPanel();
dialogBtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
showProgress();
}
});
progStatus.setEditable(false);
pan.add(progStatus);
pan.add(dialogBtn);
setContentPane(pan);
this.setSize(200, 100);
setVisible(true);
}
public void showProgress() {
dlg.add(BorderLayout.CENTER, dpb);
dlg.add(BorderLayout.NORTH, new JLabel("prog message"));
dlg.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dlg.setSize(300, 75);
dlg.setLocationRelativeTo(null);
dlg.setVisible(true);
for (int i = 0; i < 100; i++) {
final int ii = i;
try {
Thread.sleep(25);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
updateBar(ii);
}
});
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void updateBar(int newValue) {
dpb.setValue(newValue);
}
public static void main(String[] args) {
JBarEx jbx = new JBarEx();
}
}
Your showProgress method is being executed within the context of the Event Dispatching Thread. The EDT is responsible for, amongst other things, processing paint requests. This means that so long as your for-loop is executing, the EDT can not process any new paint requests (or handle the invokeLater events either) as it is blocking the EDT.
While there are any number of possible ways to solve the problem, based on your code example, the simplest would be to use a SwingWorker.
It has the capacity to allow your to execute the long running task the a background thread (freeing up the EDT), but also allows you means for publishing updates (if required) so that they can be processed in the EDT and also provides handy progress notification.
For example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
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.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
public class SwingWorkerProgress {
public static void main(String[] args) {
new SwingWorkerProgress();
}
public SwingWorkerProgress() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JProgressBar pbProgress;
private JButton start;
public TestPane() {
setBorder(new EmptyBorder(10, 10, 10, 10));
pbProgress = new JProgressBar();
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(4, 4, 4, 4);
gbc.gridx = 0;
gbc.gridy = 0;
add(pbProgress, gbc);
start = new JButton("Start");
gbc.gridy++;
add(start, gbc);
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
start.setEnabled(false);
ProgressWorker pw = new ProgressWorker();
pw.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if (name.equals("progress")) {
int progress = (int) evt.getNewValue();
pbProgress.setValue(progress);
repaint();
} else if (name.equals("state")) {
SwingWorker.StateValue state = (SwingWorker.StateValue) evt.getNewValue();
switch (state) {
case DONE:
start.setEnabled(true);
break;
}
}
}
});
pw.execute();
}
});
}
}
public class ProgressWorker extends SwingWorker<Object, Object> {
#Override
protected Object doInBackground() throws Exception {
for (int i = 0; i < 100; i++) {
setProgress(i);
try {
Thread.sleep(25);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
}
Check out Concurrency in Swing for more details
Even if you fix the loop as others have pointed out, you'd still block the event dispatch thread. The for loop is run in showProgress() which is called from an event listener. The updates are pushed to the event queue, but that does not get processed until the loop has completed.
Use a Swing Timer instead. Something like this:
Timer timer = new Timer(25, new ActionListener() {
private int position;
#Override
public void actionPerformed(ActionEvent e) {
position++;
if (position < lastPosition) {
updateBar(position);
} else {
((Timer) e.getSource).stop();
}
}
});
timer.start();
where lastPosition would be the state where you want the progress bar to stop.
Unrelated to that bug, but a bug still, you should not create swing components outside the event dispatch thread. It's best to do it right from the start:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JBarEx jbx = new JBarEx();
}
});
}
for (int i = 0; i < 0; i++) {
You will never enter this code so will never call the updateBar(..) method
i needs to be greater than 0 in this case. If it is 1 then updateBar will be called once, if 2 then updateBar will be called twice etc
Also rather than doing
Thread.sleep(25);
take a look at java executors as these will help with your scheduling and remove the need for the sleep
I did a quick and dirty implementation of SwingWorker to test the publish() and setProgress() methods. When I set the parameter for the setProgress() method like stated in the complete sourcecode below, everything works as expected.
However if I set the setProgress parameter like this:
setProgress((int) ((i/2000) * 100));
OR like this
setProgress((int) (i * (100/2000)));
The progressbar does not update since the propertyChange is not getting fired.
I seriously have no clue why this is since in my opinion I don't see anything mathematically wrong there...
Any ideas?
Thanks in advance!
Best regards
package test;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingWorker;
import javax.swing.text.BadLocationException;
import javax.swing.text.StyledDocument;
public class ConsoleTest extends SwingWorker<Void, String>{
private JTextPane console;
private JProgressBar pb;
public ConsoleTest(JProgressBar pb, JTextPane out){
this.console = out;
this.pb = pb;
}
private static void createGUI(){
JFrame container = new JFrame();
container.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTextPane console = new JTextPane();
JButton start = new JButton("start");
JButton reset = new JButton("reset");
JPanel btnpane = new JPanel();
btnpane.add(start);
btnpane.add(reset);
btnpane.add(new JCheckBox( "Click me to proof UI is responsive" ));
JProgressBar pb = new JProgressBar(0,100);
btnpane.add(pb);
container.add(btnpane, BorderLayout.SOUTH);
JScrollPane sp = new JScrollPane(console);
sp.setPreferredSize(new Dimension(800,800));
container.add(sp,BorderLayout.NORTH);
start.addActionListener(e -> { ConsoleTest test = new ConsoleTest(pb, console);
test.addPropertyChangeListener(evt -> { if ("progress".equals(evt.getPropertyName())) {
System.out.println("check");
test.getPG().setValue((int)evt.getNewValue());
}
});
test.execute();
});
reset.addActionListener(e -> clear(console));
container.pack();
container.setVisible(true);
}
private static void clear(JTextPane console) {
console.setText("");
}
public JProgressBar getPG(){
return this.pb;
}
#Override
protected Void doInBackground() throws Exception {
LinkedList<Integer> list = new LinkedList<>();
for (int i = 0; i< 2000;i++){
list.add(i);
if(((i+1) % 5) == 0)
publish(Integer.toString(i+1));
setProgress((int) (i*100/2000));
Thread.sleep(1);
}
return null;
}
#Override
protected void process(List<String> chunks){
StyledDocument doc = this.console.getStyledDocument();
try {
for(String s: chunks)
doc.insertString(doc.getLength(),"- "+s+" elements added to the list\n", null);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
#Override
protected void done() {
try {
System.out.println("DONE!!!");
}
catch (Exception ignore) {
}
}
public static void main(String[] args) {
createGUI();
}
}
you work with integers !!
setProgress((int) ((i/2000)*100));
is always 0 since (int)i / 2000 is always 0
same problem with
setProgress((int) (i*(100/2000)));
100 / 2000 is always 0 in integer
try
setProgress((int) (((float)i/2000)*100));
or
setProgress((int) (i*((float)100/2000)));
and it will work
you should make (i * 100)/2000
the formula is
(current_state * 100) / final_state
I've created a series of classes to try and figure out Observer patterns and am having some trouble.
The two classes in the observer/observed relationship are ClockPanel, and TheTimer. TheTimer is a (swing) timer which keeps track of time from start in seconds. ClockPanel is a GUI (swing) which has a button to start the timer and a JLabel which I want to display the time.
The goal of my observer pattern: take the value being created in TheTimer and print it on my GUI.
The current problem: The timer is updating the time just fine, but I do not seem to understand how to update the value in my GUI.
I found a question similar to this one in a C# discussion, but the problem was more nuanced and way over my head.
Here are the five classes which comprise the program:
1. The GUI-ClockPanel
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.JLabel;
import javax.swing.JPanel;
public class ClockPanel implements Observer {
JFrame frame;
JPanel panel;
JButton sbutton;
JLabel label;
#Override
public void update(int counter) {
String clockval = String.valueOf(counter);
label.setText(clockval);
}
public ClockPanel() {
frame = new JFrame();
frame.setSize(100, 100);
panel = new JPanel();
label = new JLabel();
TheTimer myTimer = new TheTimer();
sbutton = new JButton("start");
sbutton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
myTimer.StartTimer();
}
});
frame.setLayout(new FlowLayout());
frame.add(panel);
frame.add(sbutton);
frame.add(label);
frame.setVisible(true);
}
}
2. The Swing Timer-TheTimer
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JPanel;
import javax.swing.Timer;
public class TheTimer extends JPanel implements Subject {
private ActionListener action;
private Timer Time;
private int delay = 1000;
private ArrayList<Observer> observers = new ArrayList<Observer>();
private int counter = 0;
public TheTimer() {
action = new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println(counter);
counter++;
setCounter(counter);
}
};
}
public void StartTimer() {
Time = new Timer(delay, action);
Time.setInitialDelay(0);
Time.start();
}
public int getCounter() {
return counter;
}
public void setCounter(int counter) {
this.counter = counter;
notifyObservers();
}
#Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
#Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
#Override
public void notifyObservers() {
for (Observer ob : observers) {
System.out.println("Notifying ClockPanel on change in counter value");
ob.update(this.counter);
}
}
}
3. The Observer-Observer
public interface Observer {
public void update(int counter);
}
4. The Observer-related methods-Subject
public interface Subject {
public void registerObserver(Observer observer);
public void removeObserver(Observer observer);
public void notifyObservers();
}
5. The Main-TestMain
import javax.swing.SwingUtilities;
public class TestMain {
public static void main(String args[]) {
ClockPanel panel = new ClockPanel();
TheTimer timer = new TheTimer();
timer.registerObserver(panel);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ClockPanel();
}
});
}
}
You have two TheTimer objects: one in ClockPanel, the other in TestMain#main().
You need to remove the timer from (say) main() and add:
myTimer.registerObserver(this);
to your ClockPanel constructor.
I'm working on an assignment for Java subject. I'm using NetBean IDE. My assignment requests me to make a word game. The game I'm designing involves a timer with delay of 1000 ms. The timer decrements a variable from 30 to 0. The timer itself is working. It is placed in the main function of GUI class. The problem I'm facing that I don't know how I'm supposed to update a jTextfield with everytime the variable is decremented.
public static void main(String args[]) {
Time counter=new Time();
ActionListener actListner = new ActionListener() {
public void actionPerformed(ActionEvent event) {
counter.decTime();
jTime.setText("Time left: " + counter.getTime());
}
};
Timer timer = new Timer(1000, actListner);
timer.start();
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new StartGUI().setVisible(true);
}
});
}
I'm not sure how to implement this properly
jTime.setText("Time left: " + counter.getTime());
Not sure what you're doing wrong (that's why you should always provide a short example that we can copy-paste-compile-run that demonstrates the problem. When I make the code runnable, it works fine. That's why we need to be able to run your code to see where you're going wrong.
Here's the runnable version:
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.Timer;
public class StartGUI extends JFrame {
static JTextField jTime = new JTextField(10);
public StartGUI() {
jTime.setEditable(false);
add(jTime);
setLayout(new GridBagLayout());
setSize(200, 200);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setLocationRelativeTo(null);
}
static class Time {
int time = 1000;
void decTime() {
time--;
}
int getTime() {
return time;
}
}
public static void main(String args[]) {
Time counter = new Time();
ActionListener actListner = new ActionListener() {
public void actionPerformed(ActionEvent event) {
counter.decTime();
jTime.setText("Time left: " + counter.getTime());
}
};
Timer timer = new Timer(1000, actListner);
timer.start();
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new StartGUI().setVisible(true);
}
});
}
}
Here is the code refactored a bit with some better practices
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.Timer;
public class StartGUI extends JFrame {
private JTextField jTime = new JTextField(10);
private Timer timer = createTimer(1000);
public StartGUI() {
jTime.setEditable(false);
add(jTime);
setLayout(new GridBagLayout());
pack();
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setLocationRelativeTo(null);
}
private Timer createTimer(int delay) {
Timer timer = new Timer(delay, new ActionListener(){
Time counter = new Time(30);
public void actionPerformed(ActionEvent e) {
if (counter.getTime() == 0) {
((Timer)e.getSource()).stop();
jTime.setText("Times up!");
} else {
jTime.setText("Time left: " + counter.getTime());
counter.decTime();
}
}
});
timer.setInitialDelay(0);
return timer;
}
private Timer getTimer() {
return timer;
}
static class Time {
int time = 1000;
public Time(int time) {
this.time = time;
}
void decTime() {
time--;
}
int getTime() {
return time;
}
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
StartGUI start = new StartGUI();
start.setVisible(true);
start.getTimer().start();
}
});
}
}
I have a JMenu of 16 JMenuItems, of which I want 3 items to be displayed upfront and the rest 13 items to fade in with a 500 ms delay. Is there a way to do this animation in Java?
This is not as easy as it sounds.
Basically I originally thought "I'll attach a popup listener to the popup menu that the menu items are added to"...but apparently this doesn't work so well. The menu popup is built dynamically on demand. Makes sense, but it's still a pain.
So instead, I've found that if I wait for addNotify I can simply start the animation engine.
The animation engine is a simple concept. It has a javax.swing.Timer that ticks at a regular interval. Coupled with a start time and a duration, we can calculate the progress of the animation and generate the alpha value as required.
The only thing left is then to notify all the interested parties that the animation has changed and voila...
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
public class FadeMenu {
private AnimationEngine engine;
public static void main(String[] args) {
new FadeMenu();
}
public FadeMenu() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
engine = new AnimationEngine();
JMenuBar mb = new JMenuBar();
JMenu flip = new JMenu("Flip");
flip.add("Static 1");
flip.add("Static 2");
flip.add("Static 3");
flip.add(new FadeMenuItem("Fade 1"));
flip.add(new FadeMenuItem("Fade 2"));
flip.add(new FadeMenuItem("Fade 3"));
flip.add(new FadeMenuItem("Fade 4"));
mb.add(flip);
JFrame frame = new JFrame("Testing");
frame.setJMenuBar(mb);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class FadeMenuItem extends JMenuItem {
public FadeMenuItem(String text) {
super(text);
engine.addTimingListener(new TimingListener() {
#Override
public void timingEvent() {
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(engine.getAlpha()));
super.paintComponent(g2d);
g2d.dispose();
}
#Override
public void removeNotify() {
Container parent = getParent();
if (parent instanceof JPopupMenu) {
JPopupMenu menu = (JPopupMenu) parent;
engine.stop();
}
super.removeNotify();
}
#Override
public void addNotify() {
super.addNotify();
Container parent = getParent();
if (parent instanceof JPopupMenu) {
JPopupMenu menu = (JPopupMenu) parent;
engine.restart();
}
}
}
public interface TimingListener {
public void timingEvent();
}
public class AnimationEngine {
private Timer fade;
private float alpha;
private long startTime;
private long duration = 1000;
private List<TimingListener> listeners;
public AnimationEngine() {
listeners = new ArrayList<>(5);
fade = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long elapsed = System.currentTimeMillis() - startTime;
if (elapsed >= duration) {
((Timer) e.getSource()).stop();
alpha = 1f;
} else {
alpha = (float) elapsed / (float) duration;
}
fireTimingEvent();
}
});
fade.setRepeats(true);
fade.setCoalesce(true);
fade.setInitialDelay(500);
}
public void addTimingListener(TimingListener listener) {
listeners.add(listener);
}
public void removeTimingListener(TimingListener listener) {
listeners.add(listener);
}
protected void fireTimingEvent() {
for (TimingListener listener : listeners) {
listener.timingEvent();
}
}
public void restart() {
fade.stop();
alpha = 0;
fireTimingEvent();
startTime = System.currentTimeMillis();
fade.start();
}
public float getAlpha() {
return alpha;
}
public void stop() {
fade.stop();
}
}
}
While this works on Windows, I'd be concerned that it might not work on other platforms, as the means by which the menus are generated are controlled (in part) by the UI delegate. This could become very messy, very quickly
start a timer to fire an event to fade in