0

To speed up application startup I moved some of the GUI generating code to worker thread. That thread creates all the GUI instances and then uses SwingUtilities.invokeLater to append them to the already running application GUI.

I extended ArrayList to create a helper class that remembers what should be appended to what.

I use it like this:

// This code runs in some other thread, not Swing thread
GUIAppendList list = new GUIAppendList();
JToggleButton button = new JToggleButton();
// The first variable represents already appended and rendered
// GUI element - for example menu
list.add(menuBarGlobalVariable, button);
// This will create all elements in Swing GUI thread
list.create();

The GUIAppendList creates the items like this:

  synchronized public void create() {
    if(done)
      return;
    done = true;
    SwingUtilities.invokeLater(new Runner(this));
  }

  protected static class Runner implements Runnable {
    public final GUIAppendList list;
    public Runner(GUIAppendList list) {
      this.list = list;
    }
    @Override
    public void run() {
      for(AddChild c : list) {
        c.join();
      }
      if(!list.after.isEmpty()) {
        for(Runnable c : list.after) {
          c.run();
        }
      }
    }
  }

But since I did that, my application started randomly (not always and never in debug mode so far) freezing. Is it possible that creating GUI elements causes that even if they are not connected to the main Window?

Here's the full appender class:

import java.awt.Component;
import java.awt.Container;
import java.util.ArrayList;
import javax.swing.SwingUtilities;

/**
 * This class allows you to prepare a list of swing elements that should be
 * joined together. After you create the list, use .create() method which will
 * then add all the items within the Swing main thread. This is done using
 * invokeLater.
 * @author Jakub
 */
public class GUIAppendList extends ArrayList<GUIAppendList.AddChild> {
  /**
   * Represents two nodes, container and component.
   */
  public static class AddChild {
    public AddChild(Container parent, Component child) {
      this.parent = parent;
      this.child = child;
    }
    final Container parent;
    final Component child;

    // Joins the two elements together
    // THIS MUST BE CALLED IN THE SWING THREAD!!!
    void join() {
      parent.add(child); 
    };
  }

  // Prevents from calling the add in the list multiple times
  private boolean done = false;
  // List of other actions to be executed after creating the GUI
  protected ArrayList<Runnable> after = new ArrayList();

  synchronized public void add(Container parent, Component child) {
    this.add(new AddChild(parent, child));
  }
  synchronized public void after(Runnable action) {
    after.add(action);
  }
  /**
   * Will ask Swing to run our runnable object which will append all the items.
   */
  synchronized public void create() {
    if(done)
      return;
    done = true;
    SwingUtilities.invokeLater(new Runner(this));
  }

  protected static class Runner implements Runnable {
    public final GUIAppendList list;
    public Runner(GUIAppendList list) {
      this.list = list;
    }
    @Override
    public void run() {
      // Append all nodes to each other
      for(AddChild c : list) {
        c.join();
      }
      // Run after actions
      if(!list.after.isEmpty()) {
        for(Runnable c : list.after) {
          c.run();
        }
      }
    }
  }
}
Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • 1
    As a general rule of thumb, no, you should never create ui components outside of the EDT, you have no urge tee when a method might trigger an event or when it might respond to them, it would be better to just to load the data in a back ground thread and simply update the ui (from the within the EDT) when you can – MadProgrammer Jan 05 '16 at 02:04
  • 1
    @MadProgrammer is correct, for [example](http://stackoverflow.com/a/25526869/230513). – trashgod Jan 05 '16 at 04:28

2 Answers2

4

No it is not correct, all Swing components must be created/modified/added/removed on the EDT.

Rhangaun
  • 1,430
  • 2
  • 15
  • 34
2

if you have ever looked at the internals of the swing/awt stuff, you would see that there is a lot going on under the hood (i.e. interacting with native ui widgets). i would not use any swing/awt class outside of the EDT unless it specifically states that this is allowed.

I can't imagine that just setting up ui widgets would have such a dramatic affect on your application startup. if it's the data behind the widgets, then i would suggest creating the widgets without data and disabling them. load the data in the background and then update the ui on the main thread.

jtahlborn
  • 52,909
  • 5
  • 76
  • 118
  • Despite my best effort, I was unable to do that. I was convinced that the widgets are just some classes and unless they are connected to main thread, they do nothing. Other frameworks work like this. – Tomáš Zato Jan 05 '16 at 01:29
  • 2
    @TomášZato Other frameworks throw exceptions when you violate the single thread rules, Swing does not. Have created some complex uis, I'd find it hard to believe that just created the ui alone would take that much time – MadProgrammer Jan 05 '16 at 02:05
  • @MadProgrammer That part of GUI depends on too many different things that make it rather difficult to populate without instantiating those things first. If I had some versatile event system at hand, things would probably get a little easier as I could notify separated parts without risk that they will be null. – Tomáš Zato Jan 05 '16 at 02:10
  • SwingWorker? Has publish/process functionality and PropertyChamge support – MadProgrammer Jan 05 '16 at 02:11