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 14 posts at DZone. You can read more from them at their website. View Full User Profile

JavaFX – Tabbed Pane & Tab Panel

07.21.2010
| 12962 views |
  • submit to reddit

With each release of JavaFX, more components and controls are becoming available for developers to use when building applications. However, what if you needed a component that wasn’t included in the current JavaFX release? Well, you have a few options: you could Wait, Find, Pay or Build it yourself (or suck your thumb and rock back and forth in a fetal position underneath your desk).  The current version of JavaFX 1.3.1 did not include a Tabbed Pane class, so I decided to create my own. My intent was to see how rapidly I could create a Tabbed Pane and Tab Panel that looked pretty decent.

Disclaimer: This implementation does not follow best practices (API design). So use at your own risk. I encourage the reader to take the code apart and to improve it as they see fit.
To launch the Tab Demo click button (Java Webstart App).

Tab Demo

Screen Shots Tab Demo - Tab 0

Tab Demo - Tab 0

 

Tab Demo - Tab 1

Tab Demo - Tab 1

 

Tab Demo - Tab 2

Tab Demo - Tab 2

 

Source Code

Main.fx

Below is the main application which assembles a TabbedPane to be displayed in a Scene on the Stage.

package tabdemo;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.effect.DropShadow;
import javafx.scene.paint.Color;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.scene.paint.RadialGradient;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextBox;
import javafx.animation.Timeline;
import javafx.animation.Interpolator;
import javafx.scene.shape.Rectangle;
import javafx.stage.StageStyle;
import javafx.scene.input.MouseEvent;
import javafx.scene.Group;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import tabdemo.tabs.TabPane;
import tabdemo.tabs.TabPanel;
import javafx.scene.paint.LinearGradient;
import javafx.scene.control.Button;

/**
* @author cdea
*/
var stageWidth:Float = 450;
var stageHeight:Float = 400;

var tabPane:TabPane = TabPane {
translateY: 20
};

var tab0:TabPanel = TabPanel {
index:0
title: "Tab 0"
parentTabPane:tabPane
content:[Circle {
centerX: 100, centerY: 100
radius: 40
fill: RadialGradient {
centerX: 5
centerY: 5
focusX: 0.1
focusY: 0.1
radius: 10
stops: [
Stop {
color : Color.BISQUE
offset: 0.0
},
Stop {
color : Color.BLUE
offset: 1.0
},
] // stops
} // radial gradient
effect: DropShadow {
offsetX: 10
offsetY: 10
color: Color.BLACK
radius: 10
} // effect
} // Circle
] // content
}

var firstNameRow = HBox {
spacing:7
content: [Label {
text: "First Name:"
},
TextBox {
selectOnFocus: true
}
]
}
var lastNameRow = HBox {
spacing:7
content: [Label {
text: "Last Name:"
},
TextBox {
selectOnFocus: true
}
]
}

var formView = VBox {
spacing:7
translateX:20
translateY:100
content: [firstNameRow,lastNameRow
]
}

// all widgets or controls to be laid out.

var tab1:TabPanel = TabPanel {
index:1
title: "Tab 1"
parentTabPane:tabPane
content:[formView]
}

def spinRect = Rectangle {
translateX: 100,
translateY: 160
width: 80, height: 80
fill: Color.BLUE
}

var animateThing = Timeline {
repeatCount: 4
autoReverse:true
keyFrames: [
at (1s) { spinRect.rotate => 100.0 tween Interpolator.EASEBOTH }
];
}
animateThing.play();

var activateSpin = Button {
translateX: 30
translateY: 60
text: "Click to Spin"
action: function() {
animateThing.playFromStart();
}
}

var tab2:TabPanel = TabPanel {
index:3
title: "Tab 2"
parentTabPane:tabPane
content: [spinRect, activateSpin]
}

tabPane.content = [tab0, tab1, tab2];

var currentX:Number;
var currentY:Number;

var closeButton:Circle;

var titleGrabClose = Group {
content : [
// the window background
Rectangle {
x: 0, y: 0
arcHeight:15
arcWidth:15
width: stageWidth, height: stageWidth
fill: Color.WHITE
opacity: 0.85
},
// grabby area
Rectangle {
x: 0, y: 0
arcHeight:15
arcWidth:15
width: stageWidth, height: 20
fill: LinearGradient {
startX : 0.0
startY : 0.0
endX : 1.0
endY : 0.0
stops: [
Stop {
color : Color.LIGHTBLUE
offset: 0.0
},
Stop {
color : Color.BLUE
offset: 1.0
},

]
}

opacity: 80
onMousePressed: function(e: MouseEvent): Void {
currentX = e.screenX - stage.x;
currentY = e.screenY - stage.y;
}

onMouseDragged: function(e: MouseEvent): Void {
stage.x = e.screenX - currentX;
stage.y = e.screenY - currentY;
}
},

// window title text
Text {
font : Font {
size: 18
embolden:true
}
x: 10, y: 15
content: "Tabs Demo"
}

// mouse close area
closeButton = Circle {
centerX: stageWidth - 10 , centerY: 10
radius: 8
fill: Color.WHITE
onMousePressed: function(e: MouseEvent): Void {
javafx.lang.FX.exit();
}
},

// X marks the spot
Text {
font : Font {
size: 14
}
x: (2+ stageWidth - closeButton.radius * 2)
y: 15
content: "X"
}
] // content
} // titleGrabClose

