HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Swing GUI in Java

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
guiswingjava

Problem

I'm learning Java at the moment and I'm currently trying to make a GUI using Swing. I've done some reading and people usually prefer and advice to use composition instead of inheritance of e.g. JFrames.

I'm trying to code the GUI by hand instead of using the GUI builders, since they all generate a lot of code and I want to have a proper understanding of what's happening, and I can't seem to do that with the builders.

Questions: Is this a proper use of 'composition' over inheritance, in this case of using JFrames? I want the code behind the GUI to be as efficient as possible, before I begin on expanding the GUI.

Also: Swing is not thread-safe, so I read that it's good to use invokeLater. Am I using it correctly? Can I use it like that in the main, or should it go under the createGUI method?

package com.company;
import javax.swing.*;

public class Main {

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Main initiateProgram = new Main();
                initiateProgram.createGUI();
            }
        });
    }

    public void createGUI() {

        JFrame programGUI = new JFrame("GUI Program");

        programGUI.setSize(500, 500);
        programGUI.setLocationRelativeTo(null);
        programGUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        programGUI.setVisible(true);

        // And here on, an ActionListener for e.g. a button...
    }
}

Solution

Yes, that is proper use of composition over inheritance. Although some may say this is actually aggregation, due to the nature of Swing.

Composotion is the act of one object owning another object. When Main becomes garbage, so should the objects it's composed of.

Aggregation is a similar concept, except the composed objects do not become garbage. Composition is a "has-a" relationship, while aggregation is a "uses-a" relationship. Aggregation is another form of composition, with the difference being ownership (no ownership when using aggregation).

When you create a JFrame, the window is also referenced "behind the scenes", and can be accessed via Window.getWindows(). Even if Main becomes garbage, the window will still exist until you call dispose() on it.

It's recommended to avoid putting logic in your constructors. It helps lower the cost of actually creating the object, as well as makes testing easier. I recommend adding your components to a JPanel, then passing that to the constructor to be added to the frame via JFrame#setContentPane(Container).

The way you use invokeLater is fine in the sense that it forwards GUI code to the Event Dispatch Thread, which is what you always want to do. But hiding it in the constructor could lead to confusions. From outside of the class, we cannot be sure of whats inside the constructor. Someone may assume that the code is not already forwarded to the EDT, leading them to them wrapping the constructor call in an invokeLater aswell, resulting in posting 2 events to the EDT.

To remove the logic from your constructor, you should pass in the JFrame once you have prepared it:

public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
        JFrame frame = new JFrame();
        //add components

        new Test(frame);
    });
}


Your Test class would then look something like this:

class Test {
    private JFrame frame;

    public Test(JFrame frame) {
        this.frame = frame;
    }

    //...
}


This is referred to as Dependency Injection. The idea is that Test needs a frame. Without a frame, there shouldn't be a Test object. Create Test objects when you are certain that there is a frame for it to use.

The benifits of this include avoiding creating garbage objects (creating Test would be pointless if there was a problem creating the frame it needs), as well as allowing you to pass different frames, rather than having the frame hard-coded into it. The ability to use different frames, rather than be forced to use a single hard-coded frame, is referred to as Inversion of control, and increases reusability.

You shouldn't size your frame directly. You shouldn't size any component directly. Instead, use a layout manager to handle the size and location of your components.

Once you have chosen a layout manager, size your frame by using pack(), which sizes the frame based on the components inside of it.

Code Snippets

public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
        JFrame frame = new JFrame();
        //add components

        new Test(frame);
    });
}
class Test {
    private JFrame frame;

    public Test(JFrame frame) {
        this.frame = frame;
    }

    //...
}

Context

StackExchange Code Review Q#123480, answer score: 4

Revisions (0)

No revisions yet.