Within a Java swing application, the following snippet is supposed to play a siren. It starts OK but stops prematurely, not always at the same time (i.e. sometimes it stops almost immediately and sometimes after a longer delay, but usually it does not finish playing the whole sound file). What could be causing this?
I've done my best to create a minimal example that still has the problem:
package monster;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.io.File;
import javafx.embed.swing.JFXPanel;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Example_2 extends JPanel {
protected static final long serialVersionUID = 1L;
public Example_2() {
setPreferredSize(new Dimension(100,100));
setBackground(Color.white);
createPanel();
}
public static void main(String[] args) {
Example_2 e = new Example_2();
JFrame f = new JFrame();
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.add(e, BorderLayout.CENTER);
f.pack();
f.setVisible(true);
f.repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
new Thread(
new Runnable() {
public void run() {
try {
File f = new File("sound/siren_short.wav");
String url = "file:///"+f.getAbsolutePath().replaceAll("\\\\","/").replaceAll(" ", "%20");
MediaPlayer mp = new MediaPlayer(new Media(url));
mp.play();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
public static JFXPanel createPanel() {
return new JFXPanel();
}
}
In the current example code "Example2" posted, it looks to me like the main() executes and exits. Yes, the call to MediaPlayer launches a background thread, but I'm guessing when the main() ends, this terminates the MediaPlayer thread as well (perhaps because it only has daemon status). I haven't used the MediaPlayer yet, myself, so I don't know if this behavior can result or not.
Here is a simple test for you: add the following line.
Thread.sleep(5000); // pauses 5 seconds
or something similar to the end of the main(), after the repaint() call. The number is milliseconds: use a number that is longer than the length of your sound. Does the sound now play to completion? You'll probably have to put the new line of code in a try...catch block.
Check out the api for Thread() when you get a chance. When you get to the page, I'd recommend doing a search on "daemon". There are methods for testing or setting daemon status, plus a terse explanation of what the status means. There is a chance that if you set your runnable to not be a daemon, the program (as shown) will play once and then hang with no way to terminate it except killing it via Eclipse (if it was run in Eclipse) or via an OS task manager.
I haven't delved much into debugging threads in Eclipse--am afraid I can't offer any suggestions there.
Andrew Thompson made a good suggestion, to make use of Clip. This has the benefit of keeping us in "familiar" territory. I've used Clip frequently, but not JavaFX libraries. If you had used Clip, the line of code I'm suggesting (Thread.sleep) would definitely have been needed to allow the sound to play to completion.
Here is a theory: the code which loads and executes the play() command is non-daemon. However, the background processes which deliver the sound data to the line are daemon. If that were the case, then the behavior I describe (termination after the main thread is done) would be consistent.
Related
This is simplified code, which would be called from pressing a button in my main JFrame class. Using this code, and then dismissing one of the resulting dialogs, causes all of my active windows in my Java session to either lock up or just go away.
//Broken code
private void buttonActionPerformed(java.awt.event.ActionEvent evt) {
List<JFrame> frameList = new ArrayList<>();
frameList.add(new TestJFrame());
frameList.add(new TestJFrame());
frameList.forEach(frame -> frame.setVisible(true));
frameList.forEach(frame -> {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(frame, "Msg", "Title", 0);
frame.setVisible(false);
frame.dispose();
});
});
}
However, if I were to remove the SwingUtilities.invokeLater() section then it works like I would expect (dialog pops up, close the dialog, window goes away, repeat).
//Working code
private void buttonActionPerformed(java.awt.event.ActionEvent evt) {
List<JFrame> frameList = new ArrayList<>();
frameList.add(new TestJFrame());
frameList.add(new TestJFrame());
frameList.forEach(frame -> frame.setVisible(true));
frameList.forEach(frame -> {
//SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(frame, "Msg", "Title", 0);
frame.setVisible(false);
frame.dispose();
//});
});
}
I'd rather not use the second one because the actual code is being started in a background thread that is notifying a set of listeners, so if I were to use the second one then it would block up the thread and slow down listeners until the user responds (when I could be processing during that time). What about the invokeLater() is breaking me? Is this expected behavior?
NOTE: This is simplified code pulled out of how I'm actually using it, but the core issue still exists (I have multiple JFrames, and if multiple JOptionPane.showMessageDialog()s were put on invokeLater()s for different JFrames then it breaks me. I tested the above code with a new, isolated, JFrame class created in Netbeans and was able to reproduce the error.
EDIT: I can't seem to reproduce the error in Windows, only seems to happen in Linux.
It is most likely invokeLater() which is breaking your code. If you want to thread this action try using a simple thread or
EventQueue.invokeLater(new Runnable()
{
public void run()
{ //Your joptionpane here
}
});`
Instead of invoke later i prefer using a
1) simple thread
2) TimerTask
3) ScheduledExecutorService
Use one of these methods.
This is an example for using timer task
import java.util.Timer;
import java.util.TimerTask;
public class Task2 {
public static void main(String[] args) {
TimerTask task = new TimerTask() {
#Override
public void run() {
// task to run goes here
System.out.println("Hello !!!");
}
};
Timer timer = new Timer();
long delay = 0;
long intevalPeriod = 1 * 1000;
// schedules the task to be run in an interval
timer.scheduleAtFixedRate(task, delay,
intevalPeriod);
} // end of main
}
If you are not satisfied you can use invoke later.
But remember never use invoke and wait its a bad idea
Here is my approach, as I understand the problem is on the locked windows, which are waiting for an event to finish, in swing the events are related with the AWT-EventQueue.
Here is a little explanation about: https://stackoverflow.com/a/22534931/1670134
So, in order to get your window working I used the Future type:
From the java doc:
A Future represents the result of an asynchronous computation. Methods
are provided to check if the computation is complete, to wait for its
completion, and to retrieve the result of the computation. The result
can only be retrieved using method get when the computation has
completed, blocking if necessary until it is ready.
package com.stackoverflow.future;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import com.stackoverflow.frame.MyFrame;
public class MySwingWorker extends SwingWorker<Void, Void>{
#Override
protected Void doInBackground() throws Exception {
final ExecutorService service = Executors.newFixedThreadPool(1);
List<Future<JFrame>> frameList = new ArrayList<Future<JFrame>>();
frameList.add(service.submit(new SwingLoader(new MyFrame())));
frameList.add(service.submit(new SwingLoader(new MyFrame())));
try {
service.shutdown();
service.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
ex.printStackTrace();
} finally {
service.shutdownNow();
}
return null;
}
public static void main(String[] args) {
MySwingWorker mySwingWorker = new MySwingWorker();
SwingUtilities.invokeLater(mySwingWorker);
}
}
The loader:
package com.stackoverflow.future;
import java.util.concurrent.Callable;
import javax.swing.JFrame;
public class SwingLoader implements Callable<JFrame>{
private JFrame frame;
public SwingLoader(JFrame frame){
this.frame = frame;
}
#Override
public JFrame call() throws Exception {
frame.setVisible(true);
return frame;
}
}
NOTE: This code is just a proto, in order to provide you with ideas and it must be modified and cleaned in order to achieve the desired results.
Here you are a link with a couple of explanations of each type:
http://winterbe.com/posts/2015/04/07/java8-concurrency-tutorial-thread-executor-examples/
On the Linux platform, Frame::getBounds and Frame::setBounds do not work consistently. This has already been reported in 2003(!), see here:
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4806603
For convenience, I have simplified the stated code that results in a bug and paste it as:
import java.awt.Button;
import java.awt.Frame;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/** Demonstrates a bug in the java.awt.Frame.getBounds() method.
* #author Mirko Raner, PTSC
* #version 1.0 (2003-01-22) **/
public class GetBoundsBug extends Frame implements ActionListener {
public static void main(String[] arg) {
GetBoundsBug frame = new GetBoundsBug();
Button button = new Button("Click here!");
button.addActionListener(frame);
frame.add(button);
frame.setSize(300, 300);
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent event) {
Rectangle bounds = getBounds();
bounds.y--;
setBounds(bounds);
bounds.y++;
setBounds(bounds);
}
}
Unexpected behavior: Upon clicking the button the window is shifted slightly below! (On my system by 28 pixels each click.)
Here is a screen recording: https://youtu.be/4qOf99LJOf8
This behavior has been around for 13+ years, so probably there won't be any change from the official side.
Does anybody have a workaround for this bug? Specifically, I would like to store and restore the window/frame/dialog at the previous location reliably on all platforms.
PS: My java installation is jdk1.8.0_102 for amd64 by Oracle on Ubuntu 16 Linux. Since I recently migrated from Windows to Ubuntu, I know that on Windows, the code above works as expected.
The adaptation to Swing using SwingWorker produces the same effect:
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
public class GetBoundsBug extends JFrame implements ActionListener {
public static void main(String[] arg) {
GetBoundsBug myJFrame = new GetBoundsBug();
JButton myJButton = new JButton("Click here!");
myJButton.addActionListener(myJFrame);
myJFrame.setContentPane(myJButton);
myJFrame.setSize(300, 300);
myJFrame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent event) {
SwingWorker<Void, Void> mySwingWorker = new SwingWorker<Void, Void>() {
#Override
public Void doInBackground() {
Rectangle myRectangle = getBounds();
myRectangle.y--;
setBounds(myRectangle);
myRectangle.y++;
setBounds(myRectangle);
return null;
}
};
mySwingWorker.execute();
}
}
Well, in the original bug database entry this is marked as "Won't fix" and explained as a quirk in the window manager, rather than the JDK.
What window manager are you using?
Just an additional note. I noticed that your code failed to do very much on the Event Dispatch Thread. All of Java's drawing APIs are single threaded by design, meaning that your application should not be expected to work correctly unless you dispatch GUI updates to the Event Dispatch Thread.
This means you need to (in your main) create a new Runnable that when evaluated will present your widgets, and submit that to the EDT.
Also, your action listener updates the component state within the action, bypassing the repaint request and ignoring the typical safeties required to ensure dispatch to the EDT.
In both of these ways, you code is not valid GUI code in a Java environment, and the bug that you identify may have nothing to do with your behavior, as your program is violating the GUI toolkit design before you even know if the bug impacts it.
Also, awt only wraps components. If the components lie, then the lie trickles into Java. Not much can be done about that (but I no longer think it's the primary thing to worry about). If you don't like that, use Swing, which is a much more sane / stable environment.
I'm currently working on an application for work that has a main JFrame that always exists. I currently have a child JDialog that shows up on a button press. This frame has a JMenu with an item to "log out of the display." I've been tasked to ensure this child JDialog goes away when the log out of the display option is pressed. When the logout occurs, the main display is set invisible via:
mainFrame.setVisible(false);
The child JDialog has the default close operation:
DISPONSE_ON_CLOSE
When the user logs back in, the first thing that's done is:
mainFrame.setVisible(true);
When this happens, the child dialog shows back up. Looking at the JDialog Javadoc, this seems to be expected behavior. However I haven't found a way to break the parent/child releationship or completely destroy the child JDialog. It also seems like the JDialog will remain until it has been GC, which may not happen in a timely manner.
Here is a sample program that simulates the behavior I'm seeing:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
public class WindowTest {
public static void createAndShowGUI() {
JFrame aFrame = new JFrame("LAUNCHER");
final JFrame aParent = new JFrame("PARENT");
final JDialog aChild = new JDialog(aParent);
aParent.setSize(200,200);
final JToggleButton showParentButton = new JToggleButton("HIDE");
showParentButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showParentButton.setText(!showParentButton.isSelected() ? "SHOW": "HIDE");
aParent.setVisible(!showParentButton.isSelected());
}
});
aChild.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
aChild.setSize(200,200);
aParent.addComponentListener(new ComponentAdapter() {
public void componentHidden(ComponentEvent e) {
aChild.dispose();
aChild.setVisible(false);
}
});
aFrame.setContentPane(showParentButton);
aFrame.pack();
aFrame.setVisible(true);
aParent.setVisible(true);
aChild.setVisible(true);
}
public static void main(String [] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
createAndShowGUI();
}
});
}
}
When the parent is hidden, the child is disposed. When the parent is shown, the child shows back up. What's really weird is that when I press the X on the child: when the parent is hidden and then shown again, the child does not show back up.
The only difference I see is that clicking the X also fires a WindowClosing event. I tried the dispatch the even, in the componentHidden method above by:
//Added into the constructor
//add to the imports: import java.awt.event.WindowEvent;
aParent.addComponentListener(new ComponentAdapter() {
public void componentHidden(ComponentEvent e) {
aChild.dispose();
aChild.setVisible(false);
WindowEvent closingEvent =
new WindowEvent(aChild, WindowEvent.WINDOW_CLOSING);
aChild.dispatchEvent(closingEvent);
}
});
And that didn't solve the problem.
Currently it looks like my only option is to change the type of child to a JFrame. I just wanted to know if there was a proper way of disposing a child JDialog.
I'm currently running with Java version: 1.7.0_76 64 bit on Redhat Enterprise Linux Server release 6.4.
I wasn't aware the naming conventions affected the compile.
It doesn't. Conventions are done for consistency and readability and maintainability. The person who writes the code is not always the person that maintains the code. So if you want other people to read your code, especially when asking for help, follow the standards.
You can start with Java Programming Style Guidelines
I'm copying the code by hand from another screen per my companies standards.
This is a complete waste of time. There is nothing proprietary about your code. Again when you ask a question, the code should be in the form of a SSCCE so it demonstrates the problem. This allows you to remove all the unnecessary code.
it would be straight forward to figure out the imports.
Exactly, so you should do it. You want us to help you, so why should we spend the time figuring it out??? Make is as easy as possible for people to want to help you.
Adding the imports did not help. The code you posted still does not compile so I don't know if it accurately reflects the problem you are attempting to describe.
Again the point of posting code is so that we can copy/paste/compile/test. Until you post a proper SSCCE I will not provide the answer.
Edit:
From my testing, if the visibility of the child window is changed by the visibility of the parent when the parent is made non-visible, then the visibility of the child is also changed by the parent when it is made visible. So it looks like the parent is retaining the state of the child windows when the visibility changes.
So the solution is to make the child window non-visible before the parent:
showParentButton.setText(!showParentButton.isSelected() ? "SHOW": "HIDE");
aChild.setVisible(false); // add this
aParent.setVisible(!showParentButton.isSelected());
If you don't have a reference to the child window then I guess you can use the Windows.getOwnedWindows() method to access all the child windows.
Another edit:
As a hack I created a custom dialog that can't be shown again once it is disposed:
final JDialog aChild = new JDialog(aParent)
{
private boolean disposed = false;
#Override
public void dispose()
{
super.dispose();
disposed = true;
}
#Override
public void show()
{
if (disposed)
return;
super.show();
}
};
I found the method addAWTKeyListener in the class Toolkit, but I can't get it to work properly, whether or not the window has focus. My code is as follows:
import java.awt.AWTEvent;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.KeyEvent;
public class KeyTest {
public static void main(String[] args){
Thread t = new Thread(){
#Override
public void run() {
System.out.println("STARTING");
Toolkit kit = Toolkit.getDefaultToolkit();
kit.addAWTEventListener(new AWTEventListener(){
#Override
public void eventDispatched(AWTEvent event) {
System.out.println("EVENT");
if(event instanceof KeyEvent){
KeyEvent kEvent = (KeyEvent) event;
System.out.println(kEvent.getKeyCode());
}
}
}, AWTEvent.KEY_EVENT_MASK);
while(true);
}
};
t.start();
}
}
Is there something I'm doing wrong? I get to the point that STARTING prints and there are no errors. The even is simply not called.
I may be wrong as I'm certainly not an expert, but as far as I know what you're trying to do isn't possible in Java.
Are you trying to capture a key click using a Java program, but without creating a window? Part of Java's security, and this is what I may be wrong on, is that it can only listen to events inside Java windows created by that particular Java program.
So if you were trying to make something key-logger-esque that runs in the background and captured a key press, it wouldn't be able to do that.
I wish I could give you a more concrete answer but I hope this helped.
Just a guess, but you your sample doesn't have any AWT windows in it, so I'm guessing that is why the event never gets fired.
When you say "whether or not the window has focus" does your real app have windows that you have chopped out, or are you talking about a java console window or similar?
Can anyone tell me why does this timer run only once?
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TimerTest implements ActionListener{
private Robot r;
private Timer t;
private int i;
public TimerTest(){
i = 0;
try {
r = new Robot();
} catch (AWTException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t = new Timer(1000, this);
t.setRepeats(true);
t.start();
}
public static void main(String [] args){
new TimerTest();
}
#Override
public void actionPerformed(ActionEvent arg0) {
i++;
System.out.println("Action..." + i);
}
The funny thing is that, if I decrease the delay in the Timer to just 100, it works as expected. And what's even funnier is that if I delete the code in which I initialize the Robot, it doesn't work at all, the program terminates as soon as I run it.
I've tried this on Windows 7 and on Ubuntu (although on Ubuntu I couldn't use the Robot at all, since I get an exception. Something related to rights, maybe).
Your main is processed so the program stops. You can test it by using this code, adding it to TimerTest()
JFrame testFrame = new JFrame();
testFrame.setVisible(true);
testFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
That JFrame keeps your main not from finshing, when you close the Frame the TimerTest ends. Which concludes your main which causes the main to finsh. Ending the program and stoping your swing timer.
See "main exits before javax.swing.Timer's start() can start" at the bug database.
Evaluation
Described behavior - when application exits before Swing timer is started - is correct. Here is what's going then:
Swing timer is created.
Separate thread for swing timer is started. It will notify attached actionListeners when the timeout is passed by posting an instance of InvocationEvent to EDT.
Main thread exits.
At this moment there is no non-daemon threads running in JVM. Application is terminated.
..the evaluator goes on to add..
..This looks like a RFE rather than a defect.
One surefire way to make it behave is to create a GUI element and display it. Which is why I asked earlier..
..why exactly are you creating the timer without any GUI elements? Is this for repeated screen-shots?
To handle that situation, I would typically create and show a frame to allow the user to configure the rate and area for screenshots, then minimize the frame and begin processing when the user clicks:
Screen Capture!