var stage: Stage;
stage = Stage {
style: StageStyle.TRANSPARENT
title: "Slide Tab Demo"
width: stageWidth
height: stageHeight
scene: Scene {
width: stage.width
height: stage.height
content: [titleGrabClose, tabPane]
fill:null
};
resizable:false
opacity: 0.85
}

Code walk through of Main.fx

  • Lines 30-31 : initialize Stage
  • Lines 33-35: Create a TabbedPane really a JavaFX Stack
  • Lines 37-69: Create Tab 0 which contains a Circle
  • Lines 71-98: Create a simple form with Labels and controls
  • Lines 102-107: Create Tab 1 which contains the simple form
  • Lines 109-114: Create a Blue Rectangle to be put in Tab 2
  • Lines 116-122: Create a animator to control Blue Rectangle
  • Line 123: Start the animation
  • Lines 125-132: Create a button to reanimate the Blue Rectangle when pressed
  • Lines 134-139: Create Tab 2 which will contain the button and Blue Rectangle
  • Line 141: Put all tab panels into the tabbed pane object.
  • Lines 143-225: Create a drag-able title area with a close button on the upper right.
  • Lines 227-241: Display Stage containing TabPane and drag-able title.

tabs.fx

Below is the library that contains a TabPanel and TabbedPane classes.

package tabdemo;

import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Stack;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.ShapeSubtract;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;

/**
* @author cdea
*/
public class TabPanel extends CustomNode {
public-init var index:Integer;
public-init var title:String;
public var parentTabPane:Stack;
public var content:Node[] = [];
// The clickable tab area
def tabRect = Rectangle {
x: 10 + ((4 + 50) * index), y: 10
arcHeight:7
arcWidth:7
width: 50, height: 25
opacity:0
onMousePressed: function( e: MouseEvent ):Void {
var tabs:Node[] = parentTabPane.content;
for (tab in tabs) {
var tabPanel:TabPanel = tab as TabPanel;
if (index == tabPanel.index) {
delete tab from parentTabPane.content;
insert tab into parentTabPane.content;
break;
} // found so make it the top of stack [add to end]
} // loop through all tab panels
}
onMouseEntered: function(e: MouseEvent): Void {
println("Entered {title}");
}
onMouseExited: function(e: MouseEvent): Void {
println("Exited {title}");
}

}

// tab text
def tabText = Text {
content: title
textOrigin: TextOrigin.TOP
font: Font.font("Verdana", FontWeight.BOLD, 10);
x: 15 + ((4 + 50) * index)
y: 15
}

// merge or blend the bottom of tab with the main content region
def blendBottomOfTab = Rectangle {
x: 10 + ((4 + 50) * index), y: 10 + 20
width: 50, height: 25
}

// content area background area
def tabPanelContentArea = Rectangle {
x: 10, y: 10 + 25
arcHeight:15
arcWidth:15
width: 350, height: 300
}

def tabArea = ShapeSubtract {
a: [ tabRect,
blendBottomOfTab,
tabPanelContentArea
] // union tab and content area

fill: LinearGradient {
startX: 0.0, startY: 0.0, endX: 1.0, endY: 1.0
proportional: true
stops: [
Stop { offset: 0.0 color: Color.WHITE }
Stop { offset: 1.0 color: Color.GRAY }
]
} // fill
stroke: Color.BLACK
effect: DropShadow {offsetX: 2 offsetY: 4}
}

public override function create(): Node {
return Group {
content: [tabArea, tabRect, content, tabText]
}
}
}

public class TabPane extends Stack {}

Code walk through of tabs.fx

  • Line 22: Create a custom node representing a single Tab Panel
  • Line 23: Index is the position and order of a tab from left to right on a Tabbed Pane
  • Line 24: Tabs title text
  • Line 25: The owning Tabbed Pane
  • Line 26: The contents that would be displayed on this Tab Panel
  • Lines 28-52: The tab’s rectangular click region surrounding the tab title text region
  • Lines 55-61: The Text containing the title text to be overlaid on the click-able tab area
  • Lines 63-67: This is a simple rectangle to make the bottom of the click-able tab area connect smoothly to the main Tab Panel content region
  • Lines 70-75: This is the main Tab Panel content region
  • Lines 77-93: Using the ShapeSubtract on attribute ‘a’ union the three rectangles (Lines 28-75) to create a smooth looking Tab Panel
  • Lines 95-100: Creating and assembling the Tab Panel
  • Line 102: Class representing a Tabbed Pane
Conclusion

With many types of application contexts having variations of tabbed panes (Look-n-Feel) and numerous features (functionality), one might be hard pressed to find the right one for their application.  I find that it is very important in a GUI toolkit to be able to quickly prototype a control or container component before investing heavily on designing an advanced component with a good API.  JavaFX allows me to rapidly create a TabbedPane while adequately providing simple functionality.

Any feedback is welcome.

References

From http://carlfx.wordpress.com/2010/07/21/javafx-tabbed-pane-tab-panel/

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.)