Here's an interesting bug (read: i've probably missed something) in Java Swing I spent the last 2 days trying to trace.
First things first, create a SSCCE.
Here you go.
class GUI extends JFrame{
public static void main(String[] args){
// All GUI work should be handled in the EDT.
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
new GUI().setVisible(true);
}
});
}
GUI(){
// Make a nice icon
ImageIcon img = new ImageIcon(this.getClass().getClassLoader().getResource("img/1.png"));
// Make a TrayIcon with the image
SystemTray sysTray = SystemTray.getSystemTray();
TrayIcon trayIcon = new TrayIcon(img.getImage());
try {
sysTray.add(trayIcon);
}
catch(AWTException e) {
e.printStackTrace();
System.out.println("System Tray unsupported!");
}
this.setTitle("Example GUI");
this.setIconImage(img.getImage());
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
}
}
If I run this and close the Window, I would expect it to dispose, and the various Threads to terminate. This is the case if I comment out the "Make a TrayIcon" try/catch block.
The sysTray.add() line seems to not create an Exception, but having it in the code stops the Threads from terminating, as the code hangs on a wait() in the AWT-EventQueue Thread.
Is this a bug, or something I'm missing?
Cheers all.
To get the program to terminate properly when you close it, you need to set the DefaultCloseOperation to EXIT_ON_CLOSE, like this:
GUI.setDefaultCloseOperation(EXIT_ON_CLOSE);
EXIT_ON_CLOSE is defined in JFrame so you don't need to define it or import it from anywhere.
Check the API for more exit operations:
Related
Basically I have got a game with main class.
public static void main(String[] args) throws InterruptedException {
JFrame frame = new JFrame("Mini Tennis");
Game game = new Game();
frame.add(game);
frame.setSize(300, 400);
frame.setResizable(false);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
while (true) {
game.move();
game.repaint();
Thread.sleep(10);
}
}
When I am loading a class directly it working fine.
But when I am calling it from another class, it does not paint anything.
private void btnGameActionPerformed(java.awt.event.ActionEvent evt) {
try {
String[] args = null;
Game.main(args);
} catch (InterruptedException ex) {
Logger.getLogger(DrawerMain.class.getName()).log(Level.SEVERE, null, ex);
}
}
And after a few seconds it paints that I have lost a game. So basically game is running but I can't do anything and I don't see anything.
The reason for this is that in the 2nd case, where you are calling the main method from an ActionListener, you are running your main method on the UI thread. And since you have your active wait block in this code, the UI is never able to draw anything. You should check out javax.swing.Timer and replace the loop I've copied over below with an implementation that uses Timer
while (true) {
game.move();
game.repaint();
Thread.sleep(10);
}
You need to move your main code into a Constructor, main should be only on the 1st class or the "main" class you're going to run.
The main method is the entry point for your application to run, you shouldn't be calling it as if it were any other method.
public Game () {
JFrame frame = new JFrame("Mini Tennis");
Game game = new Game();
frame.add(game);
frame.setSize(300, 400);
frame.setResizable(false);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
while (true) {
game.move();
game.repaint();
Thread.sleep(10);
}
}
Then you call it as:
private void btnGameActionPerformed(java.awt.event.ActionEvent evt) {
try {
String[] args = null;
Game game = new Game();
} catch (InterruptedException ex) {
Logger.getLogger(DrawerMain.class.getName()).log(Level.SEVERE, null, ex);
}
}
You should read more about Constructors and Classes and Objects
Also as mentioned in other answers, Thread.sleep() will cause your application to freeze, you should use a Swing Timer instead to handle it in another thread, so your application won't freeze.
With Thread.sleep() your application will wait the time inside it before repainting it.
Asothers have said, you shouldn't call main() yourself. That said, you cannot have a Thread.sleep() in your loop - if it is run on the EventDispatchThread (which it must be) that will block the thread and you will never get repaints. You need to use a SwingWorker to do the looping.
First, you should not run everything from your main thread. And the main method is not for you to call, it's supposed to be the entry point of your application.
In your main method, do this:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI(); // in this method, you create your frame and other UI components and show them
}
});
This Runnable will run in the event dispatch thread and will make the UI ready before any actions related to the UI.
Once you have your UI ready, say, you created the frame and linked it to a game. Then use a class which acts like a controller of the frame and the game to listen to events it's interested in and update the UI when it needs to.
If you need to create a window running this game each time when some event happens, then you should define this window as a class. You just need to create an instance of this class and make the window visible. Or if you just need to restart the game, then use a single instance of the class across your application, and only restart the game in it. The controller class is always useful to notify the game to restart and as well as update the UI.
NOTE: I work a lot of hours and research google and stackoverflow but I cannot find answer.
I use Thread.sleep() in a JDialog and it freezes all other JDialog, JFrame and threads.
My example code:
public Guitest()
{
setSize(300,300);
// create a JDialog that make guitest wait
MyDialog dlg = new MyDialog();
dlg.setSize(100,100);
dlg.setVisible(true);
while(dlg.isWait())
{
try
{
Thread.sleep(1000);
} catch (InterruptedException ex)
{
Logger.getLogger(Guitest.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println("waiting mydialog");
}
}
class MyDialog extends JDialog
{
boolean wait = true;
JButton btn = new JButton("OK");
public MyDialog()
{
setSize(50,50);
btn.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
wait=false;
}
});
add(btn);
}
public boolean isWait()
{
return wait;
}
}
In this situation JDialog does not appear correctly:
inccorect appear jdialog
But it must be apper this:
true appear jdialog
How can I solve this problem. I want to make main thread wait another thread.And someone can correct my sample code or share a sample code with this situation.
IMHO, it appears like you have just one running thread. At first, we draw our JDialog, after that, you sleep your main thread because of the wait flag.
ie. you can't execute your button action listener, thus you can't awake your thread.
Hope it helps understanding.
Thread.Sleep() just sleeps the current thread (i.e. stops it from doing anything, such as redrawing, processing clicks etc), which in your case is the UI thread.
You need to use a worker thread. Any main work that needs to be done that may take a larger amount of time needs to be done in its own thread, and this is the thread that you will want to sleep. It is currently ran alongside the UI components and so this is why you're seeing them freezing.
A good reference is the documentation for concurrency for swing http://docs.oracle.com/javase/tutorial/uiswing/concurrency/
The following may be useful too:
http://java.sun.com/developer/technicalArticles/Threads/swing/
http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html
We want to add a menu to our 2D game.
So far, we had our game loop running on the main thread while the EventQueue from Swing was handling key inputs to our game. Now since we want to add the menu, we no longer can simply start the gameloop from our main method.
The problem: We don't know how to start the gameloop in such a way that the EventQueue keeps receiving the KeyEvents.
Here's what we have in our menu:
startGameButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
CardLayout cl = (CardLayout)Menu.this.getParent().getLayout();
cl.show(Menu.this.getParent(), "Game");
}
});
So clicking the startGameButton removes the menu from the JFrame and shows the game itself, but the game is frozen, because we don't start the gameloop from our main thread anymore. We think that it's here, that we need to start the gameloop.
So we created a new Thread that will run our gameloop to prevent freezing our UI:
startGameButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
CardLayout cl = (CardLayout)Menu.this.getParent().getLayout();
cl.show(Menu.this.getParent(), "Game");
Thread gameThread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
game.update();
game.repaint();
Thread.sleep(10);
}
}
});
gameThread.start();
}
});
This works perfectly, the game shows up, and it's running... BUT: Our KeyEvents are no longer firing. Somehow the EventQueue is not picking up keystrokes anymore and we don't know why.
Having the exact same code (creating the Thread and starting it) in the main method without using the CardLayout menu works perfectly fine, also getting KeyEvents. So we think the problem is somewhere in using the CardLayout but we don't know what it is exactly.
Thanks to D-Klotz for the comment about the input maps problem. Even though it was not the correct solution for my problem, it caused me to notice someting:
When switching from the JFrame to another window and then back, the KeyListener was working fine again. So the only line of code missing in the actionPerformed method was:
game.requestFocus();
Works like a charm. Thanks for your help again.
Assuming that Runtime.getRuntime().addShutdownHook(new Thread() has been set up correctly, how can the ShutdownHook thread be invoked when a Java application window (JFrame) is closed (in this case the only window) and the dispose window default close operation is DISPOSE_ON_CLOSE or EXIT_ON_CLOSE?
Note that for a quit command handled with a System.exit(0) which is then fed through the ShutdownHook thread, the application terminates correctly as all the associated threads are terminated before the Java application exits. So I want to accomplish the same thing by making the closing of the JFrame window go through the ShutdownHook thread clean up.
frame.addWindowListener() and override windowClosed(WindowEvent e). From your question it looks you just need an event handling when the window gets closed.
good luck!
This works according to your spec. If I close the frame by clicking the X button, the shutdown hook is invoked.
import javax.swing.*;
class ShutDownHookDemo {
public static void endMessage() {
// clean up threads here..
System.out.println("Thanks for using the app.");
}
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
endMessage();
}
};
Runtime.getRuntime().addShutdownHook(t);
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300,300);
f.setVisible(true);
}
}
Runtime.getRuntime().addShutdownHook(...);
I have a problem with my application where the user will open more than one window at a time. And i have added dispose() method to call on closing the window. Now i should keep at-least one window open all the time so that the application does not hides without closed fully. If you don't understand read the following scenario:
I have window A and window B opened at the same time. Now i can close either window A or Window B but not both. In other words window B should be allowed to close only if window A is opened and vice versa. How do i do this in swing ??
A simple kind-of windowManger is not really tricky, all you need is
WindowListener which keeps tracks of the Windows it's listening to
a defined place to create the windows and register the the listener
make the windows do-nothing-on-close and make the listener responsible for the decision of whether to close or not (will do so for all except the last)
Some snippet:
// the listener (aka: WindowManager)
WindowListener l = new WindowAdapter() {
List<Window> windows = new ArrayList<Window>();
#Override
public void windowOpened(WindowEvent e) {
windows.add(e.getWindow());
}
#Override
public void windowClosing(WindowEvent e) {
if (windows.size() > 1) {
windows.remove(e.getWindow());
e.getWindow().dispose();
}
}
};
// create the first frame
JFrame frame = createFrame(l);
frame.setVisible(true);
// a method to create a new window, config and add the listener
int counter = 0;
private JFrame createFrame(final WindowListener l) {
Action action = new AbstractAction("open new frame: " + counter) {
#Override
public void actionPerformed(ActionEvent e) {
JFrame frame = createFrame(l);
frame.setVisible(true);
}
};
JFrame frame = new JFrame("someFrame " + counter++);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.add(new JButton(action));
frame.addWindowListener(l);
frame.pack();
frame.setLocation(counter * 20, counter * 10);
return frame;
}
Just a possible approach...
Create a class, call it WindowManager, that manages creation and disposal of windows.
It could for example retain the count of the windows currently open, and allow a dispose operation only if there are more than one windows "alive", otherwise show a confirm message with JOptionPane telling the user "Really close? That would terminate the application." or something like that.
The "tricky" part is that you have to do this kind of window-related operations throughout the WindowManager, otherwise everything would screw up.
Dunno if Swing has something like this built-in, I've never seen such a scenario.
simply check if the other window is open before closing with window.isVisible();