Book Author, Blogger, a wannabe GUI guy, JavaFX enthusiast, Groovy, mobile phones, AI and most importantly husband and proud father of two awesome kids. Carl is a DZone MVB and is not an employee of DZone and has posted 15 posts at DZone. You can read more from them at their website. View Full User Profile

JavaFX – A Poor Man’s Form Designer

01.14.2010
| 6989 views |
  • submit to reddit

Software developers who build form based applications will often struggle with positioning controls dynamically while a window resizes. This is better known as “Layout Management”. Many developers will rely on GUI builders that provide a “WYSIWYG” ability allowing a designer to drag and drop components onto a canvas area. On the other hand some developers prefer to hand code GUI form screens. There are pros and cons to using both strategies; however in this article I will strike a balance by building a form designer to allow a developer to assist them in hand coding GUI form screens (sound strange, just read on). While the JavaFX community has anticipated for GUI designer tools, I couldn’t wait.

So, I decided to create the “Poor Man’s Form Designer”. This simple designer tool uses the popular MigLayout by Mikael Grev and was ported over to the JavaFX platform by Dean Iverson (an author of the book “Pro JavaFX Platform”) into the JFXtras project. The PMFD’s source code consists of one file Main.fx and just at around 370 lines of code including comments. After the Conclusion section of this article you will see the full source code to this form designer. Simply click on the source code link to expand and your on your way to developing beautiful looking forms!

 

 

Poor Man's Form Designer Constraints Editor

Form Design Constraint Editor

Demo

 

Demo

Instructions:

  1. Click Launch button above
  2. Go to the MigLayout cheat sheet at http://migcalendar.com/miglayout/cheatsheet.html to keep handy when tweaking the “Form Design Constraint Editor” window.
  3. Go to the “Form Design Constraint Editor” and click the dump button. Here it will dump all the current constraints for the following areas: Layout, Column/Row and Components constraints.
  4. Go to the “Form Design Constraint Editor” window under the Component Constraint area is a combo box please select stateLabel, then tab to the constraint text field (just right of combo box) and type “align right“. Click Apply button to update changes.
  5. Be sure to observe the Form View window and you’ll notice the “State:” label is right justified beside the state combo box field.
  6. Play around with other constraint areas. Cut and paste from the cheat sheet. The cheat sheet is divided in three sections: Layout Constraints, Column & Row Constraints and Component constraints. Don’t forget to hit the apply button.
  7. You’ll notice in the component constraint area when selecting a control in the combo box the previous constraint is preserved and displayed in the constraint text box.
  8. Go to the “Form Design Constraint Editor” and click the dump button. Here it will dump all the current constraints for the following areas: Layout, Column/Row and Components constraints.
  9. Copy and paste those dumped constraints to assist in your hand coded GUIs. (See below “Some Code Details” section)

Some Code Details

Disclaimer: While the coding details below seem strange,  you may want to visit Dean’s Pleasing Software blog entry “MigLayout for JavaFX Reloaded” regarding how to Get started with MigLayout and JavaFX. http://pleasingsoftware.blogspot.com/search?q=layout

Controls and default constraints for Form View window Display

// all widgets or controls to be laid out.
var firstNameLabel=Label {
id:"firstNameLabel"
text: "First Name:"
};
var firstNameField=TextBox {
id:"firstNameField"
selectOnFocus: true
}
// ... other controls

var layoutConstraintString:String = "";

var rowConstraintString:String = "";

var compConstraintStrings = [
"", // 0
"", // 1
"gap unrelated", // 2
"wrap", // 3
// ... other constraint strings are added here ...
];

// Nodes to be laid out

var nodesToLayout = bind [
migNode( firstNameLabel, compConstraintStrings [0] ),
migNode( firstNameField, compConstraintStrings [1] ),
migNode( lastNameLabel, compConstraintStrings [2] ),
migNode( lastNameField, compConstraintStrings [3] ),
// ... other nodes are added here ...
];
  

Main MigLayout object placed in a scene:

// http://pleasingsoftware.blogspot.com/search?q=layout
var scene:Scene = ResizableScene {
content: [
MigLayout {
constraints: bind layoutConstraintString
content:bind nodesToLayout
rows: bind rowConstraintString
}
]
};
  

Enhancements

Above you will see hand coded controls that eventually get displayed onto the Form View window (Person Form). Since the the GUI control code elements are simply sequential, I believe it would be quite easy to add, insert and remove controls dynamically onto the Form View window area.  Also, possibly a property sheet window for controls, skins and behavior swapping.

Conclusion

The main goal is to properly lay out components in order to create a nice looking forms and also to learn the popular layout framework library MigLayout with it’s constraints language syntax. Using JavaFX’s binding allows the form designer tool to enable the user to easily tweak constraints on the fly without having to rerun an application GUI for every adjustment made during the layout process in a form view. Another interesting thing to note is that since we are in the JavaFX world we can layout shapes, custom nodes and graphics (not just regular form controls). As a reminder regarding the use of JFXtras MigLayout, users should read about the known issues, please go to JFXtras MigLayout wiki at: http://jfxtras.org/portal/core/-/wiki/JFXtras/MigLayout . Well, by the time you read this blog entry I’m sure a nice GUI form designer/builder tool will be available.

