Swing: does DefaultBoundedRangeModel coalesce multiple events? - java

I have a JProgressBar displaying a BoundedRangeModel which is extremely fine grained and I was concerned that updating it too often would slow down my computer. So I wrote a quick test program (see below) which has a 10Hz timer but each timer tick makes 10,000 calls to microtick() which in turn increments the BoundedRangeModel. Yet it seems to play nicely with a JProgressBar; my CPU is not working hard to run the program.
How does JProgressBar or DefaultBoundedRangeModel do this? They seem to be smart about how much work it does to update the JProgressBar, so that as a user I don't have to worry about updating the BoundedRangeModel's value.
package com.example.test.gui;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoundedRangeModel;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.Timer;
public class BoundedRangeModelTest1 extends JFrame {
final private BoundedRangeModel brm = new DefaultBoundedRangeModel();
final private Timer timer = new Timer(100, new ActionListener()
{
#Override public void actionPerformed(ActionEvent arg0) { tick(); }
});
public BoundedRangeModelTest1(String title) {
super(title);
JPanel p = new JPanel();
p.add(new JProgressBar(this.brm));
getContentPane().add(p);
this.brm.setMaximum(1000000);
this.brm.setMinimum(0);
this.brm.setValue(0);
}
protected void tick() {
for (int i = 0; i < 10000; ++i)
{
microtick();
}
}
private void microtick() {
this.brm.setValue(this.brm.getValue()+1);
}
public void start()
{
this.timer.start();
}
static public void main(String[] args)
{
BoundedRangeModelTest1 f =
new BoundedRangeModelTest1("BoundedRangeModelTest1");
f.pack();
f.setVisible(true);
f.setDefaultCloseOperation(EXIT_ON_CLOSE);
f.start();
}
}

See the nested class DoPostEvent in the class javax.swing.Timer and the comments on the field notify.
Addendum: Events from the DefaultBoundedRangeModel are forwarded to the UI delegate by the fireStateChanged() method of JProgressBar. The events are coalesced in the EventQueue when postEvent() is invoked by repaint().
One way to follow the trail is to set a breakpoint in the UI delegate's paint() method and examine the call stack.
JProgressBar jpb = new JProgressBar(brm);
jpb.setUI(new BasicProgressBarUI());

Related

Return Array From SwingWorker Thread to Main Thread

