I'm reading a media with vlcj and I would like to display the time elapsed and the remaining time in some JLabels. I wrote some code but it seems my JLabel.setText don't refresh more that 2 times per second.
To make some more try and being sure that it's not the vlcj's thread that would have some troubles, I wrote a very code with a JLabel. The aim of this simple code is to update the JLabel 10 times per seconds.
Here is my code :
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class TestLabel extends JFrame implements Runnable{
JLabel label = new JLabel("0");
int i=0;
TestLabel() {
this.setTitle("Test");
this.setSize(200, 200);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setContentPane(label);
this.setVisible(true);
}
public static void main(String[] args) {
TestLabel tLabel = new TestLabel();
Thread t1 = new Thread(tLabel);
t1.start();
}
#Override
public void run() {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
i+=1;
System.out.println(i);
label.setText(String.valueOf(i));
}
}, 0, 100, TimeUnit.MILLISECONDS);
}
}
Result : in the console, I get 1, 2, 3, 4... But in the JLabel, I have something like : 1, 2, 3 (...) 32, 37, 42, 47. It seems that the System.out.println write each "i", but the JLabel don't. Why do I have this artefact ?
Thank you for all your reply. Regards.
Thou shalt not use swing components from a thread other than the event dispatch thread.
So, either use a Swing Timer rather than a ScheduledExecutorService, or wrap the label change into SwingUtilities.invokeLater().
BTW, the call to new TestLabel(); should also be wrapped into SwingUtilities.invokeLater(). Read the swing concurrency tutorial.
You need to call the SwingUtilities.invokeLater method to properly update your GUI text while using Swing (i.e. JFrame, JLabel, etc).
public void run() {
i+=1;
System.out.println(i);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.setText(String.valueOf(i));
}
});
}
For more information about SwingUtilities.invokeLater, check out this SO post.
Don't use a ScheduledExecutorService. Swing components need to be updated on the Event Dispatch Thread (EDT).
Instead you should be using a Swing Timer. Read the section from the Swing tutorial on How to Use Timers for more information.
Here is a simple example using a Timer: How to make JScrollPane (In BorderLayout, containing JPanel) smoothly autoscroll. Just change the interval to 100, instead of 1000.
Related
I use .show() before a "blocking" code like a while loop. But even though the .show gets called, the UI doesn't actually show the called panel.
Here is the code that shows the issue:
(WARNING: This code contains a while true loop.)
import javax.swing.JFrame;
import java.awt.CardLayout;
import java.awt.event.ActionEvent;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JButton;
public class CardTest extends JFrame{
public CardTest() {
CardLayout cl = new CardLayout(0,0);
getContentPane().setLayout(cl);
JPanel panelA = new JPanel();
getContentPane().add(panelA, "PanelA");
JLabel lblPanelA = new JLabel("Panel A");
panelA.add(lblPanelA);
JButton btnSwitchToPanel = new JButton("Switch to Panel B");
panelA.add(btnSwitchToPanel);
JPanel panelB = new JPanel();
getContentPane().add(panelB, "PanelB");
JLabel lblPanelB = new JLabel("Panel B");
panelB.add(lblPanelB);
btnSwitchToPanel.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent event) {
cl.show(getContentPane(), "PanelB");
getContentPane().revalidate();
// Here is the problem. Even though cl.show is called first,
// it still doesn't show, before the while loop has terminated.
int i = 0;
while(i < 1000000){
i++;
System.out.println(i);
}
}
});
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setVisible(true);
}
public static void main(String[] args){
new CardTest();
}
}
If you are wondering, I need this for a downloader, where the while true loop (to download a file) is called after pressing a button in the first panel. The second panel contains the progress bar. But the progress panel never gets displayed even though the .show function is called before the download code.
UPDATE
I do know that putting the loop into a new thread, solves the draw problem, but it also introduces other problems, because I rely on sequential execution of functions after the loop(download file(loop), Unzipp file, move those files...).
The best solution would be to find a way to allow the .show() call to actually take the time to switch panes before continuing with the loop.
I use .show() before a "blocking" code like a while loop. But even though the .show gets called, the UI doesn't actually show the called panel.
Yes, because you are "blocking" the Event Dispatch Thread (EDT) which is responsible for repainting the GUI. So the GUI can't repaint itself until the code finishes executing.
You need to create a separate Thread to executing the long running task so you don't block the EDT. One way to do this is to use a SwingWorker. The SwingWorker will create the Thread for you and will notify you when the task is complete so you can update the GUI.
Read the section from the Swing tutorial on Concurrency for more information and a working example.
This happens because you are doing work on the EventDispatchingThread. This Thread is also responsible for actually drawing the GUI.
You have no other choice than doing your work in another thread.
E.g.: (Quick + Dirty)
new Thread(){
#Override
public void run() {
while (...) {...}
}
}.start();
This is because redrawing the UI is done in the same thread as event processing and doesn't happen until after the event processing has completed (i.e., all event handling methods have returned).
Best thing to do is move that "blocking" code into a runnable and execute it in a worker thread.
I want to load images in JFrame such a way that it should look like it is video.
For that I thought that I will change Images so much faster (20 images/sec.)
but Problem is when new Image get load its shows fully black window.
I dont know Why it happens.
Suggest me where I goes wrong.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.io.*;
import javax.imageio.ImageIO;
class VideoI extends JPanel {
private Image img;
private Graphics graphics;
ImageIcon icon;
VideoI(){
icon=new ImageIcon("D:\\Videos\\1.jpg");
add(icon);
}
public void paintComponent(Graphics g) {
graphics=g;
repeatImgs();
}
public void repeatImgs(){
for(int i=0;i<25;i++)
{ try{
img=ImageIO.read(new File("D:\\Videos\\"+i+".jpg"));
graphics.drawImage(img, 0, 0, null);
System.out.println(""+i);
Thread.sleep(1000);
}catch(Exception e){System.out.println(""+i+":"+e);}
}
}
}
public class Video extends JFrame
{
public static void main(String args[])
{
new Video().start();
}
public void start()
{
VideoI panel = new VideoI();
add(panel);
setVisible(true);
setSize(1300,800);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}
You are blocking the event dispatch thread. Use a swing Timer to repaint the component at the desired frequency.
You should never, ever sleep() in the EDT. What you want is essentially
public void paintComponent(Graphics g) {
// No loops or delays, just fetch the next image, preferrably it has been
// already been loaded by another thread.
g.drawImage(getNextImage(), 0, 0, null);
}
And a timer task:
ActionListener timerTask = new ActionListener() {
#Override
public void actionPerformed(final ActionEvent e) {
panel.repaint();
}
};
Timer timer = new Timer(50, timerTask);
When you want to start the video, just call timer.start().
Finally, you should also wrap creating the GUI with SwingUtilities.invokeLater().
You're sending the Event Dispatch Thread (the UI update thread) to sleep, that's why you get screen issues.
Try loading and switching images in a worker thread (have a look at the SwingWorker JavaDoc).
I'm no Swing expect, but I would guess this happens because you stop the Swing thread with the Thread.sleep. You should do the image changing and timing outside of the swing thread and use SwingUtilities.invokeLater to draw the Image. Also you need to sleep 50ms, not a whole second for 20fps. Using a ScheduledExecutorService whould fit here.
Also you always load the image from disc, when it needs to be rendered. This could be to slow. It would be better to load all image on start up and then just change the image.
I've made a program that sets a button's setEnable from time to time. The Thread.sleep() is in another class. Here's the code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Try extends JFrame implements ActionListener{
JButton n1 = new JButton("1");
JButton n2 = new JButton("2");
JButton n3 = new JButton("3");
JButton show = new JButton("Show");
{
show.addActionListener(this);
n1.setEnabled(false);
n2.setEnabled(false);
n3.setEnabled(false);
}
public Try(){
super("Try");
setVisible(true);
setSize(500, 200);
setLayout(new GridLayout(1, 4));
add(n1);
add(n2);
add(n3);
add(show);
}
public void actionPerformed(ActionEvent a) {
Object clicked = a.getSource();
if(show == clicked){
new EasyLevel1().start();
}
}
class EasyLevel1 extends Thread {
public void run() {
try {
n1.setEnabled(true);
Thread.sleep(1000);
n1.setEnabled(false);
n2.setEnabled(true);
Thread.sleep(1000);
n2.setEnabled(false);
n3.setEnabled(true);
Thread.sleep(1000);
n3.setEnabled(false);
} catch (InterruptedException e){
}
}
}
public static void main(String[] args){
Try frame = new Try();
frame.setVisible(true);
}
}
However, when I put it on my actionListener within the class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Try extends JFrame implements ActionListener{
JButton n1 = new JButton("1");
JButton n2 = new JButton("2");
JButton n3 = new JButton("3");
JButton show = new JButton("Show");
{
show.addActionListener(this);
n1.setEnabled(false);
n2.setEnabled(false);
n3.setEnabled(false);
}
public Try(){
super("Try");
setVisible(true);
setSize(500, 200);
setLayout(new GridLayout(1, 4));
add(n1);
add(n2);
add(n3);
add(show);
}
public void actionPerformed(ActionEvent a) {
Object clicked = a.getSource();
if(show == clicked){
try {n1.setEnabled(true);
Thread.sleep(1000);
n1.setEnabled(false);
n2.setEnabled(true);
Thread.sleep(1000);
n2.setEnabled(false);
n3.setEnabled(true);
Thread.sleep(1000);
n3.setEnabled(false);
} catch (InterruptedException e){}
}
}
public static void main(String[] args){
Try frame = new Try();
frame.setVisible(true);
}
}
It freezes my whole program, based from that example I've understood that a thread sleep should be ran in another thread to stop the current class from freezing. But I expected the new thread.sleep with still freeze its operations like it will still do the code above but the buttons will be responsive since its in another thread. But surprisingly it did what I wanted it to do, It didn't instantly set everything to disabled like the first program.
Thread.sleep() makes the current thread pause. You are running it in an actionPerformed, that is in a Swing Event. All Swing operations are done in a single thread, the EDT. When you pause it with a Thread.sleep(), Swing can not handle any other event because you haven't returned from the actionPerformed listener. Therefore, the GUI freezes (not the complete application, just the GUI).
In general, it is bad practice to do long-running actions in a Swing event because of this. For what you are trying to do, the good alternative is to use Swing timers.
What is happening is that in the 2nd example Thread.sleep is blocking the EDT so no further UI updates occur. In contrast, in the first example you are calling sleep in a separare Thread so no "freezing" occurs. For tasks like this the use of Swing Timers is preferred.
Thread.sleep will cause the thread that executes the call to sleep for the specified time (or until the thread is interrupted). When you call it in the actionPerformed method, it causes the UI thread to sleep. That's why your program is locking up.
You should start a separate thread that will step through the various calls you want to make while sleeping in between. Alternatively (and much better, in my opinion) you could use Swing timers to do what you want.
My code as below is not working, can anyone tell me why? Please also correct my code, I am very new to Java. Besides that, I am searching for the "loading panel component", something like ProgressMonitor but maybe more attractive and which animates better. Please suggest me if anyone has used such things before.
public class Main extends JFrame {
private JPanel contentPane;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Main frame = new Main();
frame.setVisible(true);
ProgressMonitor pm = new ProgressMonitor(frame, "Loading...",
"waiting...",
0, 100000);
for (int i = 0 ; i < 100000 ; i ++){
pm.setProgress(i);
pm.setNote("Testing");
System.out.println(i);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Main() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
}
}
As #Mark Rotteveel already indicated, you are keeping the EDT (Event Dispatch Thread) occupied. The tutorial on 'How to show progress bars/monitors' contains valuable information and code samples.
But basically it comes down to moving your calculations to a worker Thread (e.g. using a SwingWorker), and showing the ProgressMonitor on the EDT. It is up to the worker thread to indicate to the ProgressMonitor what progress has already been made.
And here is a direct link to the sample code of that tutorial which clearly shows how the work is done in the SwingWorker extension (the Task class in that example), and how the ProgressMonitor gets updated by adding a PropertyChangeListener to the SwingWorker, where the listener passes the progress to the ProgressMonitor.
I would also suggest to read the Concurrency in Swing tutorial which contains more information on how to handle Threads in combination with Swing, and why you can't/shouldn't do heavy calculations on the EDT
In Swing, the event-thread is what modifies and updates the GUI. You are keeping the event-thread busy with that for-loop and sleep, so it cannot update the GUI. All things you do on the event-thread should be short-lived. Everything else should be moved off the event-thread.
So you need to move that for-loop out of the event-thread.
1. Consider this as the rule of thumb, UI work on UI thread, and Non-UI work on Non-UI thread.
2. Event Dispatcher Thread (EDT) is the UI thread here, and so you should keep your Non-UI process intensive work on a separate thread OUT of the EDT.
3. You can do this in 2 ways.....
i. Create a separate Thread to do this.
ii. Use SwingWorker to synchronize the Non-UI and the UI thread.
4. Always keep the main() method only for making the JFrame visible in the EDT.
eg:
public static void main(String[] args){
EventQueue.invokeLater(new Runnable(){
public void run(){
myframe.setVisible(true);
}
}
}
Take a look at this site for the working example of SwingWorker:
http://www.kodejava.org/examples/381.html
The following code slides a card across the screen. When I shut down the main window, I expect the event dispatch thread to shut down as well, but it does not. Any ideas on why the ScheduledExecutorService thread prevents the EDT from shutting down?
import java.awt.Graphics;
import java.net.URL;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main extends JPanel
{
private float x = 1;
public void next()
{
x *= 1.1;
System.out.println(x);
repaint();
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
URL url = getClass().getResource("/209px-Queen_of_diamonds_en.svg.png");
g.drawImage(new ImageIcon(url).getImage(), (int) x, 50, null);
}
public static void main(String[] args)
{
JFrame frame = new JFrame();
final Main main = new Main();
frame.getContentPane().add(main);
frame.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1, new ThreadFactory()
{
public Thread newThread(Runnable r)
{
Thread result = new Thread(r);
result.setDaemon(true);
return result;
}
});
timer.scheduleAtFixedRate(new Runnable()
{
public void run()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
main.next();
}
});
}
}, 100, 100, TimeUnit.MILLISECONDS);
}
}
The default behaviour when you close a JFrame is simply to hide it, not to cause the application to exit. You need to call:
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
In other words: This has nothing to do with the ScheduledExecutorService; It is to do with the fact that the Event Dispatch thread is not a daemon thread.
ADDITIONAL
Rather than use a ScheduledExecutorService which in turn calls SwingUtilities.invoke... you should consider using javax.swing.Timer, which will fire ActionEvents periodically directly on the Event Dispatch thread, hence making your code simpler / more compact and removing the need for the additional thread.
Also, you are recreating the ImageIcon on every animation frame which will be very inefficient, particularly in a tight animation loop. Far better to create it once when the application starts.
Your thread factory is correct. If you set EXIT_ON_CLOSE on the frame then it will exit.
However, consider using a library such as Trident instead.
I ran across the answer in this excellent blog post: http://www.pushing-pixels.org/?p=369
With the current implementation, AWT terminates all its helper threads allowing the application to exit cleanly when the following three conditions are true:
There are no displayable AWT or Swing components.
There are no native events in the native event queue.
There are no AWT events in java EventQueues.
[...]
In the current implementation this timeout is 1000 ms (or one second). What this effectively means that AWT is not shutdown immediately after disposing the last window in your application and processing all pending events. Instead, it wakes every second, checks for any pending or processed events during the sleep and continues sleeping if there have been any such events.
The author goes on to say that his code posts an event to the EDT every 100ms in spite of the fact that the associated Window is no longer visible. This is exactly what happens in my case as well! The ScheduledExecutorService is posting events into the EDT, which in turn prevents AWT from shutting down, which in turn means that the ScheduledExecutorService will keep on posting more events.
As an aside, I am surprised by the number of people that recommend the use of JFrame.EXIT_ON_CLOSE. Each to his own I guess, but I recommend you read http://findbugs.sourceforge.net/bugDescriptions.html#DM_EXIT
I think that, rather than using daemon threads in your ScheduledExecutorService, you'd better explicitly shut it down when the user wants to quit.
You can do that by adding a WindowListener to the main frame:
public static void main(String[] args)
{
JFrame frame = new JFrame();
final Main main = new Main();
frame.getContentPane().add(main);
frame.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
final ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
timer.scheduleAtFixedRate(new Runnable()
{
public void run()
{
// NOTE that you don't need invokeLater here because repaint() is thread-safe
main.next();
}
}, 100, 100, TimeUnit.MILLISECONDS);
}
// Listen to main frame closure and shut down timer
main.addWindowListener(new WindowAdapter()
{
public void windowClosed(WindowEvent e)
{
timer.shutdownNow();
}
});
}
Note the changes I've made to your snippet:
timer is now declared final (needed
as it is referenced by an inner
anonymous class)
There is no more ThreadFactory passed to newScheduledThreadPool
I have removed the
use of invokeLater for calling
main.next() because the only Swing
call made there is repaint() which
is one of the few Swing methods that
are thread-safe.
Please note that I haven't tried the code above, it should compile and I think it should also solve your problem. Try it and let us know!