The source code is listed below

Requirements:

  • Java 6 JDK or greater
  • JavaFX 1.2.1 SDK
  • JFXtras version 0.5 jars
  • latest Miglayout jar

/**
* JavaFX - A Poor Man's Form Designer
* Main.fx
* cdea
* Created on Nov 25, 2009, 12:21:27 AM
*/

package migtest2;

import javafx.stage.Stage;
import javafx.scene.paint.Color;
import javafx.scene.control.TextBox;
import javafx.scene.control.Button;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import org.jfxtras.scene.ResizableScene;
import org.jfxtras.scene.layout.MigLayout;
import org.jfxtras.scene.layout.MigLayout.*;
import org.jfxtras.stage.JFXDialog;
import javafx.ext.swing.SwingComboBox;
import javafx.ext.swing.SwingComboBoxItem;
import javafx.util.Sequences;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.text.Font;
import javafx.ext.swing.SwingComponent;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;

// all widgets or controls to be laid out.
var firstNameLabel=Label {
id:"firstNameLabel"
text: "First Name:"
};
var firstNameField=TextBox {
id:"firstNameField"
selectOnFocus: true
}
var lastNameLabel=Label {
id:"lastNameLabel"
text: "Last Name:"
};
var lastNameField=TextBox {
id:"lastNameField"
}
var addressLabel=Label {
id:"addressLabel"
text: "Address:"
};
var addressField=TextBox {
id:"addressField"
}
var cityLabel=Label {
id:"cityLabel"
text: "City:"
};
var cityField=TextBox {
id:"cityField"
}
var stateLabel=Label {
id:"stateLabel"
text: "State:"
};
var stateField:SwingComboBox = SwingComboBox {
id: "stateField"
items: [
SwingComboBoxItem{text:"MD"},
SwingComboBoxItem{text:"CA"},
SwingComboBoxItem{text:"NY"}
]
visible: true
}

var zipLabel=Label {
id:"zipCodeLabel"
text: "ZIP Code:"
};
var zipField=TextBox {
id:"zipCodeField"
}

// Default constraints for each component
var compConstraintStrings = [
"", // 0
"", // 1
"gap unrelated", // 2
"wrap", // 3
"", // 4
"span, grow, wrap",// 5
"", // 6
"", // 7
"", // 8
"grow, wrap", // 9
"", // 10
"", // 11
];

// Nodes to be laid out
var nodesToLayout = bind [
migNode(firstNameLabel, compConstraintStrings[0] ),
migNode(firstNameField, compConstraintStrings[1] ),
migNode(lastNameLabel, compConstraintStrings[2] ),
migNode(lastNameField, compConstraintStrings[3] ),
migNode(addressLabel, compConstraintStrings[4] ),
migNode(addressField, compConstraintStrings[5] ),
migNode(cityLabel, compConstraintStrings[6] ),
migNode(cityField, compConstraintStrings[7] ),
migNode(stateLabel, compConstraintStrings[8] ),
migNode(stateField, compConstraintStrings[9] ),
migNode(zipLabel, compConstraintStrings[10] ),
migNode(zipField, compConstraintStrings[11] ),
// ... other nodes are added here ...
];

///////////////////////////////////////////////
// The rest below is the form designer code.
// Stuff above is what a programmer would add
// to their form to show in the form designer.
// @TODO Refactor above and allow a palette of
// graphic & control elements to add to
// the form view display area.
///////////////////////////////////////////////

// List Comprehensions - Strings representing nodes' id
var listIds = for( n in nodesToLayout) n.id;

// List comprehensions - SwingComboBoxItems
var comboboxItems = for( n in listIds) SwingComboBoxItem{ text:n };

// Combo Box control with ids of each widget on form.
var idListBox:SwingComboBox = SwingComboBox {
text : bind selection with inverse;
items: [comboboxItems]
}

// select first one as default
idListBox.selectedIndex = 0;

////////////////////////////////////////////////////
// Layout constraints section
////////////////////////////////////////////////////
var layoutConstraintString:String = "";
var layoutConstraintLabel:Label = Label {
text: "Layout Constraint"
font: Font {
embolden:true
size: 20
}
}
var layoutConstraintTextBox:TextBox = TextBox {
selectOnFocus: true
}
var layoutConstraintApplyButton:Button = Button {
text: "Apply"
action: function() {
layoutConstraintString = layoutConstraintTextBox.text;
}
}

////////////////////////////////////////////////////
// Row Column constraints section
////////////////////////////////////////////////////
var rowConstraintString:String = "";
var rowConstraintLabel:Label = Label {
text: "Row/Column Constraint"
font: Font {
embolden:true
size: 20
}
}

