Why to Use Swingutilities.Invokelater in Main Method

Why to use SwingUtilities.invokeLater in main method?

The docs explain why. From Initial Threads

Why does not the initial thread simply create the GUI itself? Because almost all code that creates or interacts with Swing components must run on the event dispatch thread.

and from The Event Dispatch Thread

Some Swing component methods are labelled "thread safe" in the API specification; these can be safely invoked from any thread. All other Swing component methods must be invoked from the event dispatch thread. Programs that ignore this rule may function correctly most of the time, but are subject to unpredictable errors that are difficult to reproduce.

Using SwingUtilities.invokeLater() in main method

The purpose of the code fragment that you show is to create the Swing UI and the model and to connect them together.

There is no Swing update (in terms of reacting to user input) since there cannot be any user input before the run() methods ends.

While you could split these tasks between main thread and EDT (and possibly gain a few milliseconds until the UI is first shown) it would also complicate the design of the application (multithreading is no easy topic) and litter the code base with invokeLater() calls. I would not do it until someone proves it to be necessary.


IMHO the EDT is the main thread in any GUI application. Every reaction to user input starts in this thread and every update of the UI must be done in this thread.

Long running tasks should be done in a background thread - which usually means anything that takes more than a few milliseconds.

What if creating the model takes several seconds?

In that case I would try to split the model creation into two parts:

  • create the minimal part that is needed so that the UI can be shown. This should be done in the EDT (because the user has to wait for the completion of this part anyway - before the UI is shown he cannot interact with it)
  • do the remaining, long running parts in a background thread.

What if this cannot be done? (i.e. the UI cannot be displayed until the model is fully initialized)

In this case the user has to wait for the complete initialization of the model before he can see and use the UI anyway. So it doesn't matter whether this initialization runs on the EDT or the main thread. So use the simpler solution: everything on the EDT.

But give the user some hint that your application is starting by showing a splash screen

SwingUtilities.invokeLater() why is it needed?

Swing objects are not thread safe. SwingUtilities.invokeLater() allows a task to be executed at some later point in time, as the name suggests; but more importantly, the task will be executed on the AWT event dispatch thread. When using invokeLater, the task is executed asynchronously; there's also invokeAndWait, which won't return until the task has finished executing.

Some information about the decision not to make Swing thread-safe can be found here: Multithreaded toolkits: A failed dream? [Archived]

What exactly does the call to the SwingUtilities.invokeLater() method into the main class that perform a Swing application?

This is really two questions rolled into one.

  1. SwingUtilities.invokeLater() makes sure that whatever you want to do happens on the Event Dispatcher Thread. Since Swing is strictly single threaded, this is required for everything that interacts with Swing and doesn't already run in the EDT (e.g. is called as a result of an event handler) The main() method of your application is one such example, as it's invoked by the VM on startup in its own separate thread (called, unsurprisingly, the Main Thread).
  2. This is an anonymous inner class. It's a shortcut for creating a class that implements Runnable and creating an instance of that class straight away. (Note that it isn't completely equivalent with doing that, there are small but important details in which it is different.) SwingUtilities.invokeLater() will then put this Runnable object in the Swing event queue, which will be processed by the EDT (usually a couple of moments) later, calling the run() method you provided.

What does SwingUtilities.invokeLater do?

As other answers have said, it executes your Runnable on the AWT event-dispatching thread. But why would you want to do that? Because the Swing data structures aren't thread-safe, so to provide programmers with an easily-achievable way of preventing concurrent access to them, the Swing designers laid down the rule that all code that accesses them must run on the same thread. That happens automatically for event-handling and display maintenance code, but if you've initiated a long-running action - on a new thread, of course - how can you signal its progress or completion? You have to modify a Swing control, and you have to do it from the event-dispatching thread. Hence invokeLater.

Why does invokeLater execute in the main thread?

To me it seems like a misunderstanding on your side

The first line is like saying: "Ok, Swing, what I want you to invokeLater is someMethod().toString()". So Swing executes it

