Jens Schauder is software developer since 1997. He loves software development for the constant challenges and constantly changing environment. A great chance to learn and teach. He is also blogger, author of various articles and speaker at conferences. Jens is a DZone MVB and is not an employee of DZone and has posted 90 posts at DZone. You can read more from them at their website. View Full User Profile

Clean Code with Swing and Scala

07.26.2011
| 6899 views |
  • submit to reddit

I guess everybody who knows Java and Swing also knows the Swing Tutorial. It is a great source of information if you want to learn Swing. It is also a major catastrophy when it comes to structuring code. The problem is: Lots of people miss critical information contained in the tutorial like ‘Everything concerned with Swing must happen in the EDT’ but they seem to suck up the messy way to structure code like a sponge.

This might result in code like the one below. It is actually Scala code, but that shouldn’t matter much. The only piece that is a little special are the calls to Binder, which is a little Swing Binding Framework which I introduced a couple of weeks ago.

private def createPersonPanel(p : PersonEditor) = {

            val panel = new JPanel()
            val layout = new GridBagLayout()
            val c = new GridBagConstraints()
            c.gridx = 0
            c.gridy = 0
     
            panel.setLayout(layout)
     
            c.fill = 0
            panel.add(new JLabel("firstname"), c)
     
            c.fill = 1
            c.gridx += 1
            c.weightx = 1
            val firstnameTF = new JTextField()
            panel.add(firstnameTF, c)
            Binder.bind(p.firstname, firstnameTF)
     
            c.fill = 0
            c.weightx = 0
            c.gridx = 0
            c.gridy += 1
     
            panel.add(new JLabel("lastname"), c)
            c.fill = 1
     
            c.gridx += 1
            val lastnameTF = new JTextField()
            Binder.bind(p.lastname, lastnameTF)
            panel.add(lastnameTF, c)
            c.fill = 0
            c.weightx = 1
            c.gridy += 1
            c.anchor = GridBagConstraints.SOUTHEAST
            val button = new JButton("save")
            Binder.bind(p.save, button)
            panel.add(button, c)
            panel
        }

What is so bad about this piece of crap … ähm … code?

The method is long. 38 lines is about 10 times longer than healthy for a method.

There is tons of code duplication.

The method does lots of different things: creating components, adding them to a panel, configuring the layout.

There is a strong dependency on the order of commands. We can’t just move stuff up or down in the method and still hope the result will be something reasonable, even if we stick to rearrangements allowed by the compiler.

All this together makes the method extremely hard to understand. How long does it take to understand what kind of GUI results? Don’t bother to much, I help. It looks like this:

and if you resize it, it looks like this

Arguably the result looks just as ugly as the code, but once the code is clean we might be able to improve on the visual design as well.

If you don’t see it in the code, you might see it in the images: There are three different ways JComponents are handled by the method: JLabels are in the left column and don’t resize. The JTextFields are in the right column and do resize and the JButton doesn’t resize and is in the buttom right. In the code this is completely hidden in the manipulation of the GridBagConstraint. So lets make it explicit in the code:

private def addToLabelColumn(

            panel : JPanel,
            component : JComponent,
            row : Int) {
            val c = new GridBagConstraints()
            c.gridx = 0
            c.gridy = row
            c.weightx = 0
            c.fill = 0
            panel.add(component, c)
        }
     
        private def addToComponentColumn(
            panel : JPanel,
            component : JComponent,
            row : Int) {
            val c = new GridBagConstraints()
            c.gridx = 1
            c.gridy = row
            c.weightx = 1
            c.fill = 1
            panel.add(component, c)
        }
     
        private def addButton(
            panel : JPanel,
            component : JButton,
            row : Int) {
            val c = new GridBagConstraints()
     
            c.weightx = 1
            c.gridx = 1
            c.gridy = row
            c.fill = 0
            c.anchor = GridBagConstraints.SOUTHEAST
            panel.add(component, c)
        }
     
        private def createPersonPanel(p : PersonEditor) = {
            val panel = new JPanel()
            val layout = new GridBagLayout()
            panel.setLayout(layout)
     
            addToLabelColumn(panel, new JLabel("firstname"), 0)
     
            val firstnameTF = new JTextField()
            Binder.bind(p.firstname, firstnameTF)
            addToComponentColumn(panel, firstnameTF, 0)
     
            addToLabelColumn(panel, new JLabel("lastname"), 1)
     
            val lastnameTF = new JTextField()
            Binder.bind(p.lastname, lastnameTF)
            addToComponentColumn(panel, lastnameTF, 1)
     
            val button = new JButton("save")
            Binder.bind(p.save, button)
            addButton(panel, button, 2)
            panel
        }

I introduced three methods. One for adding a JLabel, one for adding a JComponent and one for adding JButtons. These handle the arrangement of components on a JPanel. The total length of the code increased because we create the GridBagConstraints insided the methods and have to set all properties and don’t rely anymore on the previous step to leave the constraint in a specific state.