// Constraint textbox
var rowConstraintTextBox:TextBox = TextBox {
columns: 30
};

var rowConstrainApplyButton:Button = Button {
text: "Apply"
action: function() {
rowConstraintString = rowConstraintTextBox.text;
}
};

////////////////////////////////////////////////////
// Component constraints section
////////////////////////////////////////////////////
var compConstraintLabel:Label = Label {
text: "Component Constraint"
font: Font {
embolden:true
size: 20
}
};

// Constraint textbox
var constraintTextBox:TextBox = TextBox {
columns: 30
};

var selection: String on replace {
constraintTextBox.text = compConstraintStrings [
Sequences.indexByIdentity(listIds, selection)
];
};

var configureNodeButton:Button = Button {
text: "Apply"
action: function() {
for (i in [0.. sizeof nodesToLayout -1]) {
if (nodesToLayout[i].id.equalsIgnoreCase(selection)) {
compConstraintStrings[i] = constraintTextBox.text;
break;
}
}
} // action
};

////////////////////////////////////////////////////
// MigLayout Dump constraints section
////////////////////////////////////////////////////
var miglayoutDumpLabel:Label = Label {
text: "MigLayout Constraints Dump"
font: Font {
embolden:true
size: 20
}
};

var constrainDumpButton:Button = Button {
text: "Dump"
action: function() {
var miglayout:MigLayout = scene.content[0] as MigLayout;
var buffer:String;
var migStructure:String;
buffer = "Layout Constraint: {miglayout.constraints} \n"
"Column/Row Constraint: {miglayout.rows} \n"
"Controls Constraints: \n";
var buffer2:String;
for (i in [0.. sizeof listIds -1]) {
buffer2 = "{buffer2} id: {listIds[i]} - {compConstraintStrings[i]}\n";
}
ta.setText("{buffer}{buffer2}");
}
};

var ta:JTextArea = new JTextArea();
ta.setColumns(40);
ta.setRows(20);
ta.setAutoscrolls(true);
var sp = new JScrollPane(ta);

var dumpTextArea = SwingComponent.wrap(sp);
// constrains for the constraint configure window.
var editorConstraintStrings = [
"wrap",
"growx",
"wrap",
"span, newline 15px, wrap",
"growx",
"wrap",
"span, newline 15px, wrap",
"growx",
"grow",
"",
"span, newline 15px, wrap",
"wrap",
"span, growx"
];

var inputPanelToLayout = bind [
// layout constraint section
migNode(layoutConstraintLabel, editorConstraintStrings[0]),
migNode(layoutConstraintTextBox, editorConstraintStrings[1]),
migNode(layoutConstraintApplyButton, editorConstraintStrings[2]),

// row constraint section
migNode(rowConstraintLabel, editorConstraintStrings[3]),
migNode(rowConstraintTextBox, editorConstraintStrings[4]),
migNode(rowConstrainApplyButton, editorConstraintStrings[5]),

// component constraint section
migNode(compConstraintLabel, editorConstraintStrings[6]),
migNode(idListBox, editorConstraintStrings[7]),
migNode(constraintTextBox, editorConstraintStrings[8]),
migNode(configureNodeButton, editorConstraintStrings[9]),

// dump constraints section
migNode( miglayoutDumpLabel, editorConstraintStrings[10]),
migNode( constrainDumpButton, editorConstraintStrings[11]),
migNode( dumpTextArea, editorConstraintStrings[12]),
];

var scene:Scene = ResizableScene {
fill: LinearGradient {
startX : 0.0
startY : 0.0
endX : 1.0
endY : 0.0
stops: [
Stop {
color : Color.DARKTURQUOISE
offset: 0.0
},
Stop {
color : Color.WHITE
offset: 1.0
},
] // stops
} // fill
content: [
MigLayout {
constraints: bind layoutConstraintString
content:bind nodesToLayout
rows: bind rowConstraintString
},
] // content
}; // scene

var constraintInputWindowScene:Scene = ResizableScene {
fill: LinearGradient {
startX : 0.0
startY : 0.0
endX : 1.0
endY : 0.0
stops: [
Stop {
color : Color.GRAY
offset: 0.0
},
Stop {
color : Color.WHITE
offset: 1.0
},
] // stops
} // gradient fill

content: [
MigLayout {
//constraints: ""
content:bind inputPanelToLayout
},
] // content
};

// Form View (Person Form)
var stage:Stage = Stage {
y:150
x:100
width: 500
height: 200
title: "Poor Man's Form Designer"
scene: scene
visible:true
opacity: .94
}

// Form Design Constraint Editor.
var constraintInputWindow:JFXDialog = JFXDialog{
title: "Layout / Column & Row / Component Constraints"
owner:stage
x: stage.x + stage.width
y: 150
modal:false
visible:true
scene:constraintInputWindowScene
height:440
width: 600
}
From http://carlfx.wordpress.com/
Published at DZone with permission of Carl Dea, 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.)