|
Articles Index
Webster's New World College Dictionary defines the suffix o-rama as "a greater-than-usual number, volume, or variety
of a specified thing." When it comes to JavaFX applications, o-rama is a fitting suffix. Although the
JavaFX platform is only a few months old -- its initial
full release was in December 2009 -- people are already building some very interesting applications with it.
This is a vibrant, creative, and extremely productive community.
|
The JavaFX community is vibrant, creative, and extremely productive. |
That creativity and productivity is no surprise because the JavaFX platform is designed to make it easy to create
rich Internet applications (RIAs) that are visually engrossing. In other words, you can build cool applications very quickly.
And the JavaFX platform enables you to develop applications that can be run in multiple types of devices -- from desktops,
to laptops running browsers, to the latest mobile devices, to television sets and other consumer devices. In many cases,
a JavaFX application can run in all of these devices with little or no change to the code.
This article highlights some of the applications that members of the JavaFX community have developed. It examines some of the
techniques that the application authors used and provides some insights from the authors about their experiences using the
JavaFX platform.
Contents
|
JavaFX Coding Challenge
You can add to the growing list of innovative JavaFX applications and win money too by entering the
JavaFX Coding Challenge. Design and submit a new application
using JavaFX. You might win a cash prize up to $25,000 and a showcase spot on
javafx.com.
|
|
It seems that almost everyone these days is tweeting. Twitter,
a popular social networking service, is giving users worldwide a way to stay in touch with friends, coworkers,
and family members through short microblogs called tweets. A tweet is a text-based post to Twitter of up to
140 characters in length. A tweet is displayed on the user's profile page and delivered to other users who have subscribed
to them. These subscribed users are known as followers.
TwitterFX is a JavaFX-based Twitter client and open-source
project led by IT Architect Steven Herod. Although TwitterFX is currently under development, its interim builds demonstrate
a very nice user interface (UI) and set of features. Figure 1 shows the UI presented by TwitterFX build 0.30j.
|
Some of the many features that TwitterFX offers are as follows:
- Public and Friends Timelines. You can view tweets in the public timeline -- these are
tweets posted by the public at large. Or you can view tweets in your friends timeline -- these are tweets
posted by the people you follow.
- Favorites. You can mark specific tweets as favorites and display those tweets.
- Follow and Unfollow Users. You can follow someone and see their tweets in your friends timeline.
You can also unfollow a user and stop viewing their tweets.
You can also see who is following you.
Your followers see your tweets.
- Mark and Filter Tweets. You can mark tweets as read
and show only unread tweets. You can also show tweets from a particular user.
- Spoiler Blocking. You can specify one or more spoiler words. These are words that are deemed unacceptable for viewing.
TwitterFX hides any tweets that contain any of these words.
- Tweet Deleting. You can delete specific tweets.
- User Information. You can get information about someone, whether that person is a public tweeter, a friend,
a follower, or someone you are following. The information includes name, location, biography, and web site, as well as tweet
statistics such as the number of people who are following this user. You can also display
a Google map of the person's location.
Exploring the Code
TwitterFX uses Twitter's REST API
to produce or consume Twitter messages and access other aspects of the Twitter service. The application makes these
calls in the TwitterAPI class. You can find the TwitterAPI class in the
twitterfx\twitter directory of the TwitterFX source code.
Here, for example, is a snippet of a function in the TwitterAPI class that handles logging into the Twitter service:
import javafx.io.http.HttpRequest;
public function login():Void {
...
try {
var request = twitterfx.twitter.UpdateRequestHandler {
location: "https://twitter.com/account/verify_credentials.xml?source=twitterfx"
method: HttpRequest.GET
}
request.enqueue();
} catch (e:Exception) {
println("WARNING: {e}");
}
}
|
Notice that the function uses the twitterfx.twitter.UpdateRequestHandler class. The
UpdateRequestHandler class extends the javafx.io.http.HttpRequest
class, Java FX's class for invoking RESTful web services. These extensions involve adding user credentials
and an authorization header to the Twitter request. Here is the code in UpdateRequestHandler that does that:
import javafx.io.http.HttpHeaders;
import javafx.io.http.HttpRequest;
public class RequestHandler extends HttpRequest {
init {
var credentials = Base64.encodeBytes("{Main.twitterConfig.username}:{Main.twitterConfig.password}".getBytes());
setHeader(HttpHeaders.AUTHORIZATION, "Basic {credentials}")
}
|
|
"A lot of the application's features combine Java technology and JavaFX, using JavaFX for the visual side of
things and then falling back to Java and its libraries and ecosystem for additional services."
Steven Herod
IT Architect
|
When you examine the TwitterFX source code, you will see that the application not only takes advantage of the richness
of the JavaFX platform but also leverages the platform's flexibility. An important aspect of this flexibility is the use
of Java technology with JavaFX. Significantly, you can use any Java library in a JavaFX application. Steven Herod, the
TwitterFX project lead, says that "a lot of the application's features combine Java technology and JavaFX, using
JavaFX for the visual side of things and then falling back to Java and its libraries and ecosystem for additional services."
An example of the visual side of things is the handling of the tweet entry panel. The panel is added to
the application's scene in Main.fx, the main class of the application. You can find Main.fx in the
twitterfx directory. Initially, the panel is unopened.
Here is the code in Main.fx that
adds the tweet entry panel to the scene:
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public var sendTweetPanel: SendTweetPanel = SendTweetPanel {
translateY: bind (mainScene.height - 30)
}
public var mainScene: Scene = Scene {
fill: Color.web("#232323")
width: 420
height: 65
content: [
backgroundImageView,
Group {
content: [ friendTimelinePanel, repliesTimelinePanel, directTimelinePanel, favesTimelinePanel,
publicTimelinePanel , followersPanel, friendPanel, addGroup, homePanel, banWords]
},
topControlPanel, sendTweetPanel, currentUserPanel, welcomePanel, networkErrorPanel,toolTip]
}
stage = Stage {
x: twitterConfig.startX;
y: twitterConfig.startY;
resizable: true
title: "TwitterFX (0.30j)"
scene: mainScene
width: twitterConfig.startWidth;
height: twitterConfig.startHeight;
onClose: function() { FX.exit()
}
}
|
Notice that the value of the sendTweetPanel variable is a SendTweetPanel object.
The SendTweetPanel class implements and manages the tweet entry panel. You can find the
SendTweetPanel class in the twitterfx\panels directory.
When a user clicks the tweet entry icon, it invokes the toggleTweetPanel() function in
SendTweetPanel. If the tweet entry panel is not opened, as indicated by an isRevealed
boolean value of false, the function calls the showTweetPanel() function to open the panel.
Here are what these functions look like:
public function showTweetPanel():Void{
if (not isRevealed)
{
timeline.rate = 1.0;
timeline.play();
}
tweetBox.requestFocus();
isRevealed = true;
}
public function toggleTweetPanel():Void{
if (isRevealed)
hideTweetPanel()
else
showTweetPanel()
}
|
Notice the use of animation in the showTweetPanel() function. The SendTweetPanel class
is a JavaFX custom node that supports animation. The animation occurs along a timeline represented by a
javafx.animation.Timeline object. The timeline contains a number of key frames, represented by
javafx.animation.KeyFrame objects, as follows.
import javafx.animation.Timeline;
import javafx.animation.Interpolator;
var timeline:Timeline = Timeline {
rate: -1.0
keyFrames: [
at (0s) {
animationY => 0.0 tween Interpolator.LINEAR;
},
at (0.25s) {
animationY => -95.0 tween Interpolator.LINEAR;
}
]
}
|
The key frame values specify the scene's state at two points in time relative to the start of the timeline: 0 seconds
and .25 seconds. The tween operator is a literal constructor for an interpolated value. In other words,
the interim states of the scene are dynamically calculated. The vertical position of the tweet entry panel starts
at a relative position of 0 and moves vertically 95 pixels.
|
"Writing JavaFX Script is a pleasant experience,
it's very simple to pick up, and I feel the declarative aspect makes putting together complex components in the UI very
straightforward."
Steven Herod
IT Architect
|
The timeline.rate value specifies the
direction and speed of the animation. A positive value means play forward, and a negative value
means play in reverse. The timeline.play() method plays the timeline.
When the timeline is played, the showTweetPanel() function sets the timeline.rate value
to 1.0, and the tweet entry panel slides open. When a user clicks the tweet entry icon again, it invokes
the hideTweetPanel() function, which sets the timeline.rate value to -1.0. This
reverses the direction of the animation, and the tweet entry panel slides closed.
Steven says that "writing JavaFX Script is a pleasant experience,
it's very simple to pick up, and I feel the declarative aspect makes putting together complex components in the UI very
straightforward."
He adds that "another powerful feature is bind. Almost all of the state change behaviors
in TwitterFX are done with bind, so panel reveals, text highlighting, image swapping (red/grey icons), tooltips, tweet
character countdowns are all implemented with bind. It's a very powerful capability, and it's very simple to use."
The character countdowns that Steven mentions refer to the character countdown counter in the tweet entry panel.
The counter counts down from a starting value of 139 and subtracts 1 for each character that a user enters in the panel.
The value of the counter is the large grey number shown in the tweet entry panel in Figure 2.
The counter is implemented by a standard JavaFX Text component whose position is managed by binding to the scene
width minus the width of the component. This is shown in the following code snippet:
import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;
public var tweetBox:TweetTextComponent = TweetTextComponent{
translateY: 5;
width: bind (scene.width - 120)
};
var countText:Text =
Text {
translateX: bind (scene.width - (countText.layoutBounds.width + 40))
translateY: 70
textOrigin: TextOrigin.BOTTOM
// textAlignment: TextAlignment.RIGHT
font: Font {
name: "Lucida Grande"
size: 45
}
content: bind (
tweetBox.charsToGo.toString())
fill: bind
if (tweetBox.tooLong) Color.PINK else Color.LIGHTGRAY
}
|
Notice that the counter's text content is bound to tweetBox.charsToGo.toString(). The tweetBox
variable is a TweetTextComponent -- a Swing JTextArea component. You can find
TweetTextComponent in the twitterfx\components directory. Notice too that the
color of the text is bound to the tweetBox.tooLong property. The property is set to true
when the countdown value goes below 0. At that point, the counter text changes from light grey to pink.
Steven notes that "JavaFX currently lacks its own text components (which are coming in later versions), but
in the meantime you can use standard Swing components. Inserting a plain JTextArea in its traditional
square form is no fun in the new RIA world, so we make it a bit more fun by wrapping a JTextArea in
JavaFX (TweetTextComponent) and then placing it in a Group along with a shaped rectangle
and its own gradient fill to give our TextArea the appropriate visual appeal."
Here is some of the code in SendTweetpanel that does the grouping:
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.shape.Rectangle;
var tweetPanel:Group = Group {
translateY: 30
content: [
Rectangle {
translateX:10
width: bind (scene.width - 40)
height: 70
fill: LinearGradient {
startX: 0.0,
startY: 0.0,
endX: 0.0,
endY: 1.0
proportional: true
stops: [ Stop {
offset: 0.0
color: Color.web("#CCCCCC") },
Stop {
offset: 0.32
color: Color.WHITE }]
}
arcHeight: 15
arcWidth: 15
}, tweetBox, countText,spellCheck,
...
|
The flexibility and functionality of JavaFX give developers the power to develop content and
media-rich applications such as TwitterFX and make it fun and easy.
Not only is the JavaFX community actively creating JavaFX applications, but some members of the community are also
creating JavaFX components that can be used in JavaFX applications. A good example of this initiative is the work
of Mauricio Aguilar, the project owner of memefx.
The objective of the open-source memefx project is to provide rich components for the JavaFX platform.
For example, you can find components in memefx for various types of gauges and control knobs, as well as a
stage controller component that you can use to attach some automatic behaviors to a stage,
that is, the top-level container, or window, for a JavaFX application. You can use the stage
controller to make the window stick to the edges of the screen -- the window returns to the edge if moved. Or you
can set the window to the maximum screen width or height, or even animate the window.
Mauricio's says that his motivation for the project was "to do useful things while learning JavaFX, and at the
same time show other developers how easy it is to do useful and cool things with JavaFX. In other words,
I wanted to show others that JavaFX not only has potential but is already a powerful tool."
One of the other interesting components in memefx is ImagesAccordion.
This component allows you to open images in an accordion style from a visual menu. The memefx project includes
a demonstration application that uses the ImagesAccordion component as well as some other
memefx components. You can find demonstrations of all the memefx components on the
memefx project page.
When you launch the ImageAccordion demo, it displays a visual menu of image slices, as shown in
Figure 3.
Figure 3. Picture Slices in a Visual Menu
Click the image to enlarge it.
|
Click or mouse over any image slice in the visual menu and the full image with descriptive text slides open,
as shown in Figure 4.
Figure 4. An Opened Picture and Text
Click the image to enlarge it.
|
Let's examine some of the code in the ImageAccordion demo application, especially to see how it uses
memefx components.
Exploring the Code
Here is the code in the ImageAccordion demo that sets the initial scene:
import javafx.stage.Stage;
import javafx.scene.Scene;
import org.memefx.accordion.*;
import org.memefx.stage.*;
/** Stage controller */
var cs = ControlStage {
width: compoWidth + 120.0,
height: compoHeight + 150.0
initialPosition: ControlStage.CENTER
checkMinWidth: true,
minWidth: compoWidth + 120.0,
checkMinHeight: true,
minHeight: compoHeight + 150.0
persistPosition: true,
persistFile: "accordiondemo.per"
stickyBorders: true,
limitBorders: true
};
Stage {
// bind Stage (window) to the controller
width: bind cs.width with inverse
height: bind cs.height with inverse
x: bind cs.x with inverse
y: bind cs.y with inverse
// store persistent position and size
onClose: function() {
cs.persistStage()
};
scene: Scene {
content: [
accordion,
Text {
font: Font {
size: 12,
oblique: true
}
x: bind cs.width - 140,
y: bind cs.height - 52
content: "by Mauricio Aguilar"
}
]
}
}
|
Notice that various Stage and Scene object variables are bound to variables in
the ControlStage object. The ControlStage class is the previously mentioned
memefx stage controller component. The stage controller sets the position of the stage and the
position of the text content in the scene. It also persists, that is, stores in a local file
named accordiondemo.per, the position and size of the stage. This initializes the
position and size of the stage for the next launch of the application.
The value of the accordion variable in the scene content is an ImagesAccordion
object. Here's a snippet of code that defines the accordion variable:
var accordion = ImagesAccordion {
width: compoWidth,
height: compoHeight,
orientation: compoOrientation
translateX: 50,
translateY: 50
// selectOnPressed: true,
// hideOnExit: false
onAccordionExited: function() {
println("EXITED");
}
images: [
ImageItem {
id: "moais",
caption: "Moais"
image: Image { url: "{__DIR__}moais.jpg"
}
message: "Easter Island (Rapa Nui) is a Polynesian island in the "
"southeastern Pacific Ocean, The island is a special "
"territory of Chile. Easter Island is famous for its "
"monumental statues, called moai."
messageArea: Rectangle2D {
minX: 30,
minY: 253,
width: 350,
height: 87
}
onMouseEntered: enter
onMousePressed: click
},
...
|
The ImagesAccordion class is a custom node whose images variable specifies a sequence
of ImageItems -- the ImageItem class is yet another memefx component. Each
ImageItem object specifies an identification, caption, image, message string, and rectangular
message area.
The accordion effect in the application is handled by the ImagesAccordion component in concert with the
AccordionItem component. The following code is where the significant action takes place in the
ImagesAccordion component:
/** active imageitem index (mouseover image) */
public-read var active: Integer on replace {
/** triggers recalculation of images position.
* workaround: deferAction used to avoid a random image flicker */
FX.deferAction(function():Void {
for (item in pictures) {
item.adjust();
};
});
};
/** sequence containing the accordion items */
public var images: ImageItem[] on replace {
...
// scan items to add elements to the accordion
for (image in images) {
insert
AccordionItem {
// owner of this item... the accordion
owner: this
// item information (image, caption, etc)
item: image
// position of the item in the accordion
imagePos: indexof image
// Entering event
onMouseEntered: function(e) {
// if selected on mouse entered
if (not selectOnPressed) {
// will set the item as the active item
activeItem=image;
// will set the active item with this imageitem index
active=indexof image;
// will call function associated to onMouseEnter the image
image.onMouseEntered(image);
}
// will increase the number of active mouse entered
count++;
}
...
// Click event
onMousePressed: function(e) {
// if select on mouse pressed
if (selectOnPressed) {
// will set the item as the active item
activeItem=image;
// will set the active item with this imageitem index
active=indexof image;
}
// will call function associated to onMousePressed the image
image.onMousePressed(image);
}
} into pictures;
};
...
|
For each image in the ImageItem sequence, a replace trigger inserts a corresponding
AccordionItem object into the ImagesAccordion object. Each AccordionItem object implements an image
in the accordion. When a user mouses over or clicks on an image, a trigger invokes the adjust() function
for the associated AccordionItem.
The adjust() function is in the AccordionItem class. Here is a snippet of the code in the function:
import javafx.animation.*;
/** calculate self horizontal position
* and animates transition*/
public function adjust() {
...
// if there's an item selected in the accordion
else {
// all the pieces with minimum width
posX = owner.minWidth * imagePos;
// if this item is located after the selected item
if (imagePos > owner.active) then {
// adds to its horizontal position the available space for
// the selected item minus the minimum space for that item
posX += owner.availableWidth - owner.minWidth
};
};
// if it will change it horizontal position
if (posX != startX) {
// creates and executed an animated transition (workaround)
timeline = Timeline {
repeatCount: 1
keyFrames: [
KeyFrame {
time: 0s
values: [currentX => startX]
}
KeyFrame {
time: owner.timeToExpand
values: currentX => posX tween owner.interpolator
canSkip: true
}
]
};
timeline.playFromStart();
}
|
The adjust() function handles the positioning of the images when an image is selected and
animates the accordion effect. When an image is selected, the function repositions all the images except
the selected image. It does the repositioning based on the minimum image width initially established in the stage controller.
The function allocates the maximum available space for the selected image and factors that width into the position of images
that follow the selected image.
The selected image expands into its maximum space within a time frame set by the
owner.timeToExpand value. The owner variable value is an ImagesAccordion object.
Notice that the key frame values for the animation are interpolated by owner.interpolator. This interpolator
is provided by a memefx component named SpringInterpolator.
Here are the settings in the ImagesAccordion object for owner.timeToExpand
and SpringInterpolator
import org.memefx.interpolator.SpringInterpolator;
/** time to expand selected image */
public var timeToExpand = 0.5s;
/** interpolator applied on items movement */
public var interpolator:Interpolator = SpringInterpolator {
mass: 2.0,
bounce: true,
stiffness: 10
};
|
|
"I love JavaFX Script not just because it allows me to create cool pretty things that used to be very difficult
with raw Java code, but because I'm so productive with it."
Mauricio Aguilar
Project Owner, memefx
|
The memefx components add to the rich set of components available in the JavaFX platform. The components are
open source and available for use in JavaFX applications. This underscores the vital role that the JavaFX community
plays in the extending the JavaFX platform.
Mauricio says that he really loves JavaFX Script "not just because it allows me to create cool pretty things
that used to be very difficult with raw Java code, but because I'm so productive with it. The ImagesAccordion
component took me just a couple of hours of work from concept to deployed example on the Web (one afternoon) --
something unimaginable using just Java code. And that's including all kinds of configurable settings and nice visual
effects."
Mauricio adds that "you need to change a couple of paradigms about how you develop your code to understand the
benefits and possibilities offered by JavaFX Script programming, but it 's really worth the effort. I just started to
learn JavaFX in January. I've been able to accomplish pretty impressive things with it since day one."
One of the interesting application areas for JavaFX is widgets, lightweight applications or applets
that provide simple utility functions. Typical examples of widgets are clocks or calendars that run as
desktop accessories. WidgetFX is an open-source platform that makes
it easy to create JavaFX widgets and run them on the desktop. The platform, which is being developed in the
widgetfx project, was initially released in January 2009
and now is at the version 1.1 level. The lead developer on the project is Stephen Chin, a senior manager at Inovis,
a provider of solutions for business community management.
|
Launch WidgetFX
|
Stephen says that the WidgetFX project "grew out of the need for a quality, open-source, cross-platform desktop
widget framework for enterprise Java developers." Although there are other frameworks available for consumer
desktop widgets, Stephen says that "the majority of them are tied to JavaScript and HTML, which means they lack full
desktop integration support and have significant security issues. This, along with very restrictive licensing, prevents
them from being adopted within large enterprises, which limits their commercial use."
Stephen adds that "in contrast, WidgetFX runs on all major platforms including Windows XP/Vista, Linux,
and Mac OS X, and has a robust security model that leverages the secure sandbox of the Java platform. Also, WidgetFX
is designed to be on the front end of a rich desktop application (RDA) movement by providing a very rich library of visual,
animation, and media capabilities."
As part of its platform, WidgetFX provides a dock for displaying available widgets.
Figure 5 shows the dock with a number of widgets created with WidgetFX. You can drag and drop any
widget from the dock to the desktop and back. For example, Figure 6 shows a clock widget after
it is dragged to the desktop from the dock.
Figure 5. Widgets in the WidgetFX Dock
Click the image to enlarge it.
|
|
Figure 6. Widget Dragged to the Desktop From the WidgetFX Dock
|
|
The key WidgetFX downloadable for developers is the
WidgetFX 1.1.1 SDK. The SDK includes
a JAR file that provides the WidgetFX classes, Javadoc documentation for the WidgetFX 1.1 API, and sample code -- in this case,
a NetBeans project for a calendar widget. The SDK also includes a JAR file that provides utility classes created in the
JFXtras project. JFXtras is another JavaFX-related
open-source project that Stephen Chin leads. The objective of the JFXtras project is to build utilities
and other add-ons to the JavaFX platform.
The primary class in the WidgetFX API is org.widgetfx.Widget. This is the class you need to implement to build
a JavaFX-based widget. The API also includes a package of widget-related property classes,
org.widgetfx.config. One of the important classes in the package is
org.widgetfx.config.Configuration, which configures a widget to do things such as show a configuration dialog,
save properties to disk, and load properties on startup.
Let's examine some of the code in the sample calendar widget and see how it uses WidgetFX classes.
Exploring the Code
Here is the code in the calendar widget that sets the scene:
import org.widgetfx.*;
import org.widgetfx.config.*;
import javafx.scene.control.*;
def defaultWidth = 180.0;
def defaultHeight = 200.0;
def widget:Widget = Widget {
width: defaultWidth
height: defaultHeight
aspectRatio: defaultWidth / defaultHeight
configuration: config
skin: Skin {
scene: scene
}
}
return widget;
|
Notice that the calendar widget returns a Widget object. The main file for a WidgetFX widget
must return a Widget object. The requirement to return a Widget object follows
the model of a JavaFX applet, which returns a Stage class from its main class. Also note that although
all the code for this widget is in one main script file, most widgets -- especially more complicated widgets -- have
their own class file.
The Widget
class supports properties specific to widgets such as those that control resizing, configuration, and aspect ratio,
that is, the ratio of the object's width to its height. The Widget class also contains event-handler
callbacks for resizing and docking operations. The code snippet above sets the width and height of
the Widget object to 180 and 200 pixels, respectively, and sets the aspect ratio. The aspect ratio stays
constant even when the widget is resized.
Notice too that the code makes no explicit reference to the JavaFX Scene class. That's because the
Widget class extends the JavaFX Control class and sets the scene through the
Control class's skin variable. The skin variable value is a JavaFX
Skin object that renders the control. The Widget object sets the Skin's
scene variable -- this establishes the root of the scene graph used to render the Skin,
and by extension, the root of the scene graph used to render the UI control.
Here are the scene value settings:
import javafx.scene.*;
import javafx.scene.transform.*;
def scene:Group = Group {
def arcHeight = defaultHeight / 20;
def offset = defaultWidth / 25;
transforms: bind Transform.scale(
widget.width / defaultWidth,
widget.height / defaultHeight);
// scale down by 1% to leave room for stroke width
scaleX: .99
scaleY: .99
content: [
for (i in reverse [0..3]) {
createPage(offset*i/3, offset*i/3 + arcHeight,
defaultWidth - offset,
defaultHeight - offset - arcHeight)
},
createSpiral(defaultWidth - offset, arcHeight),
createPageContents(0, arcHeight * 2,
defaultWidth - offset,
defaultHeight-offset-arcHeight*2)
]
}
|
The scene value is a JavaFX Group object. Notice how it uses a JavaFX Transform
object to scale to fit the size of the window. The Group's content uses functions defined in the calendar
widget to create the parts of the calendar.
The createPage() function creates the calendar pages, the createSpiral() function
creates the spiral binding at the top of the calendar, and the createPageContents() function
create the day and date text on each calendar page. All of these functions use JavaFX classes to perform
their actions.
For example, the createSpiral() function uses the Arc class to
create the spiral binding. Here is what the createSpiral() function looks like:
import javafx.scene.paint.*;
import javafx.scene.shape.*;
function createSpiral(width:Number, arcHeight:Number) {
def numArcs = 20;
for (i in [1..numArcs]) {
var arcSpacing = width / (numArcs + 2);
Arc {
centerX: arcSpacing * (i + 1)
centerY: arcHeight
radiusX: arcHeight * 2 / 3
radiusY: arcHeight
startAngle: 0
length: 230
stroke: Color.BLACK
fill: null
}
}
}
|
One of the interesting features of the calendar widget is that you can set its locale through the UI. If you click the
wrench icon in the calendar widget's toolbar, it displays a dialog box to set the locale, as shown in
Figure 7.
Figure 7. Setting the Calendar Locale
|
The calendar widget uses the WidgetFX Configuration object to manage the locale setting. Here is where
that processing takes place in the calendar widget code.
import org.widgetfx.config.*;
import org.jfxtras.scene.layout.*;
import org.jfxtras.scene.layout.GridConstraints.*;
import javafx.ext.swing.*;
def config = Configuration {
properties: [
StringProperty {
name: "language"
value: bind language with inverse
},
StringProperty {
name: "country"
value: bind country with inverse
},
StringProperty {
name: "variant"
value: bind variant with inverse
}
]
var locales = Locale.getAvailableLocales();
var localePicker = SwingComboBox {
items: for (l in Arrays.asList(locales)) {
SwingComboBoxItem {
selected: l == locale
text: l.getDisplayName()
value: l
}
}
}
scene: Scene {
content: Grid {
rows: row([
Text {content: "Locale:"},
localePicker
])
}
}
onSave: function() {
var l = localePicker.selectedItem.value as Locale;
language = l.getLanguage();
country = l.getCountry();
variant = l.getVariant();
}
onLoad: function() {
localePicker.selectedIndex =
Sequences.indexOf(locales, locale);
}
}
|
The properties variable contains a sequence of property settings that persist to the next
start of the widget. Here, the property settings are for the choice of language, country, and variant.
The variant property value is a platform-specific code such as WIN for Windows
or MAC for Macintosh. The scene variable defines the configuration dialog,
which in this case uses the Grid container to position and size a JavaFX SwingComboBox
object. The Grid container is a utility provided by the JFXtras project. This positioned and sized
SwingComboBox is the dialog that pops up when you click on the wrench icon in the calendar
widget's toolbar.
After you build a WidgetFX widget, you need to create a JNLP file that refers to the widget's
compiled class. For example, here is the reference to the calendar widget class in the sample widget's JNLP file:
<application-desc main-class="org.widgetfx.widget.calendar.CalendarWidget">
</application-desc>
|
You can then deploy the widget by pointing your browser to
http://widgetfx.org/dock/launch.jnlp?arg=<widgetURL>
|
where <widgetURL> is the URL of the widget's JNLP file.
This automatically launches WidgetFX and adds the widget to the dock. For example, assuming that the JNLP file URL
for the calendar widget is
http://mywidget/Calendar.jnlp, the following URL launches WidgetFX and adds the calendar widget to the
dock:
http://widgetfx.org/dock/launch.jnlp?arg=http://mywidget/Calendar.jnlp
|
|
"Once you get used to the new bind, trigger, and animation support, it is hard
to go back to the old style of imperative, event-driven UI programming."
Stephen Chin
Senior Manager, Inovis
|
WidgetFX also provides a standalone widget runner that allows you
to run a widget outside of the dock for testing. If you launch the widget's JNLP file, it automatically starts the widget runner.
WidgetFX is a good example of the creativity of the JavaFX community. Stephen says that not only does JavaFX
provide a platform for creativity and productivity, but that the declarative programming approach in JavaFX
can be quite addictive. "Once you get used to the new bind, trigger, and animation support, it is hard
to go back to the old style of imperative, event-driven UI programming."
He also envisions a growing need for what JavaFX offers. "In the long term, developers will demand the
productivity benefits and business will demand the rich user-interface technology that JavaFX provides."
This article highlighted some of the interesting applications developed by the JavaFX community.
But there are many more. Here are some good places to find cool JavaFX applications, sample programs, and demonstrations:
Do you have comments about this article? We welcome your participation in our community. Please keep your comments civil
and on point. You may optionally provide your email address to be notified of replies - your information is not used for
any other purpose. By submitting a comment, you agree to these
Terms of Use.
|
Ed Ort is a writer on the staff of the Sun Developer Network. He has written extensively about a wide variety of programming topics including relational database technology, programming languages, web services, and Ajax. Read his blog.
|
|