I have a Swing GUI that when it open it performs a a thread in the background. For that I used SwingWorker thread. the problem is that I have an Array of String declare in main thread that is expected to collect information from swing worker thread and return that but after the thread is finishes the array is still empty. I guess once the thread is finished the array looses its values even though the array is declare in the main thread.
How can I return the values of the array to the main thread?
ArrayList<String> allnets = new ArrayList();
new SwingWorker<Object, Void>() {
#Override
public Object doInBackground() throws SocketException, UnknownHostException {
System.out.println("interfaces");
netAdapter = new NetInterface();
System.out.println(Thread.currentThread().getName());
for(int i = 0; i < allnets.length; i++) {
allnets.add("interface number");
}
return null;
}
#Override
public void done() {
}
}.execute();
System.out.println(Thread.currentThread().getName());
// String[] inetfaces = {"eth0", "eth1", "wlan1", "wlan2", "wlan3"};
JComboBox comboBox = new JComboBox(allnets);
comboBox.setToolTipText("Interfaces");
comboBox.setBounds(444, 51, 137, 22);
frame.getContentPane().add(comboBox);
To get the list of all interface names, call [static] method getNetworkInterfaces() of class java.net.NetworkInterface. And I assume you want to call that method from inside a SwingWorker. Also, from the code you posted, I assume you want to populate the JComboBox in the done() method of class SwingWorker. And since all the work is being done in the SwingWorker class, method doInBackground() doesn't need to return anything.
When it comes to using SwingWorker, I prefer to make a completely separate class that extends SwingWorker and not an anonymous inner class.
Here is the code for a Swing application. The JFrame displays a JLabel and a JComboBox. The JcomboBox contains the list of the names of all the network interfaces.
Note that on my Windows 10 (64 bit) machine running JDK 13, populating the JComboBox takes hardly any time at all, so the SwingWorker is not really needed. I guess you just want to practice using SwingWorker, correct?
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.WindowConstants;
public class SwngWrkr implements Runnable {
private JComboBox<Object> combo;
private JFrame frame;
public void run() {
showGui();
}
private JPanel createMainPanel() {
JPanel mainPanel = new JPanel();
mainPanel.add(new JLabel("Interfaces"));
DefaultComboBoxModel<Object> model = new DefaultComboBoxModel<Object>();
model.addElement("Loading...");
combo = new JComboBox<Object>(model);
mainPanel.add(combo);
return mainPanel;
}
private void showGui() {
frame = new JFrame("SW");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(createMainPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
new GetIfTsk(combo).execute();
}
public static void main(String[] args) {
SwngWrkr instance = new SwngWrkr();
EventQueue.invokeLater(instance);
}
}
class GetIfTsk extends SwingWorker<Void, Void> {
private JComboBox<Object> combo;
private List<Object> netIfNames;
public GetIfTsk(JComboBox<Object> combo) {
this.combo = combo;
netIfNames = new ArrayList<>();
}
protected Void doInBackground() throws Exception {
Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces();
int ndx = 0;
while (ifs.hasMoreElements()) {
NetworkInterface ni = ifs.nextElement();
String name = ni.getName();
System.out.printf("%2d. %s%n", ++ndx, name);
netIfNames.add(name);
}
return null;
}
protected void done() {
DefaultComboBoxModel<Object> model = (DefaultComboBoxModel<Object>) combo.getModel();
model.removeAllElements();
model.addAll(netIfNames);
model.setSelectedItem(netIfNames.get(0));
}
}

How do I call a method from an action listener without the JFrame freezing and waiting for the action listener to complete?

How do I set the text of a JTextArea while its JFrame is running, and refresh the JFrame to show the change, from another class?
I have a JFrame with a JTextArea which acts as a log, and the string it prints i update periodically with new activity from another class. My JFrame class (EnablePage) looks like this:
package bot;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JButton;
import javax.swing.JLabel;
import java.awt.Font;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class EnablePage extends JFrame {
public static String enablePane;
private static JPanel contentPane;
public static JTextArea txtrHello = new JTextArea();
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
EnablePage frame = new EnablePage();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public EnablePage() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 594, 474);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
JScrollPane scrollPane = new JScrollPane();
scrollPane.setToolTipText("");
scrollPane.setBounds(6, 89, 582, 357);
contentPane.add(scrollPane);
txtrHello.setEditable(false);
txtrHello.setText(enablePane);
txtrHello.setWrapStyleWord(true);
txtrHello.setLineWrap(true);
scrollPane.setViewportView(txtrHello);
JButton btnNewButton = new JButton("Enable");
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
navigator.navigator();
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
btnNewButton.setBounds(59, 29, 117, 29);
contentPane.add(btnNewButton);
}
public static void update(String x) {
txtrHello.setText(enablePane+"\n"+x);
}
}
And from my navigator class I've been trying to use this line of code to update the JtextArea, while it manipulates a website. This code I didn't include, but replaced here with "Thread.sleep(100000);" to illustrate the problem:
package bot;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.JOptionPane;
public class navigator {
public static DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy(HH:mm:ss)");
public static void navigator() throws Exception {
Date date1 = new Date();
Thread.sleep(100000);
EnablePage.update("Bot enabled: "+dateFormat.format(date1));
}
}
However this is not updating the JFrame with the new text, because the EnablePage class is stuck waiting for the navigator() method to complete. What ends up happening is the Enable button stays blue because the actionlistener method is never broken from, because the nagivator() method never finished. What can I do to still call navigator() from the enable button but not have the EnablePage class freeze on this line?
Here's a simple example. A clock JTextField is updated from a Thread.
As you can see, there are no update, validate, or invalidate method calls.
Edited to add: The calls to the SwingUtilities invokeLater method are important, to ensure that the Swing components are created and updated on the Event Dispatch thread (EDT).
I also modified the Clock example to stop the Timer thread cleanly before disposing of the JFrame.
package com.ggl.testing;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class Clock implements Runnable {
private JFrame frame;
private JTextField clockDisplay;
private Timer timer;
#Override
public void run() {
frame = new JFrame("Clock");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
JPanel panel = new JPanel();
clockDisplay = new JTextField(12);
clockDisplay.setEditable(false);
clockDisplay.setHorizontalAlignment(JTextField.CENTER);
panel.add(clockDisplay);
frame.add(panel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
timer = new Timer(this);
new Thread(timer).start();
}
public void exitProcedure() {
timer.setRunning(false);
frame.dispose();
System.exit(0);
}
public void setText(String text) {
clockDisplay.setText(text);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Clock());
}
public class Timer implements Runnable {
private volatile boolean running;
private Clock clock;
private SimpleDateFormat timeFormat;
public Timer(Clock clock) {
this.clock = clock;
this.running = true;
this.timeFormat = new SimpleDateFormat("h:mm:ss a");
}
#Override
public void run() {
while (running) {
displayTime();
sleep();
}
}
public void displayTime() {
Calendar calendar = Calendar.getInstance();
Date date = calendar.getTime();
final String s = timeFormat.format(date);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
clock.setText(s);
}
});
}
public void sleep() {
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
}
}
public synchronized void setRunning(boolean running) {
this.running = running;
}
}
}
JTextArea#append will allow you to append text to the JTextArea, both setText and append are bound methods, this means that they will trigger an update when they are called so you shouldn't need to do anything more. If it's not updating then it sounds like you have a reference issue.
You should consider providing a fully runnable example which demonstrates your problem. This will result in less confusion and better responses
You should avoid the use of static, especially when associated with UI components, as this really begins to give you trouble with what you are referencing and what's on the screen. static is NOT a cross communication mechanism for objects and shouldn't be used as such.
If you can, you should define some kind of interface which describes the actions which be executed on your log frame (ie addLog(String)), have your log frame implement this interface and then pass a reference of it to those classes that need it.
Alternatively, you could use a singleton pattern to allow your log window to be accessed from any where in your application, personally, I'd be tempted to devise a queue of some kind, where other classes pushed log events onto this (singleton) queue and you had your frame either poll it or use some kind of blocking queue mechanism to monitor for changes to the queue. This would require you to have a separate Thread (or SwingWorker) which monitored the queue in the background so you don't block the Event Dispatching Thread.
Avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify
Updated
Your runnable example works for me, more or less. Your reliance on static is worrying and Thread.sleep(100000); will block the Event Dispatching Thread, making your program look like it's hung (cause it has). The following is reworked version of your example, without null layouts, without static and using a Swing Timer instead of Thread.sleep. The great thing about this, is once you press the "Enable" button, the timer will update the text area every second...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Timer;
public class EnablePage extends JFrame {
private JTextArea txtrHello = new JTextArea(10, 20);
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
EnablePage frame = new EnablePage();
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public EnablePage() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
JScrollPane scrollPane = new JScrollPane(txtrHello);
scrollPane.setToolTipText("");
add(scrollPane);
txtrHello.setEditable(false);
txtrHello.setWrapStyleWord(true);
txtrHello.setLineWrap(true);
JButton btnNewButton = new JButton("Enable");
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
Navigator.navigator(EnablePage.this);
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
add(btnNewButton, BorderLayout.NORTH);
}
public void update(String x) {
System.out.println("Update " + x + "\n");
txtrHello.append(x);
}
public static class Navigator {
public static DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy(HH:mm:ss)");
public static void navigator(EnablePage page) throws Exception {
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Date date1 = new Date();
page.update("Bot enabled: " + dateFormat.format(date1));
}
});
timer.start();
}
}
}