The second line is like saying: "Ok, Swing, what I want you to invokeLater is the method toString() of the object returned by the method someMethod()". A someMethod() method that I am executing right now

So the result is completely logical to me

Just keep in mind that before evaluating a function (in this case invokeLater) Java needs to evaluate all arguments. So in the first case Java evaluate a lambda function (no need to execute it) and in the second case it encounters a method invocation so it needs to execute it

What to put (and not to put) inside SwingUtilities.invokeLater?

For future comers, I figured it out, even though the docs don't really explain this in a way that is obvious to Swing newbies:

  • When the docs or blogs/articles/people say "Anything that touches the UI", this translates to: "Any manipulation of a Swing class under javax.swing.*". Hence, doing anything with JFrame, JPanel, or any of the JComponents. All this stuff should be executed inside the Runnable you pass to SwingUtilities.invokeLater from inside your app's main method. So basically you end up with one gigantic, monolithic Runnable that does all UI code manipulation. Strange and anti-MVC IMHO.
  • If you have a long running process you should strip all the Swing code out of that process and pass it into your own SwingWorker

SwingUtilities.invokeLater

Do I have to use each time I need to update the GUI components?

No, not if you're already on the event dispatch thread (EDT) which is always the case when responding to user initiated events such as clicks and selections. (The actionPerformed methods etc, are always called by the EDT.)

If you're not on the EDT however and want to do GUI updates (if you want to update the GUI from some timer thread, or from some network thread etc), you'll have to schedule the update to be performed by the EDT. That's what this method is for.

Swing is basically thread unsafe. I.e., all interaction with that API needs to be performed on a single thread (the EDT). If you need to do GUI updates from another thread (timer thread, networking thread, ...) you need to use methods such as the one you mentioned (SwingUtilities.invokeLater, SwingUtilities.invokeAndWait, ...).

Animations and SwingUtilities.invokeLater

Here is that self-contained code (good call on posting that, BTW) updated to use a Timer as suggested by @MadProgrammer. In order to access the x variable, it was moved into the action listener defined for the timer. In order to access the Timer from within the action listener, it was moved to being a class attribute. The latter meant it was easier to move the bulk of the code into a constructor for an instance of the object.

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.*;

public class Test {

Timer timer;

Test() {
int width = 854;
int height = 480;
String title = "Test";
BufferedImage bufferedImage = new BufferedImage(
width, height, BufferedImage.TYPE_INT_RGB);
JFrame frame = new JFrame();
JPanel panel = new JPanel() {
@Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics2D graphics2D = (Graphics2D) graphics;
// when you have an ImageObserver, may as well use it
//graphics2D.drawImage(bufferedImage, 0, 0, null);
graphics2D.drawImage(bufferedImage, 0, 0, this);
}

@Override
public Dimension getPreferredSize() {
return new Dimension(width,height);
}
};
frame.add(panel);
frame.pack();
frame.setTitle(title);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
int size = height / 3;

ActionListener animationListener = new ActionListener() {

int x = -size;

@Override
public void actionPerformed(ActionEvent e) {
if (x <= width) {
Graphics2D graphics2D = (Graphics2D) bufferedImage.getGraphics();
graphics2D.setColor(Color.RED);
graphics2D.fill(new Rectangle2D.Double(x, 0, size, size));
graphics2D.setColor(Color.GREEN);
graphics2D.fill(new Rectangle2D.Double(x, size, size, size));
graphics2D.setColor(Color.BLUE);
graphics2D.fill(new Rectangle2D.Double(x, 2 * size, size, size));
graphics2D.dispose();
panel.repaint();
++x;
} else {
timer.stop();
frame.dispose();
}
}
};
timer = new Timer(10, animationListener);
timer.start();
}

public static void main(String[] args) {
Runnable r = () -> {
new Test();
};
SwingUtilities.invokeLater(r);
}
}


Related Topics



Leave a reply



Submit