7
\$\begingroup\$

Oftentimes, I write small little dialogs or forms without a UI builder. I usually find myself having a lot of "container" panel variables that just float around. I found this leads to time-wasting experiences.

Where did I put that BorderLayout panel?

Oh right, here it is.

Wait, never mind. That one has a GridLayout.

A while ago, I had the idea to nest widgets in anonymous classes, directly in add calls. This way, a simple layout has fewer variables that get in the way of digesting the code's logic since every component is nicely indented in its own scope.

Pros

  • Component hierarchy is obvious, as are component attributes like colors and fonts
  • Code is cleaner, with less dummy container variables
  • Forces component logic to be encapsulated directly in the component (i.e. not 50 lines up or below the component initialization: saves time)
  • I found it easier to come back to code written in this style after a couple months and still understand the layout.

Cons

  • Might bloat generated code size, because all the Swing constants would have to be written to each classfile

Below is a simple (Java 8) frame that shows a button and a label. I realise that this same layout could have been made with fewer panels, but I'm presenting it as an overly explicit example of my approach.

import javax.swing.*;
import java.awt.*;

public class TestFrame extends JFrame {
    public TestFrame() {
        super("A Frame");
        add(new JPanel() {
            {
                setLayout(new BorderLayout());
                setBackground(Color.LIGHT_GRAY);
                add(new JPanel() {
                    {
                        setLayout(new BorderLayout());
                        setOpaque(false);
                        add(new JLabel("A label", JLabel.LEFT) {
                            {
                                setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
                                setFont(getFont().deriveFont(Font.BOLD));
                            }
                        }, BorderLayout.EAST);
                        add(new JButton("Click me!") {
                            int timesClicked = 0;

                            {
                                addActionListener((x) -> {
                                    timesClicked++;
                                    setText(String.format("You've clicked me %d time(s)!", timesClicked));
                                });
                            }
                        }, BorderLayout.CENTER);
                    }
                });
            }
        });
        setSize(new Dimension(640, 480));
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] argv) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ReflectiveOperationException | UnsupportedLookAndFeelException ignored) {
        }
        SwingUtilities.invokeLater(() -> {
            TestFrame test = new TestFrame();
            test.setVisible(true);
        });
    }
}

I (and I'm not by any means an expert on Swing) haven't seen Swing code like this around on StackExchange sites, or really, anywhere I look. I can't have been the only one to think of this approach - is there a reason why an aggressive subclassing approach is not preferred, or have I just not been searching hard enough?

I think the readability benefits far outweigh any costs this approach has.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ I too faced the same problem that you faced. I never did think of this approach. It looks very XAML/HTML inspired. (i'm a C# guy now, but used to a be a big java guy) but I like this approach.. Seems easy enough. I think the only thing I would change is for much larger panels that use a common look to a component is to make a custom component and reuse that to save on the background color/size things. \$\endgroup\$ – Robert Snyder Apr 27 '15 at 2:44
  • \$\begingroup\$ I think I was very much influenced by HTML layouts, having just come back to Java from a long Django experience. Indeed, I generally put commonly-styled panels in their own, separate classes which I subclass. \$\endgroup\$ – Xyene Apr 27 '15 at 2:50
  • \$\begingroup\$ When working this way, I tend to either using inner classes or purpose build, configurable classes. Each one builds a small portion of the UI which is focused performing the required functionality/logic. It easier to plug and play functionality as required and reuse common functionality...as well as making the code easier to read \$\endgroup\$ – MadProgrammer Apr 27 '15 at 10:30
4
\$\begingroup\$

Anonymous constructors are very confusing for many people. They don't correspond well to people's expectations, and are not common knowledge.

I would recommend against them on principle.

Having said that, I can see how you think this has helped reduce your code boilerplate, but, you need to be consistent about it then. Currently your class is defined as:

public class TestFrame extends JFrame {
    public TestFrame() {
        super("A Frame");
        add(new JPanel() {
            {

when really, that should be:

public class TestFrame extends JFrame {
    {
        setName("A Frame");
        add(new JPanel() {
            {

If you are going to be unconventional, then you at least need to be consistently unconventional.

timesClicked should probably be an AtomicInteger so you can deal with some concurrency in the clicked event - or, rather, so you can read it from a different thread later. Lambdas with side-effects should be avoided.

Having said all of that, you really should use a style that is more conventional just so others can read your code better. The indentation levels on their own are a distraction with the addition of the anonymous blocks.

Oh, there's one other item that needs to be said, with all the anonymous classes there, your stack traces for any exceptions are going to be much harder to read without the code in hand too. You lose all the benefits of named classes. This will hamper your debug time.

\$\endgroup\$
2
  • 2
    \$\begingroup\$ +1 "If you are going to be unconventional, then you at least need to be consistently unconventional." \$\endgroup\$ – quantum Apr 27 '15 at 2:57
  • \$\begingroup\$ I think the debugger is a minor problem. The OP said he did a bunch of HTML and Django. The few mark-up languages i've programmed for there is next to no debugging support especially with stack traces. As long as a person separates UI and BL there should be little to no problem with this. \$\endgroup\$ – Robert Snyder Apr 27 '15 at 12:28
2
\$\begingroup\$

I can feel your pain and sort of like your approach. However, it has the disadvantages already mentioned by rolfl. Consider using miglayout, which allows to create complex layouts without nesting (nesting is sometimes fine as it helps to structure the GUI, but usually you get too much of it).

Add some methods like

JLabel newLabel(String name) {
   ... set name
   ... align left
   ... add border
}

This can get complicated, but you usually want all labels to look the same. Otherwise, you can use additional arguments.

Using these methods you can simply call

mainPanel.add(newLabel("Surname"), "gap unrelated");

without making any classes or variables (look at Example 01 at the page linked to see what "gap unrelated" does).

It's a pity that Swing provides no fluent setters as it'd allow to eliminate quite a few variables even without helper methods.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.