One of my favorite Saturday Night Live sketches is More Cowbell, in which Christopher Walken's character keeps asking for "more cowbell" during a recording session. Today's example covers some of the simple but powerful concepts of JavaFX in the context of an imaginary iPhone-esque application that lets you select a music genre and control the volume. Of course, "Cowbell Metal", shortened to "Cowbell", is one of the available genres :-) Click the screenshot below to launch the application, and then I'll show you the code behind it.
Application Behavior and the Code Behind It
When you play with the application, notice that adjusting the volume slider changes the associated decibel (dB) level displayed. Also, selecting the Muting checkbox disables the slider, and selecting various genres changes the volume slider. This behavior is enabled by concepts that you'll see in the code below, such as binding to a class that contains a model, on replace triggers, and sequences (think arrays).
Here is the main program, which contains the declarative script that expresses the UI:
/*
* AudioConfigMain.fx - A JavaFX Script example program that demonstrates
* "the way of JavaFX" (binding to model classes, triggers, sequences, and
* declaratively expressed, node-centric UIs). Note: Because this example
* covers beginning JavaFX concepts, it is more verbose than necessary.
*
* Developed 2008 by James L. Weaver jim.weaver [at] javafxpert.com
* as a JavaFX Script SDK 1.0 example for the Pro JavaFX book.
*/
package projavafx.audioconfig.ui;
import javafx.ext.swing.*;
import javafx.stage.Stage;
import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.text.*;
import javafx.scene.transform.*;
import projavafx.audioconfig.model.AudioConfigModel;
Stage {
var acModel = AudioConfigModel {
selectedDecibels: 35
}
title: "Audio Configuration"
scene: Scene {
content: [
Rectangle {
x: 0
y: 0
width: 320
height: 45
fill: LinearGradient {
startX: 0.0
startY: 0.0
endX: 0.0
endY: 1.0
stops: [
Stop {
color: Color.web("0xAEBBCC")
offset: 0.0
},
Stop {
color: Color.web("0x6D84A3")
offset: 1.0
},
]
}
},
Text {
translateX: 65
translateY: 12
textOrigin: TextOrigin.TOP
fill: Color.WHITE
content: "Audio Configuration"
font: Font {
name: "Arial Bold"
size: 20
}
},
Rectangle {
x: 0
y: 43
width: 320
height: 300
fill: Color.rgb(199, 206, 213)
},
Rectangle {
x: 9
y: 54
width: 300
height: 130
arcWidth: 20
arcHeight: 20
fill: Color.color(1.0, 1.0, 1.0)
stroke: Color.color(0.66, 0.67, 0.69)
},
Text {
translateX: 18
translateY: 69
textOrigin: TextOrigin.TOP
fill: Color.web("0x131021")
content: bind "{acModel.selectedDecibels} dB"
font: Font {
name: "Arial Bold"
size: 18
}
},
SwingSlider {
translateX: 120
translateY: 69
width: 175
enabled: bind not acModel.muting
minimum: bind acModel.minDecibels
maximum: bind acModel.maxDecibels
value: bind acModel.selectedDecibels with inverse
},
Line {
startX: 9
startY: 97
endX: 309
endY: 97
stroke: Color.color(0.66, 0.67, 0.69)
},
Text {
translateX: 18
translateY: 113
textOrigin: TextOrigin.TOP
fill: Color.web("0x131021")
content: "Muting"
font: Font {
name: "Arial Bold"
size: 18
}
},
SwingCheckBox {
translateX: 280
translateY: 113
selected: bind acModel.muting with inverse
},
Line {
startX: 9
startY: 141
endX: 309
endY: 141
stroke: Color.color(0.66, 0.67, 0.69)
},
Text {
translateX: 18
translateY: 157
textOrigin: TextOrigin.TOP
fill: Color.web("0x131021")
content: "Genre"
font: Font {
name: "Arial Bold"
size: 18
}
},
SwingComboBox {
translateX: 204
translateY: 148
width: 93
items: bind for (genre in acModel.genres) {
SwingComboBoxItem {
text: genre
}
}
selectedIndex: bind acModel.selectedGenreIndex with inverse
}
]
}
}
Notice how the bind operator is used in various places to cause the UI to reflect the state of the model. In a couple of places, a bind with inverse is employed to keep the UI and the model class in sync bi-directionally. Now take a look at the model class, and in particular the on replace trigger that is invoked when the user selects a genre:
/*
* AudioConfigModel.fx - The model class behind a JavaFX Script example
* program that demonstrates "the way of JavaFX" (binding to model classes,
* triggers, sequences, and declaratively expressed, node-centric UIs).
*
* Developed 2008 by James L. Weaver jim.weaver [at] javafxpert.com
* as a JavaFX Script SDK 1.0 example for the Pro JavaFX book.
*/
package projavafx.audioconfig.model;
/**
* The model class that the AudioConfigMain.fx script uses
*/
public class AudioConfigModel {
/**
* The minimum audio volume in decibels
*/
public var minDecibels:Integer = 0;
/**
* The maximum audio volume in decibels
*/
public var maxDecibels:Integer = 160;
/**
* The selected audio volume in decibels
*/
public var selectedDecibels:Integer = 0;
/**
* Indicates whether audio is muted
*/
public var muting:Boolean = false;
/**
* List of some musical genres
*/
public var genres = [
"Chamber",
"Country",
"Cowbell",
"Metal",
"Polka",
"Rock"
];
/**
* Index of the selected genre
*/
public var selectedGenreIndex:Integer = 0 on replace {
if (genres[selectedGenreIndex] == "Chamber") {
selectedDecibels = 80;
}
else if (genres[selectedGenreIndex] == "Country") {
selectedDecibels = 100;
}
else if (genres[selectedGenreIndex] == "Cowbell") {
selectedDecibels = 150;
}
else if (genres[selectedGenreIndex] == "Metal") {
selectedDecibels = 140;
}
else if (genres[selectedGenreIndex] == "Polka") {
selectedDecibels = 120;
}
else if (genres[selectedGenreIndex] == "Rock") {
selectedDecibels = 130;
}
};
}
As always, please leave a comment if you have any questions!
Learn JavaFX in Stockholm, Sweden in January 2009
I'll be speaking on JavaFX at the Jfokus 2009 conference in January. While in the area, I will also be conducting a two day JavaFX class in Stockholm entitled "Rich Internet Application Development with JavaFX". I'll be teaching this two day class for Informator beginning January 29, 2009.
Regards,
Jim Weaver
JavaFXpert.com
"I don't see anything about allowing Java classes to use compiled JavaFX classes just as easily as any other Java class, though, and this is -- as I mentioned -- when we would see a quantum leap in benefit for Java and JavaFX.
Do you know of any news on that front??"
Ari,
I don't know of any news on that front, but the way that I call JavaFX from Java code is by creating Java interfaces (method #1 in the following blog post).
http://blogs.sun.com/michaelheinrichs/entry/using_javafx_objects_in_java
Thanks,
Jim Weaver
Posted by: Jim Weaver | April 03, 2009 at 04:03 PM
Thanks for responding so quickly!
I checked this out, and am glad to hear it -- I like some of the planned changes. I don't see anything about allowing Java classes to use compiled JavaFX classes just as easily as any other Java class, though, and this is -- as I mentioned -- when we would see a quantum leap in benefit for Java and JavaFX.
Do you know of any news on that front??
Thanks very much,
Ari
Posted by: Ari Goldstein | April 03, 2009 at 03:44 PM
"I know -- but I may not need this enum in my Java code. I'd rather stay with the rapidity of the scripting language where possible, and save Java for the heavy lifting..."
Ari,
This was submitted as a desired feature by Martin Brehovsky (the JavaFX Production Suite guy), and comment on by Brian Goetz (JavaFX compiler team lead). See:
http://javafx-jira.kenai.com/browse/JFXC-1770
Thanks,
Jim Weaver
Posted by: Jim Weaver | April 03, 2009 at 03:21 PM
I know -- but I may not need this enum in my Java code. I'd rather stay with the rapidity of the scripting language where possible, and save Java for the heavy lifting.
This is mainly because I feel that Java and JavaFX are not really playing together well enough yet to intermingle them freely. When I can easily call JavaFX classes from Java, gaining from their binding capabilities and easy declaration (or if you want to give me "bind" and "property" keywords in java), THEN the ease of development of JavaFX will make it into Java, and I will mix-and-match to my heart's content.
Best,
Ari
Posted by: Ari Goldstein | April 03, 2009 at 03:12 PM