JProgressBar instantiate it self multiple time

I'm trying to add a JProgressBar to a simple program just to learn. So far i can display it, but it adds multiple instance while i want just one to show.
Here's the code :
package package1;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
public class Opening extends JPanel {
private JProgressBar loadingBar;
private Thread t;
public void paintComponent(Graphics g)
{
super.paintComponent(g);
//Loading bar
bar();
}
private void bar()
{
loadingBar = new JProgressBar();
t = new Thread(new LoadMyBar());
this.add(loadingBar).setLocation(25, 600);
loadingBar.setSize(625, 25);
loadingBar.setStringPainted(true);
loadingBar.setValue(0);
loadingBar.setMinimum(0);
loadingBar.setMaximum(100);
t.start();
}
class LoadMyBar implements Runnable
{
public void run(){
for(int i = loadingBar.getMinimum(); i <= loadingBar.getMaximum(); i++)
{
loadingBar.setValue(i);
try
{
t.sleep(1000);
}catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
Any idea on what i'm doing wrong?
You're calling bar() inside of the paintComponent(Graphics g) method. This method is responsible for drawing the component and can be called potentially many times and not in your control. Don't do that, but rather call it once in a constructor or some other location where it can be called just once.
You're also setting the JProgressBar's value off of the Swing event thread, something that can be dangerous to do. Use a Swing Timer instead or use a SwingWorker's progress property together with a PropertyChangeListener.
e.g.,
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
#SuppressWarnings("serial")
public class Opening extends JPanel {
private static final int TIMER_DELAY = 1000;
private JProgressBar loadingBar;
private Timer t = new Timer(TIMER_DELAY, new TimerListener());
public Opening() {
bar();
}
private void bar() {
loadingBar = new JProgressBar();
this.add(loadingBar);
loadingBar.setStringPainted(true);
loadingBar.setValue(0);
loadingBar.setMinimum(0);
loadingBar.setMaximum(100);
t.start();
}
private class TimerListener implements ActionListener {
int value = 0;
#Override
public void actionPerformed(ActionEvent e) {
if (value <= 100) {
loadingBar.setValue(value);
value++;
} else {
// value > 100
((Timer) e.getSource()).stop(); // stop timer
}
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("Opening");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(new Opening());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
You are creating the JProgressBar and adding it to your class Opening in the method paintComponent().
paintComponent() is called every time, the component needs to draw itself.
This can be after resizing the window (JFrame) or some other application overlapping your application.
You should move the initialization to the constructor of Opening.
See http://docs.oracle.com/javase/tutorial/uiswing/painting/closer.html

JFrame repaint(); How to update the JFrame with Thread info?

I was trying to set up a simple time ticker on a JFrame. I am able to display the initial value of counter, but any subsequent change doesn't update on the JFrame. Any nudge towards an answer to what I am doing wrong would be appreciated. I think it is a problem of repaint() not getting called, but I either end up with errors, or nothing if I try putting it in.
package com.game.ryan;
import java.awt.Dimension;
import javax.swing.JFrame;
class Screen extends JFrame{
private Dimension d = new Dimension(800,600);
private JFrame f;
public Screen(){
f = new JFrame();
f.setIgnoreRepaint(false);
f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setMinimumSize(d);
f.setLocationRelativeTo(null);
f.add(new MyPanel());
f.pack();
f.setVisible(true);
}
public static void main(String[] args){
Screen s = new Screen();
}
}
I also have:
package com.game.ryan;
import java.awt.Graphics;
import javax.swing.JPanel;
public class MyPanel extends JPanel{
private OneThread ot = new OneThread();
private int counter = ot.getThreadCounter();
public MyPanel(){
Thread t1 = new Thread(new OneThread());
t1.start();
}
public void paintComponent(Graphics g){
g.drawString("TIME: ", 10, 20);
g.drawString(Integer.toString(counter), 50, 20);
}
}
and finally
package com.game.ryan;
public class OneThread implements Runnable{
private int counter = 45;
public OneThread(){
}
#Override
public void run() {
for(int x = 0; x >= 0; x++){
try{
Thread.sleep(1000);
counter++;
x++;
System.out.println(counter);
}catch(Exception e){
e.printStackTrace();
}
}
}
public int getThreadCounter(){
return counter;
}
}
I am getting an increasing counter on the console so I guess that part is working correctly.
Expected result was for the counter to display correctly in the JFrame (updating every 1000ms).
I don't see anywhere you tell the UI it should update itself.
You're also going a long way out of your way to replicate what is already available within the APIs.
Swing and threads need careful consideration. Swing uses a single threaded model for managing all the updates to the UI. It is expected that all iterations with the UI will be done within the context of the thread (AKA The Event Dispatching Thread).
This means any time you want to create or update the UI from any other thread, you need to synchronize the calls back to the EDT.
While there are a number of ways to achieve this, the simplest in your case in the use of javax.swing.Timer
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class LabelClock {
public static void main(String[] args) {
new LabelClock();
}
protected static final DateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm.ss");
private JLabel clock;
public LabelClock() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
clock = new JLabel();
tick();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
frame.add(clock);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
tick();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
});
}
protected void tick() {
clock.setText(DATE_FORMAT.format(new Date()));
}
}
You may wish to re-think your design:
A primitive variable holds a value and that's it. If you assign the value held by the primitive to another variable, changing the original variable later will have no effect on the value held by the other variable. For instance, nothing in your code changes value held by the counter variable held by the JPanel.
Note that even if your plan were successful, you have two completely independent OneThread objects, and that changing the state of one will have no effect on the other.
Better to have your GUI listen for changes to a variable and then have your thread change the state of the variable and then notify all listeners of this change. A PropertyChangeListener could work well for this.
Note that a Swing Timer would be much easier to implement then a background thread.

how to update a jLabel every time with a while loop with a delay

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
int count = jSlider1.getValue();
int delay = jSlider2.getValue();
int valueOfSlider = jSlider2.getValue();
int valueOfSlider2 = jSlider1.getValue();
while (count > 0)
{
count--;
String count2 = ""+count;
jLabel3.setText(count2);
try {Thread.sleep(delay); }
catch (InterruptedException ie) { }
}
It will eventually show the final number on the jLabel but it does not incrementally update the number. any help
Swing is single-threaded. Therefore, long-running tasks should never take place in the EDT. This includes sleeping. Instead, use a javax.swing.Timer. This will delay in a background thread, and then post an action to be executed in the EDT.
See also:
How to Use Swing Timers
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public final class JLabelUpdateDemo {
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI(){
final JFrame frame = new JFrame("Update JLabel Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new FlowLayout());
frame.getContentPane().add(JTimerLabel.getInstance());
frame.setSize(new Dimension(275, 75)); // used for demonstration purposes
//frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer t = new Timer(1000, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
int val = Integer.valueOf(JTimerLabel.getInstance().getText());
JTimerLabel.getInstance().setText(String.valueOf(++val));
}
});
t.start();
}
private static final class JTimerLabel extends JLabel{
private static JTimerLabel INSTANCE;
private JTimerLabel(){
super(String.valueOf(0));
setFont(new Font("Courier New", Font.BOLD, 18));
}
public static final JTimerLabel getInstance(){
if(INSTANCE == null){
INSTANCE = new JTimerLabel();
}
return INSTANCE;
}
}
}
This SSCCE imitates a counter that will count up from 0 every second (i.e. update the JLabel instance) until the application is terminated.
Your problem is that your doing something time consuming in an ActionPerformed callback, which executes in the event thread. In callbacks, you should do something quickly and return, even if that "something" is spawning a thread. The GUI can't update while you're occupying the event thread, it will only update after your callback returns.

Categories

Resources