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.
Related
I understand that EventQueue.invokeLater() is a function called to make the Java Swing components Thread-Safe.
Also, I know that the argument to this function is an object with implements Runnable.
However, I am unable to understand the syntax for this function call, i.e. this call -
EventQueue.invokeLater(()-> {
new Screen();
});
Here, Screen() is a class that extends JFrame.
public class Screen extends JFrame
{
Screen()
{
setSize(1000, 1000);
JPanel j1 = new Board();
j1.setBounds(0,0,500, 500);
JPanel j2 = new DiceModel();
j2.setBounds(500, 0, 500, 500);
add(j1);
add(j2);
setLayout(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args)
{
EventQueue.invokeLater(()-> {
new Screen();
});
}
}
This code runs as expected.
Board and DiceModel are two classes I have defined that which extend JPanel.
The invocation
EventQueue.invokeLater( new Screen() );
gives the expected error that Screen is not an object of type Runnable.
So,my question is, what is the meaning of the syntax for the function call for invokeLater() ?
Is it a kind of anonymous function call in Java ?
The complete Swing processing is done in a thread called EDT (Event Dispatching Thread). Therefore you would block the GUI if you would compute some long lasting calculations within this thread.
The way to go here is to process your calculation within a different thread, so your GUI stays responsive. At the end you want to update your GUI, which have to be done within the EDT. Now EventQueue.invokeLater comes into play. It posts an event (your Runnable) at the end of Swings event list and is processed after all previous GUI events are processed.
Also the usage of EventQueue.invokeAndWait is possible here. The difference is, that your calculation thread blocks until your GUI is updated. So it is obvious that this must not be used from the EDT.
Still there is Java code out there that starts a JFrame simple from the main thread. This could cause issues, but is not prevented from Swing. Most modern IDEs now create something like this to start the GUI
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new NewJFrame().setVisible(true);
}
});
}
I am trying to develop a JFrame which has two buttons that would let me to call the main method of other classes. The first try was to put it directly into the actionPerformed of each button, this will cause the JFrame of the other class to open but showing only the title of it and not showing any contents of the JPanel additionally freezing the program (can't even press close button, have to go into task manager or eclipse to kill it). The second try was adding a method call in actionPerformed, and adding the method will this time call the main method of other class however the same result (freeze of program).
For testing purposes I have placed the call to main method of other class, straight in this class main method which has proven to me that the frame of other class has successfully appeared, including all its JPanel contents, functionality etc.
I know I could make some kind of infinite loop in my main method to wait until a boolean is set to true, but then I though there must be some less-expensive way to get it working. So here I am asking this question to you guys.
Here is the code of the 2nd try;
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 Chat {
public static void main (String[] args) {
JFrame window = new JFrame("Chat Selection");
//Set the default operation when user closes the window (frame)
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Set the size of the window
window.setSize(600, 400);
//Do not allow resizing of the window
window.setResizable(false);
//Set the position of the window to be in middle of the screen when program is started
window.setLocationRelativeTo(null);
//Call the setUpWindow method for setting up all the components needed in the window
window = setUpWindow(window);
//Set the window to be visible
window.setVisible(true);
}
private static JFrame setUpWindow(JFrame window) {
//Create an instance of the JPanel object
JPanel panel = new JPanel();
//Set the panel's layout manager to null
panel.setLayout(null);
//Set the bounds of the window
panel.setBounds(0, 0, 600, 400);
JButton client = new JButton("Run Client");
JButton server = new JButton("Run Server");
JLabel author = new JLabel("By xxx");
client.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//run client main
runClient();
}
});
server.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//run server main
}
});
panel.add(client);
client.setBounds(10,20,250,200);
panel.add(server);
server.setBounds(270,20,250,200);
panel.add(author);
author.setBounds(230, 350, 200, 25);
window.add(panel);
return window;
}
private static void runClient() {
String[] args1={"10"};
ClientMain.main(args1);
}
}
Only one main method is allowed per application. Honestly I am not sure what you are trying to do or think is supposed to happen when you call main on other classes. When you call main on other classes all you are doing is calling a method that happens to be called main and passing args to it. Your freezing is probably because you are not using Swing correctly:
http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
The problem you're having is that Java Swing is single threaded. When you're running the main function of the other class, however you do it, the GUI won't be able to keep running until it returns. Try spawning off a new thread that calls the second main method.
private static void runClient() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
String[] args1={"10"};
ClientMain.main(args1);
}
});
}
EDIT: Updated, as per #Radiodef's suggestion. Missed at the top when you said this second class had to display things on the GUI. Definitely want to go with the invokeLater then.
How could I update content of several visible (at one time) components separately(independently) ?
For example, I would like to show some kind of progress indicator with connected information, and it should only be updated/painted without painting all other components on form?
Or if I have more then one components in progress and must update only their content.
You can (and will have to, here) schedule your updates. You SHOULD NOT be running the long running calculation in the GUI thread (which appears unlikely, if you have a progress bar). But you still need to let the GUI know it needs to update... Something like so:
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
// I didn't seem to see anything like this with a quick look-through.
// anybody else know differently?
public class ComponentUpdater implements ActionListener {
private static List<Component> componenets = new ArrayList<Component>();
public void addComponent(Component component) {
componenets.add(component);
}
#Override
public void actionPerformed(ActionEvent arg0) {
for(Component component : componenets) {
component.repaint();
}
}
}
And to use it, you need a timer:
UpdatingComponent componentToUpdate = new UpdatingComponent(dataSourceToExamine);
panel.add(componentToUpdate);
ComponentUpdater updater = new ComponentUpdater();
updater.addComponent(componentToUpdate);
Timer schedule = new Timer(500, updater);
timer.setRepeats(true);
timer.start();
This will cause every component added to the updater to have repaint() caller ever 500 milliseconds, forever.
There are of course much more elegant ways to do this (like being able to specify update location), but this is a simple one to get you started.
Whenever you call the repaint function (or one of your methods such as setText calls it for you) the component will repaint itself and all other components inside itself. In order to just repaint one thing, just call the repaint() method of that particular component. This will save memory and be much more predictable.
So in an example with a JProgressBar
JFrame frame = new JFrame("Title");
JPanel panel = new JPanel();
JProgressBar pBar = new JProgressBar(SwingConstants.HORIZONTAL, 0, 100);
panel.add(pBar);
frame.add(panel);
pBar.repaint(); // Will only repaint the progress bar
You can also repaint only a specific section of your program. So assuming the progress bar is located at (100, 100) and is 100 wide and 20 tall:
frame.repaint(new Rectangle(100, 100, 100, 20));
I'm trying to make a simple 2D game in Java.
So far I have a JFrame, with a menubar, and a class which extends JPanel and overrides it's paint method. Now, I need to get a game loop going, where I will update the position of images and so on. However, I'm stuck at how best to achieve this. Should I use multi-threading, because surely, if you put an infinite loop on the main thread, the UI (and thus my menu bar) will freeze up?
Here's my code so far:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class GameCanvas extends JPanel {
public void paint(Graphics g) {
while (true) {
g.setColor(Color.DARK_GRAY);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
#SuppressWarnings("serial")
public class Main extends JFrame {
GameCanvas canvas = new GameCanvas();
final int FRAME_HEIGHT = 400;
final int FRAME_WIDTH = 400;
public static void main(String args[]) {
new Main();
}
public Main() {
super("Game");
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem startMenuItem = new JMenuItem("Pause");
menuBar.add(fileMenu);
fileMenu.add(startMenuItem);
super.add(canvas);
super.setVisible(true);
super.setSize(FRAME_WIDTH, FRAME_WIDTH);
super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
super.setJMenuBar(menuBar);
}
}
Any pointers or tips? Where should I put my loop? In my main class, or my GameCanvas class?
Your game loop (model) should not be anywhere near any of your GUI classes (view). It uses your GUI classes--but even that you probably want to do through an intermediary (controller). A good way to ensure that you are doing it right is to check that your model doesn't have a single "include javax.swing.???".
The best thing you could do is to keep the game loop in it's own thread. Whenever you want to make a change in the GUI, use SwingWorker or whatever the young kids use now to force it onto the GUI thread for just that one operation.
This is actually awesome because it makes you think in terms of GUI Operations (which would constitute your controller). For instance, you might have a class called "Move" that would have the GUI logic behind a move. Your game loop might instantiate a "Move" with the right values (item to move, final location) and pass it to a GUI loop for processing.
Once you get to that point, you realize that simply adding a trivial "undo" for each GUI operation allows you to easily undo any number of operations. You will also find it easier to replace your Swing GUI with a web GUI...
You need one thread for you game loop and one thread to handle Swing UI events like mouse clicks and keypresses.
When you use the Swing API, you automatically get an additional thread for your UI called the event dispatch thread. Your callbacks are executed on this thread, not the main thread.
Your main thread is fine for your game loop if you want the game to start automatically when the programs runs. If you want to start and stop the game with a Swing GUI, then have then main thread start a GUI, then the GUI can create a new thread for the game loop when the user wants to start the game.
No, your menu bar will not freeze up if you put your game loop in the main thread. Your menu bar will freeze up if your Swing callbacks take a long time to finish.
Data that is shared between the threads will need to be protected with locks.
I suggest you factor your Swing code into its own class and only put your game loop inside your main class. If you're using the main thread for your game loop, this is a rough idea of how you could design it.
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
class GUI extends JFrame {
GameCanvas canvas = new GameCanvas();
final int FRAME_HEIGHT = 400;
final int FRAME_WIDTH = 400;
public GUI() {
// build and display your GUI
super("Game");
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem startMenuItem = new JMenuItem("Pause");
menuBar.add(fileMenu);
fileMenu.add(startMenuItem);
super.add(canvas);
super.setVisible(true);
super.setSize(FRAME_WIDTH, FRAME_WIDTH);
super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
super.setJMenuBar(menuBar);
}
}
public class Main {
public static void main(String args[]) {
GUI ui = new GUI(); // create and display GUI
gameLoop(); // start the game loop
}
static void gameLoop() {
// game loop
}
}
Java is really suited for event-driven programming. Basically, set up a timer event and listen. At each 'tick-tock' you update your game logic. At each GUI-event you update your data structures that the game logic method will read.
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!