If we now look at the createPersonPanel we’ll get a strong fealing of repetition in various places:

  • each call to add* methods takes the same JPanel as an argument. We can improve on this by creating a Builder wich crates the panel, contains the add* methods and can return the fully configured panel at the end.
  • for each property we create a JLabel, a JTextField, bind the property to the later and add both to the JPanel. We can fix this by encapsulating it in a seperate method.

The result might look like this:

  case class PanelBuilder() {
        val panel = new JPanel()
        val layout = new GridBagLayout()
        panel.setLayout(layout)
 
        def add(components : (JLabel, JComponent), row : Int) {
            addToLabelColumn(components._1, row)
            addToComponentColumn(components._2, row)
        }
 
        def add(
            component : JButton,
            row : Int) {
            val c = new GridBagConstraints()
 
            c.weightx = 1
            c.gridx = 1
            c.gridy = row
            c.fill = 0
            c.anchor = GridBagConstraints.SOUTHEAST
            panel.add(component, c)
        }
 
        private def addToLabelColumn(
            component : JComponent,
            row : Int) {
            val c = new GridBagConstraints()
            c.gridx = 0
            c.gridy = row
            c.weightx = 0
            c.fill = 0
            panel.add(component, c)
        }
 
        private def addToComponentColumn(
            component : JComponent,
            row : Int) {
            val c = new GridBagConstraints()
            c.gridx = 1
            c.gridy = row
            c.weightx = 1
            c.fill = 1
            panel.add(component, c)
        }
 
    }
 
    private def create(name : String, property : Property[String]) : (JLabel, JComponent) = {
        val textField = new JTextField()
        Binder.bind(property, textField)
        (new JLabel(name), textField)
    }
 
    private def create(name : String, action : => Unit) = {
        val button = new JButton("save")
        Binder.bind(action, button)
        button
    }
 
    private def createPersonPanel(p : PersonEditor) = {
        val builder = PanelBuilder()
 
        builder.add(create("firstname", p.firstname), 0)
        builder.add(create("lastname", p.lastname), 1)
        builder.add(create("save", p.save), 2)
 
        builder.panel
    }

The PanelBuilder has now two simple public add methods. In order to imitate the method signitures in Java we would have to create a couple of helper classes and interfaces. It would make the code less compact but this shouldn’t be a serious problem. Creation of the various components is just as the binding extracted in two create Methods. The createPersonPanel has now only 5 lines of code. Adding another property to the form should be trivial. Changing the extremely simplistic layout should be trivial and is at least limited to a single small class. I think this is pretty much OK for a first step toward clean swing code. So I leave it like it is right now. Although I do have further plans for this.

I hope most of you agree that the code is much easier to understand and maintain in the form it is right now. But is it really worth the effort? Some might say no. If the whole application would consist of only this little panel. I would agree. But a Swing application typically does not have a single panel with two textfields and a button. But tens or even hundreds of panels. Many consisting of large collections of components. If you have to maintain such a monster, would you prefer createPersonPanel methods like the last one, or would you prefer the first version? What if the customer if finally fed up with your crappy layout and insists on proper spacing between the labels and the JTextFields or other components?

All this is pretty nice when you start a new Swing application. But what if you have an existing Swing application? One with convoluted code just as the Swing Tutorial taught you? Well … start changing it now. Don’t sit down for two months and rewrite all your code, but find pieces of code duplication and extract them. It will be a long way, but you wont reach the end if you don’t start walking.

But what if you are not using Swing, but writing a web application? Well it really shouldn’t matter much. Your PanelBuilder might be written in JavaScript, or create HTML, but the principle is the same: Seperate creation of components, layout of components and binding of components to properties and actions.

 

From http://blog.schauderhaft.de/2011/06/26/clean-code-with-swing-and-scala/

Published at DZone with permission of Jens Schauder, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Yann Cébron replied on Wed, 2011/07/27 - 2:22am

I highly recommend using a decent Swing Forms library/builder like e.g. JGoodies Forms http://www.jgoodies.com/freeware/forms/index.html

 

Erik Post replied on Wed, 2011/07/27 - 9:11am in response to: Yann Cébron

Allow me to plug the utterly fantastic DesignGridLayout by Jean-Francois Poilpret. I've used it with a Swing/Scala app I built and it rocked my proverbial **** off.

Jochen Bedersdorfer replied on Wed, 2011/07/27 - 11:49am

Can't help it, but this looks like an exercise in code-refactoring.

I was hoping to see some Scala whizbang, but left unsatisfied.

I was building a helper class for GridBagLayout back in 1997, which was more general purpose and than this.

If someone can come up with a nice DSL for swing layouts in Scala, I might be interested.

 

Lund Wolfe replied on Sat, 2011/07/30 - 4:59pm

I like the power of GridBagLayout myself but this is a dubious refactoring, even for a trivial GUI. I think any simplification is inherently compromised unless the application GUI is boringly consistent. The standard snippet below does help a little:

static void addToGridBag( Container container, GridBagConstraints gridBagConstraints, Component component, int row, int column )
   {
      gridBagConstraints.gridy = row;
      gridBagConstraints.gridx = column;
      container.add( component, gridBagConstraints );
   }

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.