问题
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));
...
回答1:
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.
回答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:
- Java GridBagLayout and JPanel Error: cannot add to layout: constraint must be a string (or null)
- getting Exception : java.lang.IllegalArgumentException: cannot add to layout: constraint must be a string (or null)
回答3:
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.
回答4:
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!
来源:https://stackoverflow.com/questions/21010799/looking-for-general-method-for-gridbaglayout-component-creation