By: Dean Iverson
Note: Some of the information in this post is out of date. Please see this blog post for changes. -Dean
As much as I love the power of binding in JavaFX, it is tedious to have to bind the locations and sizes of Nodes every time you create an interface. MigLayout has long been my favorite layout manager for Java, so I volunteered to take on the task of porting it to JavaFX. It turns out that MigLayout and JavaFX are an extremely powerful combination. In this article, I'm going to show some basic MigLayout usage as well as introduce some concepts that are unique to the JavaFX version. Let's get started!
The Basics
If you are unfamiliar with MigLayout, I suggest you keep a copy of the Cheat Sheet handy while you're reading this. Anyone who has read my previous contributions to this blog, knows I'm all about showing the code. So let's start with a simple example.
Stage {
title: "Mig Centering Test"
scene: Scene {
width: 200
height: 200
fill: Color.PINK
content: MigLayout {
fitParent: true
layout: "fill"
migContent: MigNode {
constraints: "center"
node: Rectangle {
width: 100
height: 100
}
}
}
}
}
The stage and scene are set up in the usual way but with a pink background. You can see
that the content of the scene consists of a single MigLayout container. The
fitParent
variable is unique to the JavaFX implementation of MigLayout.
If set to true, the MigLayout container will expand to fit the width and height of its
parent node. If the container is at the root of the scene graph, and therefore has no
parent, the MigLayout container will expand to fill the scene.
The next variable is layout
which specifies the constraints for the layout
as a whole. There are also row and column constraints which will appear in later examples.
The layout constraint specified here is fill
, a commonly used constraint that
claims all of the available container space for the rows and columns of the layout. Note
the tie between fitParent
and the fill
constraint. The former
makes the container expand to fit its parent's bounds while the latter makes all of that
space availble for use by the layout engine.
The migContent
variable holds the sequence of MigNodes
that are to
be used in the layout. The MigNode
class is a simple wrapper that associates
a Node
with its component constraints. If you have a node that doesn't need
any constraints, you can add the node directly to the migContent sequence without wrapping
it with a MigNode object. Unlike a regular Container
or Group
,
migContent
must be used rather than the content
variable. In
this case the center
constraint is used to keep a black rectangle centered in
the layout even when the window resizes (thanks to fitParent
and
fill
).
The conciseness and elegance of its syntax is a large part of why MigLayout has made its way into the hearts of developers everywhere. Just a few lines are all it takes to center a node and keep it there. Click the link below to run this example.
What's Your Alignment?
Let's move on and pick up the pace a bit. This next example will show how to specify row and column constraints as well as look at MigLayout's ability to align its components.
Stage {
title: "Mig Alignment Test"
scene: Scene {
width: 700
height: 300
fill: LinearGradient {
stops: [
Stop {
offset: 0.0,
color: Color.SILVER.ofTheWay( Color.WHITE, 0.35 ) as Color
},
Stop {
offset: 1.0,
color: Color.SILVER.ofTheWay( Color.BLACK, 0.35 ) as Color
}
]
}
content: MigLayout {
fitParent: true
layout: "fill, wrap, insets 0"
rows: "[]50[]50[]"
columns: "[]50[]50[]"
migContent: [
migNode( createText( "Left, Top" ), "alignx left, aligny top" ),
migNode( createText( "Center, Top" ), "alignx center, aligny top" ),
migNode( createText( "Right, Top" ), "alignx right, aligny top" ),
migNode( createText( "Left, Center" ), "alignx left, aligny center" ),
migNode( createText( "Center, Center" ), "alignx center, aligny center" ),
migNode( createText( "Right, Center" ), "alignx right, aligny center" ),
// Note: you can just use ax and ay for short
migNode( createText( "Left, Bottom" ), "ax left, ay bottom" ),
migNode( createText( "Center, Bottom" ), "ax center, ay bottom" ),
migNode( createText( "Right, Bottom" ), "ax right, ay bottom" ),
]
}
}
}
function createText( content:String ) {
Text {
content: content
font: Font { size: 24 }
cache: true
effect: DropShadow {
offsetX: 5
offsetY: 5
radius: 8
}
}
}
This time the container has row and column constraints that specify three rows and three
columns all of which have 50 pixels of space between them. Also new in this example is
the use of the migNode
helper function. This is a module-level function
in the MigNode file that will create and return a MigNode
.
In JavaFX any effect, such as the DropShadow I've used here, is incorporated into the bounds of the Node before the layout engine sees it. So even though the text nodes in the third row are being aligned to the bottom of the container, their drop shadows do not get clipped. This is all taken care of automatically by JavaFX and MigLayout. You can see the alignment code in action by clicking the following link.
Docking
MigLayout is flexible enough to support docking as well as grid layout. MigLayout's docking functionality is much like Swing's BorderLayout.
Stage {
title: "Mig Docking Test"
scene: Scene {
width: 400
height: 400
fill: Color.LEMONCHIFFON
content: MigLayout {
fitParent: true
layout: "fill"
migContent: [
migNode( createLabel( Color.KHAKI, "North" ), "north" ),
migNode( createLabel( Color.GOLDENROD, "South" ), "south" ),
migNode( createLabel( Color.GOLD, "East" ), "east" ),
migNode( createLabel( Color.DARKKHAKI, "West" ), "west" ),
MigNode {
constraints: "center, grow"
node: Text {
id: "dockingText"
content: "MigLayout Docking"
font: Font { size: 24 }
}
}
]
}
}
}
function createLabel( color:Color, label:String ) {
MigLayout {
layout: "fill"
migContent: [
MigNode {
constraints: "pos 0 0 container.x2 container.y2"
node: ResizableRectangle {
fill: LinearGradient {
stops: [
Stop {
offset: 0.0,
color: color.ofTheWay( Color.WHITE, 0.25 ) as Color
},
Stop {
offset: 1.0,
color: color.ofTheWay( Color.BLACK, 0.25 ) as Color
}
]
}
}
},
MigNode {
constraints: "center, grow"
node: Text {
content: label
font: Font { size: 18 }
}
}
]
}
}
There are two interesting things in this example. First, it shows that one MigLayout can be nested inside another. A MigLayout is used to create a text label with a colorful background which is then placed inside another MigLayout. Secondly, creating the background for the text label shows some of the power of MigLayout's absolute positioning capabilities. In this case they are being used to create a background rectangle that resizes with the layout container in which it is placed. The grid portion of the layout contains only a text node that displays "MigLayout Docking", but you can place multiple nodes in the grid just like any other MigLayout grid. In fact, you can even dock more nodes inside of those that are already docked if you want: just add another node and use the same docking constraint.
You can run the web start version of this example by clicking on the link below.
Swing Components, Too!
MigLayout for JavaFX can also handle Swing components with ease.
Stage {
title: "Mig Grow Test"
scene: Scene {
width: 300
height: 200
fill: LinearGradient {
endX: 0.0
stops: [
Stop { offset: 0.0, color: Color.SLATEGRAY },
Stop { offset: 1.0, color: Color.DARKSLATEGRAY },
]
}
content: MigLayout {
fitParent: true
layout: "fill, wrap"
rows: "[][]4mm[]push[]"
columns: "[][]"
migContent: [
migNode( createLabel( "Email" ), "ax right" ),
migNode( createTextField( true ), "growx" ),
migNode( createLabel( "Password" ), "ax right" ),
migNode( createTextField( false ), "growx" ),
migNode( createButton( "Login" ), "skip, right" ),
migNode( createLabel( "This text stays at the bottom" ), "span" ),
]
}
}
}
function createLabel( text:String ) {
SwingLabel { text: text }
}
function createTextField( isPlainText:Boolean ):SwingComponent {
if (isPlainText) {
SwingTextField { }
} else {
SwingComponent.wrap( new JPasswordField() );
}
}
function createButton( text:String ) {
SwingButton { text: text }
}
This example shows more of the flexibility of MigLayout. The row constraints specify that
there should be a 4 millimeter gap between the second and third rows and a gap of
push
between the third and fourth rows. The push
constraint
will try to claim any leftover space in the layout for its gap. The result is that
the fourth row will stay pinned to the bottom of the scene. Note that the two text
fields specify the growx
constraint which allows the component to grow
in the horizontal dimension.
It should be noted that, although this example shows that MigLayout can be used with the JavaFX Swing components, it does not rely on any Swing or AWT code internally. My goal is to allow the use of MigLayout on mobile devices as well.
Click below to run this example.
Closing Comments
There are a few things that aren't supported yet in the JavaFX version of MigLayout. Among these are layout callbacks, constraint objects (only strings are supported right now), and baseline alignment. There are bound to be some things that don't work quite right, but if you are interested in testing out MigLayout for JavaFX, you can find it as part of the JFXtras project. If you encounter a bug, please file an issue.
Finally, I owe a debt of thanks to the people who have been supportive of my efforts to bring MigLayout to JavaFX. They have answered my questions and then, on occasion, have patiently explained their answers. My thanks to Mikael Grev, the creator of MigLayout, who has been very supportive of the effort to port it to JavaFX. Richard Bair and Amy Fowler at Sun have answered my many questions about the JavaFX UI libraries. And Stephen Chin, along with Keith Combs, blazed the trail in writing JavaFX layout containers and thus made my job significantly easier.
Dean Iverson
JavaFXpert.com
James: I started learning javafx and bought your book Pro JavaFX Platform and your blog on migLayout. Now I start writing my first javafx application.
I have a question on miglayout I have a stack of images in migNode sequence created in a list of migNode().
I need to push a certain image to the front automatically. I am thinking to have a timeline and go through a migNode
sequence to flip the images in each node one by one until the pause button is pressed. However, I have a difficult time to
(1) bind the list of images to the migNode() and (2) lookup the MigNode sequence, although I have a id.
var nodes = scene.lookup("migNodes") as MigNode[];
No matter how I do, nodes are always null. How should I retrive this sequence?
content: MigLayout {
id: "migNodes"
constraints: "fill, wrap 7"
rows: "[]2[]"
columns: "[]2[]"
content: bind [
migNode(createImageViewStack(gameImageGroupModels[0]), ""),
migNode(createImageViewStack(gameImageGroupModels[1]), ""),
migNode(createImageViewStack(gameImageGroupModels[2]), ""),
...
Could you give some suggestions, pointers or any advice? I appreciate it very much.
Posted by: Qin Ding | August 09, 2009 at 03:38 PM
Hi Jim,
I'm trying out the MigLayout with my mobile app,
But I keep getting an error if I choose to run my app in the mobile emulator...
Is it true than JFXtras is not yet supported for mobile apps?
this is the error I get, when I try to run my app:
Error preverifying class org.jfxtras.async.ObjectSwingWorker
java/lang/NoClassDefFoundError: javax/swing/SwingWorker
ERROR: preverify execution failed, exit code: 1
Thanks in advance!
Ramin
Posted by: Ramin | April 10, 2009 at 07:06 AM
I can't launch the demos, I receive an error.
Posted by: Alberto | March 19, 2009 at 11:27 AM
So what would you propose to do, if I take over the border layout example from your example and I need to adjust an image laying in the north panel to fill always the north panel when resizing?
Posted by: Philipp | February 23, 2009 at 10:00 AM