swing

Paving the on-ramp – Day 2 – SwingUtilities.showInFrame

There is recently an increased interest to ease the learning curve of Java for students on Day 1 of class. Brian Goetz (the architect of Java) made a proposal to eliminate some code. My proposition is a (independent) continuation to ease the learning of Java.

Day 1 (as proposed by Brian Goetz)

class HelloWord {
public static void main(String[] args) {
System.out.println("Hello World");
}
}

Simplifies to

void main() {
println("Hello World");
}

Day 2 (as proposed by me – Anthony Goubard)

import javax.swing.*;

void main() {
    SwingUtilities.invokeAndWait(() -> {
     	JFrame frame = new JFrame("Test");
       	frame.add(new JLabel("Hello World"));
       	frame.pack();
       	frame.setLocationRelativeTo(null);
       	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       	frame.setVisible(true);
    }
}

Simplifies to

import javax.swing.*;

void main() {
    SwingUtilities.showInFrame("Test", () -> new JLabel("Hello World"));
}

The on-ramp

  • No need to understand the concept of the Event Dispatch Thread (EDT) where UI operations should be done
  • No need to understand yet the concept of laying out components (pack())
  • No need to know the class JFrame and 5 of its methods
  • The focus is more on the intention of the program: Showing the text “Hello World” in a window with title “Test”

Other benefits (than the on-ramp)

  • Cleaner Stack overflow Swing examples.
  • Ease of use with JShell.
  • Event Dispatch Thread detection. Let’s face, it when it’s a small program, a lot of people forget to do the UI in the EDT.
  • Single Java execution java HelloWorld.java would require less code.
  • No language or compiler change needed.

Proposed implementation

    // public domain license - Should be in SwingUtilities
    /**
     * Show a window containing the provided component.
     * The window will exit the application on close.
     *
     * @param title the title of the window
     * @param panel the supplier for the component to show in the window
     * @return the created component
     */
    public static <T extends JComponent> T showInFrame(String title, Supplier<T> panel) {
        Function<T, JFrame> frameSupplier = createdPanel -> {
            JFrame frame = new JFrame(title);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(createdPanel);
            frame.setLocationRelativeTo(null);
            frame.pack();
            frame.setVisible(true);
            return frame;
        };
        boolean isInEDT = SwingUtilities.isEventDispatchThread();
        if (isInEDT) {
            T createdPanel = panel.get();
            frameSupplier.apply(createdPanel);
            return createdPanel;
        } else {
            List<T> result = new ArrayList<>();
            List<Exception> exception = new ArrayList<>();
            try {
                SwingUtilities.invokeAndWait(() -> {
                    try {
                        T createdPanel = panel.get();
                        frameSupplier.apply(createdPanel);
                        result.add(createdPanel);
                    } catch (Exception ex) {
                        exception.add(ex);
                    }
                });
            } catch (InterruptedException | InvocationTargetException ex) {
                throw new RuntimeException(ex);
            }
            if (!exception.isEmpty()) {
                Exception ex = exception.get(0);
                if (ex instanceof RuntimeException) throw (RuntimeException) ex;
                else throw new RuntimeException(ex);
            }
            return result.get(0);
        }
    }

Advanced

The created component is returned so you can use it further in your code. With the returned component, you also have access to the created JFrame by using SwingUtilities.windowForComponent method.

I’ve decided to wrap the exception in a RuntimeException is there is an error during the creation of the component. So you get a similar behavior whether you call this method from the Event Dispatch Thread or not.

Even though the example is with a JLabel, I would expect most of the usage will be passing a Suppier<JPanel> and probably like MyPanel::new as it’s quite common in Swing to have classes extending JPanel.

Another possibility would be to return void and use SwingUtilities.invokeLater() method. This way no need to do all the exception code and no need to have List<T> result. Comment in the reddit discussion if it’s your preference (Link below).

My history

Back when I was a student (1995), I remember it took me half a day to try to show a window on Windows 95 as I was using Borland C++. I copied the code word by word from a book as otherwise it wouldn’t work. A few days later while asking a teacher if we could have an interesting project, he told us “Sun Microsystem has just released a new language, it’s in alpha version but let’s do a project with it.”. I took the language, typed Frame frame = new Frame("Test"); frame.show(); and voilà! It was a whoa moment where I realized I would spend a lot of time with this language 😃.

Conclusion

Quite often, you see in conferences presentations about “what’s new in Java xx”, that we may forget that for some people everything is new. Paving the on-ramp doesn’t have to be a compiler or language change, it could be methods, documentation, videos, …

I think the next step would be to create a RFE ticket in the JDK bug system, but first I will wait for comments in the reddit discussion.

Hello World in Java, the difference between now and what it could be