Over a million developers have joined DZone.

Why Does JavaFX Get to Cheat?

· Java Zone

Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code! Brought to you in partnership with ZeroTurnaround.

Why does JavaFX get to do things that would be frowned upon if done in plain old Java? The prime example is it's treatment of layout managers and panels. In JavaFX a layout manager is bound together with a JPanel to create one cohesive unit. The API is then tailored specifically around the needs of that combonation of layout manager and panel. Now I look at the code and say to myself, "Hey that's convenient", and it IS, but that's not that point. Someone at some point in the existence of Java decided that the layout logic should be separate from the storage of components in a container. I'm sure there are good reasons for this separation. Now JavaFX seems to be making the statement that the separation was not a good idea. In essence saying that an easy to use API trumps this particular design pattern and justifies tight coupling. I can't say I disagree in this circumstance. I just want to lever the playing field. If JavaFX gets to break the rules so can I.

Panel Classes.

Throwing caution to the wind I used every dirty trick I know to try and create some easy to use panels for plain old Java. Here is what I have so far.

  • BorderPanel
  • CardPanel
  • GridPanel
  • BoxPanel
  • GBFPanel (Grid Bag Factory Panel)

BorderPanel, CardPanel and GridPanel

The border and card panels just add some convenient methods methods and are not that exciting. I have methods like setNorth() for BorderPanel and next() and previous() for CardPanel. With GridPanel I decided to create a constructor that takes a two dimensional array of components. In this way you can specify the exact location of every component in the grid in a way that actually reflects the layout of the final product. GridPanel Example:

Component[][] grid = new Component[][] {
{ new JButton("1"), new JButton("A") },
{ GridPanel.spacer(), new JButton("B"), new JButton("Blue") },
{ new JButton("3") } };
GridPanel gp = new GridPanel(grid, 5, 5);

 

As an added bonus this constructor will automatically add empty space when a row is under sized.

BoxPanel

With BoxPanel I was able to get a litte more creative. I added an enum called DIRECTION which defines spacer(), glue() and build() methods. I also added a varags version of add so that multiple components can be added in the same line. Box Example 1: - This code shows the new addSpacer() method, and the varargs version of add.

BoxPanel bp = new BoxPanel(X_AXIS);

bp.addSpacer(5);
bp.add(new JLabel("Section 1"), X_AXIS.spacer(5), new JButton("A"), X_AXIS.spacer(5), new JButton("B"));
bp.addSpacer(10);
bp.add(new JComboBox());
bp.addGlue();
bp.add(new JLabel("Section 2"), X_AXIS.spacer(5), new JButton("1"), X_AXIS.spacer(5), new JButton("2"));
bp.addSpacer(5);

Box Example 2: - This code shows the new build() method of Direction. It also nests BoxPanels to make a grid.

BoxPanel rowsPanel = new BoxPanel(Y_AXIS);
rowsPanel.add(Y_AXIS.spacer(10));
for(int rows = 0; rows < 10; rows++ ){
if(rows % 2 == 0){
rowsPanel.add(X_AXIS.build(X_AXIS.spacer(5), new JLabel("Row :" + rows), X_AXIS.spacer(5) , new JComboBox(), X_AXIS.spacer(5)));
}else{
BoxPanel oddColumns = new BoxPanel(X_AXIS);
oddColumns.add(X_AXIS.spacer(5), new JLabel("Row :" + rows), X_AXIS.spacer(5) ,new JButton("Button 1"), X_AXIS.glue());
oddColumns.addSpacer(10);
oddColumns.add(new JTextField(), X_AXIS.spacer(5) ,new JButton("Button 2"), X_AXIS.glue(), X_AXIS.spacer(5));
rowsPanel.add(oddColumns);
}
rowsPanel.add(Y_AXIS.spacer(10));
}

GBFPanel - GridBagFactoryPanel

GridBagLayout is by far one of the most complicated layout managers in the JDK. I decided that most of the problems come from the GridBagConstraints object. It just seems that the add(Component comp, Object constraint) method of Container is just not flexible enough to handle the kinds of constraints GridBag really needs. I wanted to deal with only the individual constraints that are relevant to the component I'm dealing with. Here is what I came up with.

