1

I'm designing a GUI with 20 or so components: 10 labels, 4 text fields, 4 buttons, and 2 text areas. Using GridBagLayout seemed a great idea. But with all the instance variables required to do it by the book for each component (i.e., not reuse), a general method for adding components seemed a must. I really thought this could work:

(Note: HORIZ is abbreviation for GridBagConstraints.HORIZONTAL; CENTER is abbreviation for GridBagConstraints.CENTER.)

public static void addComponent(Container f,      Component c, 
                                  int     x,        int   y, 
                                  int     w,        int   h, 
                                  int     ipadx,    int   ipady, 
                                  float   wtx,      float wty,
                                  int fill, int anchor, Insets insets){


  GridBagConstraints gbc = new GridBagConstraints();

  gbc.gridx = x;    gbc.gridy = y;      
  gbc.gridwidth = w;    gbc.gridheight = h;     
  gbc.fill = fill; 
  gbc.ipadx = ipadx;    gbc.ipady = ipady;  
  gbc.insets = insets;  gbc.anchor = anchor; 
  gbc.weightx = wtx;    gbc.weighty = wty;

  f.add(c,gbc);
}

I called it like so:

    Insets insets = new Insets(0,0,0,0);
    JFrame frame = new JFrame();
    label = new JLabel("Blablablah");   
    addComponent(frame, label, 0,0, 1,1, 0,0, 0.5f,0, HORIZ, CENTER, insets);

But I got message "cannot add to layout: constraint must be a string (or null)" at f.add(c.gbc).

I think I understand the error: frame doesn't have GridBagConstraints prior to the call to addComponent and gbc in the first line of the method doesn't belong to parameter f (or anything else?).

So I modified the method signature slightly, omitting Container:

public static void addComponent(                  Component c, 
                                  int     x,        int   y, 
... (rest unchanged)

And I modified the problem line like so:

frame.add(c, gbc);

So I'm using a global variable, frame, when I'd rather pass it as an argument.

Two questions:

(1) Is there a way to minimally modify my code to enable passing frame to addComponent?

(2) Is there any reason to want to do so? I guess this amounts to asking, what would YOU do?


P.S. Here's calls to the modified addComponent, hastily thrown together to get some semblance of the first few lines of what I want. The spacing reeks at the moment--I need to monkey with insets, ipads, fills--but it's actually usable. (New name for frame is GUI.)

private static void createAndShowGUI() {
  GUI = new JFrame();
  GUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  gbl = new GridBagLayout();
  GUI.setLayout(gbl);

  addComponent(lblRootNode, 0,0, 1,1, 0,0, 0.5f,0, HORIZONTAL, CENTER, new Insets(0,0,0,0));    
  addComponent(txtRootNode, 1,0, 5,1, 60,0, 0.5f,0, HORIZONTAL, CENTER, new Insets(0,0,0,0));    
  addComponent(btnBrowse,   6,0, 1,1, 0,0, 0.5f,0, HORIZONTAL, CENTER, new Insets(0,0,0,0));    
  addComponent(lblFilenamePat, 0,1, 2,1, 0,0, 0.5f,0, HORIZONTAL, EAST, new Insets(0,0,0,0));    
  addComponent(txtFilenamePat, 2,1, 4,1, 0,0, 0.5f,0, HORIZONTAL, LINE_END, new Insets(0,0,0,0));    
  addComponent(lblDates, 0,2, 2,1, 0,0, 0.5f,0, HORIZONTAL, CENTER, new Insets(0,0,0,0));    
  addComponent(lblSizes, 2,2, 2,1,   0,0, 0.5f,0, HORIZONTAL, CENTER, new Insets(0,0,0,0));    

...

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
DSlomer64
  • 4,234
  • 4
  • 53
  • 88
  • 1
    For better help sooner, post an [SSCCE](http://sscce.org/). Note the document is being reviewed and discussed on [this question](http://meta.stackexchange.com/q/214955/155831), contributions welcome. – Andrew Thompson Jan 09 '14 at 04:42
  • 1
    Several related examples are examined [here](http://stackoverflow.com/q/14755487/230513). – trashgod Jan 09 '14 at 04:53

4 Answers4

4

I use GridBagLyout quite a lot, but like many others before me, I quickly found out it can be quite verbose. There are many examples on the web of how users wrote utility methods and/or classes to help them generate GBL code. I'll show you what I do.

1) First, I created 2 enums that are wrappers for the anchor and fill GridBagConstraints fields. I prefer the type checking of enums vs. ints, and it also allows me to write more concise code (as you'll see later). And yes, I still use the older "directional" values for Anchor. I could never quite get used to the preferred values of PAGE_START and the like. Use whatever you prefer.

Anchor.java:

package gbl;

import java.awt.*;

/**
 * Convenience enum that aliases out all possible values for the
 * GridBagConstraints anchor property.
 */
public enum Anchor
{
  NORTH(GridBagConstraints.NORTH),
  SOUTH(GridBagConstraints.SOUTH),
  EAST(GridBagConstraints.EAST),
  WEST(GridBagConstraints.WEST),
  NORTHEAST(GridBagConstraints.NORTHEAST),
  NORTHWEST(GridBagConstraints.NORTHWEST),
  SOUTHEAST(GridBagConstraints.SOUTHEAST),
  SOUTHWEST(GridBagConstraints.SOUTHWEST),
  CENTER(GridBagConstraints.CENTER);

  private int constraint;

  private Anchor(int gbConstraint)
  {
    constraint = gbConstraint;
  }

  public int getConstraint()
  {
    return constraint;
  }
}

Fill.java:

package gbl;

import java.awt.*;

/**
 * Convenience enum that aliases out all possible values for the
 * GridBagConstraints fill property.
 */
public enum Fill
{
  NONE(GridBagConstraints.NONE),
  HORIZONTAL(GridBagConstraints.HORIZONTAL),
  VERTICAL(GridBagConstraints.VERTICAL),
  BOTH(GridBagConstraints.BOTH);

  private int constraint;

  private Fill(int gbConstraint)
  {
    constraint = gbConstraint;
  }

  public int getConstraint()
  {
    return constraint;
  }
}

2) Then, I created a subclass of GridBagConstraints that allowed me "chain" the properties only as I need them, while utilizing common defaults:

GBConstraints.java:

package gbl;

import java.awt.*;

/**
 * Convenience class to simplify the creation of a GridBagConstraints object.
 */
public class GBConstraints extends GridBagConstraints
{
  public GBConstraints(int gridX, int gridY)
  {
    super();

    this.gridx = gridX;
    this.gridy = gridY;

    this.gridwidth = 1;
    this.gridheight = 1;
    this.weightx = 0;
    this.weighty = 0;
    this.anchor = NORTHWEST;              // old default was CENTER
    this.fill = NONE;
    this.insets = new Insets(1,2,1,2);    // old default was (0,0,0,0)
    this.ipadx = 1;                       // old default was 0
    this.ipady = 1;                       // old default was 0
  }

  public GBConstraints anchor(Anchor anchor)
  {
    this.anchor = anchor.getConstraint();
    return this;
  }

  public GBConstraints fill(Fill fill)
  {
    this.fill = fill.getConstraint();

    /*
     * As a convenience, set the weights appropriately since these values are
     * almost always used in tandem with the given Fill. The caller can always
     * call the weight(...) method later if these defaults aren't desired. 
     */
    switch (fill)
    {
      case HORIZONTAL:
        this.weightx = 1;
        this.weighty = 0;
        break;
      case VERTICAL:
        this.weightx = 0;
        this.weighty = 1;
        break;
      case BOTH:
        this.weightx = 1;
        this.weighty = 1;
        break;
      default:
        this.weightx = 0;
        this.weighty = 0;
        break;
    }

    return this;
  }

  public GBConstraints insets(int length)
  {
    return insets(length, length, length, length);
  }

  public GBConstraints insets(int top, int left, int bottom, int right)
  {
    this.insets = new Insets(top, left, bottom, right);
    return this;
  }

  public GBConstraints ipad(int ipadX, int ipadY)
  {
    this.ipadx = ipadX;
    this.ipady = ipadY;
    return this;
  }

  public GBConstraints span(int gridWidth, int gridHeight)
  {
    this.gridwidth = gridWidth;
    this.gridheight = gridHeight;
    return this;
  }

  public GBConstraints spanX(int gridWidth)
  {
    this.gridwidth = gridWidth;
    return this;
  }

  public GBConstraints spanY(int gridHeight)
  {
    this.gridheight = gridHeight;
    return this;
  }

  public GBConstraints weight(double weightX, double weightY)
  {
    this.weightx = weightX;
    this.weighty = weightY;
    return this;
  }

  public GBConstraints weightX(double weightX)
  {
    this.weightx = weightX;
    return this;
  }

  public GBConstraints weightY(double weightY)
  {
    this.weighty = weightY;
    return this;
  }
}

3) Putting it all together, here's a demo that shows how to use the above classes. This greatly simplifies using GridBagLayout, IMHO. Side note: I normally stay away from static imports, but I like it in this situation.

Demo:

package gbl;

import static gbl.Anchor.*;
import static gbl.Fill.*;

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

public class GridBagDemo implements Runnable
{
  public static void main(String[] args)
  {
    SwingUtilities.invokeLater(new GridBagDemo());
  }

  public void run()
  {
    JLabel lblFirst  = new JLabel("First Name");
    JLabel lblLast   = new JLabel("Last Name");
    JLabel lblStreet = new JLabel("Street");
    JLabel lblCity   = new JLabel("City");
    JLabel lblState  = new JLabel("State");
    JLabel lblZip    = new JLabel("ZIP");
    JLabel lblNotes  = new JLabel("Notes");

    JTextField txfFirst  = new JTextField(15);
    JTextField txfLast   = new JTextField(20);
    JTextField txfStreet = new JTextField(40);
    JTextField txfCity   = new JTextField(15);
    JTextField txfState  = new JTextField(5);
    JTextField txfZip    = new JTextField(10);

    JTextArea txaNotes = new JTextArea(5, 50);
    JScrollPane scrNotes = new JScrollPane(txaNotes);
    scrNotes.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

    Component spacer1 = Box.createHorizontalStrut(5);
    Component spacer2 = Box.createHorizontalStrut(5);

    JPanel panel = new JPanel(new GridBagLayout());
    panel.add(spacer1,   new GBConstraints(0,0));
    panel.add(lblFirst,  new GBConstraints(0,1));
    panel.add(txfFirst,  new GBConstraints(1,1));
    panel.add(lblLast,   new GBConstraints(2,1));
    panel.add(txfLast,   new GBConstraints(3,1).spanX(3).fill(HORIZONTAL));
    panel.add(lblStreet, new GBConstraints(0,2));
    panel.add(txfStreet, new GBConstraints(1,2).spanX(5).fill(HORIZONTAL));
    panel.add(lblCity,   new GBConstraints(0,3));
    panel.add(txfCity,   new GBConstraints(1,3));
    panel.add(lblState,  new GBConstraints(2,3).anchor(EAST));
    panel.add(txfState,  new GBConstraints(3,3));
    panel.add(lblZip,    new GBConstraints(4,3));
    panel.add(txfZip,    new GBConstraints(5,3).fill(HORIZONTAL));
    panel.add(lblNotes,  new GBConstraints(0,4));
    panel.add(scrNotes,  new GBConstraints(1,4).spanX(5).fill(BOTH));
    panel.add(spacer2,   new GBConstraints(0,5));

    JFrame frame = new JFrame("Grid Bag Demo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.add(new JScrollPane(panel), BorderLayout.CENTER);
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }
}

Again, this is just one of many ways you'll find online. Hope this helps.

splungebob
  • 5,357
  • 2
  • 22
  • 45
  • Well... change the end of my last comment to "NEAR the end of a thread". Thanks, @splungebob. I'll DEFINITELY try to digest all of your hard work! – DSlomer64 Jan 09 '14 at 21:41
  • @DSlomer64 No problem. Let me know if you have any questions or problems with this code. – splungebob Jan 09 '14 at 21:42
  • 1
    Now that I've READ @splungebob's post, I'm happy to say that (a) I tried to do what he did and (b) his way's a lot better, so I win! (That's a Patrick Star reference. He says, "I win!" in a lot of inappropriate places.) (But my "I win" is appropriate since I'm gonna jump right on implementing splungebob's stuff. – DSlomer64 Jan 09 '14 at 21:44
  • 1
    And let me just say, when I started coding GUIs with GBL, I also started off using a method similar to the one you posted, since that's what my book suggested. It worked okay, but the code was still clunky for me. The code I posted is just a result of many tweaks over several years. Use as you like, or tweak it some more to suit your own likes. – splungebob Jan 09 '14 at 21:47
  • If I can implement this stuff, I owe @splungebob big time!! VERY generous. That's what SO is supposed to be about, in one sense--sharing. I'm TRYING to be generous myself, but I don't know squat! Not true... I know SQUAT, but little more. This has been a great few days!! – DSlomer64 Jan 09 '14 at 21:53
  • I finally found the time to study @Splungebob's 2 enums and extension. And I modified his demo to make sure I understood how to USE the goodies. I will use this, Splunge! THANK YOU. I'm not sure how long it will take me to get to where I can apply stuff as you've done--i.e., I've done a simple enum, but I still don't know (but will figure out) what the deal is with the "argument-looking-attachments" to each enum "value". I know it works by my messin', but not HOW. – DSlomer64 Jan 22 '14 at 22:30
  • @DSlomer64, The "argument-looking-attachments" are just parameters to the private constructor of the enum. This is where the mapping occurs for each enum value to what GridBagConstraints constant it represents. Further reading: http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html – splungebob Jan 22 '14 at 23:46
  • @Splungebob--I had just found that very info in Ivar Horton's "Beginning Java" and was about to post that when I found your recent comment. That is a VERY good, thorough book--best I've seen. I'll take your link tomorrow. Thanks! (Until I grabbed that book, I knew NOTHING about constructors for enums since NONE of the 3 books I've purchased had ANYthing about it except Niemeyer [O'Reilly] who did NOT make it--or anything else--at all as clear as Horton did. It was Niemeyer's typical flippant/cutesy drivel. See page 158. Terrible book.) – DSlomer64 Jan 23 '14 at 01:36
2

You said:

I called it like so:

Insets insets = new Insets(0,0,0,0);
JFrame frame = new JFrame();
label = new JLabel("Blablablah");   
addComponent(frame, label, 0,0, 1,1, 0,0, 0.5f,0, HORIZ, CENTER, insets);

Did you actually omit frame.setLayout(new GridBagLayout())?
Because this is what causes the cannot add to layout: constraint must be a string (or null) error...

EDIT:
Possible duplicates:

Community
  • 1
  • 1
ccjmne
  • 9,333
  • 3
  • 47
  • 62
  • @ccjmne... Again I find myself as DENSE AS ... _iridium_ (thank you, Google). But to be a little fair to myself, since I'd modified a Java Tutorial's code and that code did NOT have the missing `setLayout` ("missing" since unnecessary to the tutorial's point), my lack of knowledge about `GridBagLayout` kept me from seeing the **absolutely obvious omission**. Anyway, I just went back to last night's addComponent adventure and added your line. Perfect-a-mundo. (Why can't I see this before asking............) – DSlomer64 Jan 09 '14 at 20:00
  • Ahahah! Don't worry. I'm glad I could be of any use! Would you have any other question/issue, feel free to ask :) And if you're afraid of asking something that's obvious for some of us, you can use my personal e-mail - available on my profile. Good luck ;) – ccjmne Jan 09 '14 at 21:26
1

Here's me using @Splungebob's enums and methods (and his "demo" as a model) to positively painlessly and quickly design and implement a very nice UI into an existing program of mine that had a terrible UI.

public class UI extends JFrame {
...
JPanel panel = new JPanel(new GridBagLayout());
panel.setBackground(Color.LIGHT_GRAY);
panel.add(lblCenterX,     new GBConstraints(0,0));
panel.add(lblCenterY,     new GBConstraints(0,1));
panel.add(lblRadius,      new GBConstraints(0,2).anchor(EAST));
panel.add(lblIterations,  new GBConstraints(0,3).anchor(EAST));
panel.add(spnCenterX,     new GBConstraints(1,0).fill(HORIZONTAL));
panel.add(spnCenterY,     new GBConstraints(1,1).ipad(90,10));
panel.add(spnRadius,      new GBConstraints(1,2));
panel.add(spnIterations,  new GBConstraints(1,3));
panel.add(btnPaint,       new GBConstraints(0,4).spanX(2).spanY(2).ipad(30,20).anchor(CENTER));
panel.add(btnDefaults,    new GBConstraints(0,6));
panel.add(btnExit,        new GBConstraints(1,6).anchor(EAST));
this.add(panel, BorderLayout.CENTER);
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);

I post this only in hopes that someone needing as much help as I did about GBL finds it and then looks back up at Splunge's stuff. How cool is the line for btnPaint, where I make a HUGE button!

DSlomer64
  • 4,234
  • 4
  • 53
  • 88
0

As I commented above, @ccjmme's help led me to add two lines and modify a signature to get a working version with no global variables. I am posting a version that is much better than that working verion. My subsequent comment explains why.

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

public class Test{

  public static void addComponent(Component c, Container f,      GridBagConstraints gbc, 
                                    int     x,        int   y, 
                                    int     w,        int   h, 
                                    int     ipadx,    int   ipady, 
                                    float   wtx,      float wty,
                                    int fill, int anchor, Insets insets){
    if(fill   <= 0)     fill    = GridBagConstraints.NONE;
    if(anchor <= 0)     anchor  = GridBagConstraints.CENTER;
    if(insets == null)  insets  = new Insets(0,0,0,0);
    gbc.gridx = x;        gbc.gridy = y;      
    gbc.gridwidth = w;    gbc.gridheight = h;     
    gbc.fill = fill; 
    gbc.ipadx = ipadx;    gbc.ipady = ipady;  
    gbc.insets = insets;  gbc.anchor = anchor; 
    gbc.weightx = wtx;    gbc.weighty = wty;

    f.add(c,gbc);
  }

  public static void addComponent(String s, Container f, GridBagConstraints gbc, 
                                  int     x,        int   y, 
                                  int     w,        int   h, 
                                  int     ipadx,    int   ipady, 
                                  float   wtx,      float wty,
                                  int fill, int anchor, Insets insets){
    addComponent(new JLabel(s),f,gbc,x,y,w,h,ipadx,ipady,wtx,wty,fill,anchor,insets);
  }  

  public static void addComponent(Component c, Container f, GridBagConstraints gbc, 
                                  int     x,        int   y){
    addComponent(c, f, gbc, x, y, 1,1, 0,0, 0.5f,0.5f, 
                  GridBagConstraints.NONE, GridBagConstraints.CENTER,
                  new Insets(0,0,0,0));
  }


  public static void main(String[] args) {
    Insets insets = new Insets(0,0,0,0);
    JFrame frame = new JFrame();
    frame.setLayout(new GridBagLayout());
    JLabel label = new JLabel("Blablablah");
    JTextField text = new JTextField("text");
    JTextField next = new JTextField("a bit longer");
    GridBagConstraints gbc = new GridBagConstraints();

    addComponent("On the fly", frame, gbc, 0,0, 1,1, 0,0, 0.5f,0, 0, 0, insets);
    addComponent(label,        frame, gbc, 0,1);

    addComponent(next, frame, gbc, 1,1, 10,1, 0,0, 5.0f,0.5f, GridBagConstraints.EAST, 0, null);        
    addComponent(text, frame, gbc, 1,0);
    frame.pack();
    frame.setVisible(true);
  }
}

Improvements:

(1) Two additional signatures for addComponent so that (i) a label can be created on the fly without declaring and initializing a variable (not good if code needs to refer to it, but mine rarely does) and (ii) any component can be added by just specifying (x,y) and accepting defaults.

(2) "Default 0" options to avoid having to type 'GridBagConstraints.CENTERand...NONE` if you'll usually want those fill and anchor values.

DSlomer64
  • 4,234
  • 4
  • 53
  • 88
  • The way I look at it (and the reason for this "answer" to my own question) is that (a) Netbeans GUIbuilder isn't the way to make GUIs and (b) GridBagLayout, once mastered (still awaiting mastery, here), is time-consuming, but with enough method support, it might not be all that bad. So I'm hoping to get constructive criticism and to supply a sort-of-working model for people looking for help to consider and improve upon (and maybe learn from). And where better to find that help than at the end of a SO thread? – DSlomer64 Jan 09 '14 at 21:39