With the new, improved, custom layout capabilities in JavaFX 1.3, I thought it'd be useful to post a simple example. This example defines a custom layout in JavaFX whose behavior is like the BorderLayout, familiar to most Java programmers. Here's a screen shot:
There are actually two ways to create custom layouts in JavaFX:
- Sub-class javafx.scene.layout.Container
- Create a javafx.scene.layout.Panel instance
Here is the code from today's example that employs the first way. Note that I'm leveraging the Node#id instance variable to supply the directional values needed by the traditional BorderLayout.
/* * BorderLayout.fx - * Example of subclassing Container to create a custom layout */ package containersubclasslayoutexample.layout; import javafx.scene.layout.*; import javafx.scene.layout.Container.*; public class BorderLayout extends Container { var topHeight:Number; var bottomHeight:Number; var leftWidth:Number; var rightWidth:Number; override function doLayout():Void { for (node in getManaged(content)) { if (node.id == "top") { topHeight = getNodePrefHeight(node); setNodeWidth(node, width); setNodeHeight(node, getNodePrefHeight(node)); positionNode(node, 0, 0); } else if (node.id == "bottom") { bottomHeight = getNodePrefHeight(node); setNodeWidth(node, width); setNodeHeight(node, getNodePrefHeight(node)); positionNode(node, 0, height - bottomHeight); } else if (node.id == "left") { leftWidth = getNodePrefWidth(node); setNodeWidth(node, getNodePrefWidth(node)); setNodeHeight(node, height - (topHeight + bottomHeight)); positionNode(node, 0, topHeight); } else if (node.id == "right") { rightWidth = getNodePrefWidth(node); setNodeWidth(node, getNodePrefWidth(node)); setNodeHeight(node, height - (topHeight + bottomHeight)); positionNode(node, width - rightWidth, topHeight); } else if (node.id == "center") { setNodeWidth(node, width - (leftWidth + rightWidth)); setNodeHeight(node, height - (topHeight + bottomHeight)); positionNode(node, leftWidth, topHeight); } } } override function getPrefWidth(height:Number):Number { return width; } override function getPrefHeight(width:Number):Number { return height; } }
Here is a code example of using the BorderLayout shown above.
/* * ContainerSubclassLayoutExample.fx - * Example of subclassing Container to create a custom layout */ package containersubclasslayoutexample; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.control.*; import containersubclasslayoutexample.layout.BorderLayout; var nodes = [ Button { text: "Top" id: "top" }, Button { text: "Bottom" id: "bottom" }, Button { text: "Left" id: "left" }, Button { text: "Right" id: "right" }, Button { text: "Center" id: "center" } ]; Stage { var sceneRef:Scene; title: "Container Subclass Layout Example" scene: sceneRef = Scene { width: 500 height: 300 content: [ BorderLayout { width: bind sceneRef.width height: bind sceneRef.height content: nodes } ] } }
Lastly, here is an example of defining the BorderLayout custom layout functionality using the second way listed above, which is to create a Panel instance and override its behavior:
/* * PanelLayoutExample.fx - * Example of using a Panel to create a custom layout */ package panellayoutexample; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.*; import javafx.scene.layout.Container.*; var nodes = [ Button { text: "Center" id: "center" }, Button { text: "Top" id: "top" }, Button { text: "Bottom" id: "bottom" }, Button { text: "Left" id: "left" }, Button { text: "Right" id: "right" }, ]; Stage { var panel:Panel; var sceneRef:Scene; title: "Panel Layout Example" scene: sceneRef = Scene { width: 500 height: 300 content: [ panel = Panel { var topHeight:Number; var bottomHeight:Number; var leftWidth:Number; var rightWidth:Number; content: bind nodes width: bind sceneRef.width height: bind sceneRef.height onLayout: function():Void { for (node in getManaged(panel.content)) { if (node.id == "top") { topHeight = getNodePrefHeight(node); setNodeWidth(node, panel.width); setNodeHeight(node, getNodePrefHeight(node)); positionNode(node, 0, 0); } else if (node.id == "bottom") { bottomHeight = getNodePrefHeight(node); setNodeWidth(node, panel.width); setNodeHeight(node, getNodePrefHeight(node)); positionNode(node, 0, panel.height - bottomHeight); } else if (node.id == "left") { leftWidth = getNodePrefWidth(node); setNodeWidth(node, getNodePrefWidth(node)); setNodeHeight(node, panel.height - (topHeight + bottomHeight)); positionNode(node, 0, topHeight); } else if (node.id == "right") { rightWidth = getNodePrefWidth(node); setNodeWidth(node, getNodePrefWidth(node)); setNodeHeight(node, panel.height - (topHeight + bottomHeight)); positionNode(node, panel.width - rightWidth, topHeight); } else if (node.id == "center") { setNodeWidth(node, panel.width - (leftWidth + rightWidth)); setNodeHeight(node, panel.height - (topHeight + bottomHeight)); positionNode(node, leftWidth, topHeight); } } } prefWidth: function(height:Number):Number { return panel.width; } prefHeight: function(width:Number):Number { return panel.height; } } ] } }
To learn more about using and creating layouts in JavaFX 1.3, see the blog posts that Amy Fowler has been writing on the subject, and the Custom Layouts chapter in the upcoming new edition of the Clarke/Bruno/Connors JavaFX book.
A note regarding the JavaFX RIA Exemplar Challenge: We have received several entries and I'm in the process of packaging them up and sharing them with the judges. The winner will be announced in early June, 2010.
Regards,
Jim Weaver
The ID is supposed to be unique in the scenegraph, so I wouldn't use it for this purpose. Dedicated properties as Jonathan suggested would be my choice as well.
Posted by: Jo Voordeckers | June 07, 2010 at 07:37 AM
Pretty nice post.In any case I’ll be subscribing to your feed and I hope you write again soon! I just stumbled upon your blog and wanted to say that I have really enjoyed browsing your blog posts.
http://www.laptop-battery-chargers.com/hp-pavilion-dv9700.html hp dv9700 battery
Posted by: charger | June 07, 2010 at 04:06 AM
Jim,
Great post i was wondering why there is not BorderLayout :P
I tried to modify your 1st example and added a panel in panel. removed the first button and added a panel and id: "top"
in panel i added another BorderLayout with imageview left text center and imageview right. but when i try to run i cant see the top layer in the scene!!! any idea ! i am new to javafx.
Posted by: Account Deleted | June 04, 2010 at 08:03 AM
Jonathan,
If I was doing your version, I'd recommend extending CustomNode rather than Container. The reason being that extending Container suggests that the 'content' variable is publicly writable. (Although this is not necessarily true -- if it is bound it is no longer publicly writable -- that can't be discovered until runtime.) Extending CustomNode allows you to only expose the north, south, east, west, and centre variables for writing while children remains private.
If you think about it, however, a BorderLayout is a custom node and not a layout or container -- so that is a more appropriate approach. A BorderLayout cannot contain an unbounded list of content, but only a fixed-size set of sub-nodes in a specific arrangement. (Put 20 nodes into a BorderLayout and what would it do?)
But maybe that's just the "make things describe accurately what they do" pedant in me!
William
Posted by: William Billingsley | June 01, 2010 at 09:51 PM