public class DemoPanel extends GBFPanel {

private JButton button1 = new JButton("Button 1");
private JButton button2 = new JButton("Button 2");
private JButton button3 = new JButton("Button 3");
private JButton button4 = new JButton("Button 4");
private JButton button5 = new JButton("Button 5");
private JButton button6 = new JButton("Button 6");
private JButton button7 = new JButton("Button 7");
private JButton button8 = new JButton("Button 8");
private JButton button9 = new JButton("Button 9");
private JButton button10 = new JButton("Button 10");

public DemoPanel(){

addDefaultConstraints(FILL.BOTH);

add(button1, WEIGHT.x(1));
add(button2, WEIGHT.x(1));
add(button3, WEIGHT.x(1));
add(button4, GRID.WIDTH.REMAINDER);

add(button5, GRID.WIDTH.REMAINDER);
add(button6, GRID.WIDTH.RELATIVE);
add(button7, GRID.WIDTH.REMAINDER);
add(button8, GRID.HEIGHT.REMAINDER, WEIGHT.y(1));

add(button9, GRID.WIDTH.REMAINDER);
add(button10, GRID.WIDTH.REMAINDER);

}
}

Recognize this code? No? It's the GridBagLayout javadoc example.

See? And in just 10 lines of layout code, with no side effects to keep track of. Ok let me explain what's going on. I started by creating an interface defining the job of a single constraint.

public interface GridBagFactoryConstraint {

public void apply(GridBagConstraints gbc);

}
Then using a combination of enums and static factory methods I implemented a class representing every constraint from GridBagConstraints. Here is the implementation of the FILL enum as an example.
public enum FILL implements GridBagFactoryConstraint {

HORIZONTAL(GridBagConstraints.HORIZONTAL),
VERTICAL(GridBagConstraints.VERTICAL),
BOTH(GridBagConstraints.BOTH),
NONE(GridBagConstraints.NONE);

private int gbcValue;

private FILL(int val) {
gbcValue = val;
}

public void apply(GridBagConstraints gbc) {
gbc.fill = gbcValue;
}
}
I know what you are thinking and yes, I really did all of them. The next step was to create an add method in GBFPanel that would accept a variable number of these constraint objects.
public void add(Component comp, GridBagFactoryConstraint... constraints) {
....
}
This is the add method used in my example code above. Now some of you may have noticed the call to addDefaultConstraints(). This method lets you specify constraints that you wish to be applied to every component added to the panel. Its handy for factoring out common constraints. The constraints passed in the add method will always override any default values. Some time ago there was a "layout manager shoot-out" over on java.net. Here is how a GBFPanel entry might have looked.
public class AddressShootout extends GBFPanel {

private JTextField lastNameTF = new JTextField(15);
private JTextField firstNameTF = new JTextField(15);
private JTextField phoneTF = new JTextField(15);
private JTextField addressTF = new JTextField(20);
private JTextField emailTF = new JTextField(15);
private JTextField stateTF = new JTextField(2);
private JTextField cityTF = new JTextField(15);

public AddressShootout() {

addDefaultConstraints(ANCHOR.E, INSETS.top(5), INSETS.left(5), INSETS.right(5));

add(new JLabel("Last Name"));
add(lastNameTF, FILL.HORIZONTAL, WEIGHT.x(1));
add(new JLabel("First Name"));
add(firstNameTF, FILL.HORIZONTAL, WEIGHT.x(1), GRID.WIDTH.REMAINDER);

add(new JLabel("Phone"));
add(phoneTF, FILL.HORIZONTAL, WEIGHT.x(1));
add(new JLabel("Email"));
add(emailTF, FILL.HORIZONTAL, WEIGHT.x(1), GRID.WIDTH.REMAINDER);

add(new JLabel("Address"));
add(addressTF, FILL.HORIZONTAL, WEIGHT.x(1), GRID.WIDTH.REMAINDER);

add(new JLabel("City"), INSETS.bottom(5));
add(cityTF, FILL.HORIZONTAL, WEIGHT.x(1), INSETS.bottom(5));
add(new JLabel("State"), INSETS.bottom(5));
add(stateTF, FILL.HORIZONTAL, WEIGHT.x(1), INSETS.bottom(5));

}
}

I reimplemented both the javadoc and Java tutorial examples for GridBagLayout and discovered two things.

1. This gets the job done in a lot less code.

2. The examples are almost totally useless.

I then went on to reimplement a number of other "example layouts" from some of the 3rd party layout managers. I again discovered two things.

1. This gets the job done in close to the same amount of code.

2. Their examples are much more focused on actual development situations.

Conclusions

I think GridBag benefits the most from this evil tight coupling. The other layouts became easier to read but gained little real power. It's safe to say that the more complicated a layout manger the more it will benefit from tight coupling. Other possibilities for this treatment include SpringPanel, OverlayPanel, FlowPanel, GroupPanel, TablePanel, MigPanel, FormPanel and PagePanel. So this is evil, right? JavaFX gets to do it. Why shouldn't we? Is this an architectural compromise that's worth it?

The Java Zone is brought to you in partnership with ZeroTurnaround. Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code!

Topics:

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}