Menu Pilihan:
Intro to JavaFX - Dummy Chess
Introduction
Sun recently released a new programming language for the Java platform called JavaFX. It's primary purpose is to make it easier to develop rich internet applications (RIAs) that can run on a variety of devices including PCs, mobile phones, and Blu-ray players. It is most often compared to the new RIA languages from Microsoft (Silverlight) and Adobe (AIR). JavaFX is not limited to creating RIAs. In this article, I develop a Chess application that runs on the desktop. I call it dummy chess because the algorithm I used just selects a move at random. So if you can't beat this chess program, then you are a worse player than me. And that's pretty bad. Perhaps one day I'll write a better chess playing program and post an article called smart chess.
Background
I have been programming in Java for over 5 years and recently decided to teach myself JavaFX. I figured the best way to learn JavaFX would be to assign myself a programming project on a topic I'm interested in and to complete the project in the new language. So over the course of about a month I wrote a Chess program written in JavaFX. This article describes my learning experience and introduces the basics of programming in JavaFX.
I was expecting JavaFX to be just like Java only with a new set of framework classes to learn. I was wrong. JavaFX is an entirely different programming language. In order to help the experienced Java programmers who are reading this article, I have put in some code translations from JavaFX to Java to help you better understand the sample JavaFX code posted in this article.
Using the Code
I use IDEs at work (Eclipse and Visual Studio) and really enjoy working with them. For this project, however, I decided not to use an IDE. I like working on my VI skills whenever I can. The following list describes the downloads that you will need depending on whether you are using the command line or an IDE:
- In order to build the Chess program from the command line, you must download the JavaFX 1.1 SDK.
- If you prefer to work in an IDE, you can download the NetBeans IDE 6.5 for JavaFX 1.1.
- If you prefer Eclipse to NetBeans, there is a plugin available.
- In order to convert SVG to JavaFX graphics, you also need to download the JavaFX 1.1 Production Suite.
All of these downloads are available from javafx.com except for the Eclipse plugin which is available from kenai.com. I have never used NetBeans or Eclipse to build a JavaFX program, so I am not sure how good either of them works.
The source code files contain both Java 1.6 and JavaFX 1.1 source code files. So you will also need a Java 1.6 compiler to build the code. NetBeans and Eclipse will already come with the Java compiler, if you are building from the command line you will need to download the latest JDK from java.sun.com.
In order to build the source code from the command line and run the Chess program, enter the following from the command line (do this from the directory that contains the source code):
Compiling and Running the Chess Program
javac *.java
javafxc *.fx
javafx Main
javac
is the command line compiler that compiles the Java source code files, javafxc
is the command line compiler that compiles the JavaFX source code files, and javafx
runs the Chess program by running the specified JavaFX file (in this case it starts running the code compiled from the file Main.fx).
Points of Interest
JavaFX is a very young programming language and it definitely felt like a beta product. I've never found so many bugs in a programming language before, and I only used it for a month writing one application. I found workarounds for some of the bugs but not all of them. I never was able to change the icon for the application.
The language does have a lot going for it and hopefully these bugs will get resolved in time as the product matures. I really like the binding feature and found creating a GUI (Graphical User Interface) to be much easier compared to when I was first learning how to make GUIs in Java. Java is a very mature language (it's been around for over a decade) and a lot of code has been written for Java. It is nice to be able to instantiate this large library of existing code Java directly from JavaFX code.
If you do decide to take the plunge and do some JavaFX programming, be aware that a lot of the sample JavaFX code that I found on Google does not work in the latest version of JavaFX (v. 1.1). JavaFX must have changed pretty significantly since around mid 2008. I found that most articles published prior to mid 2008 did not work in version 1.1, while most code published in late 2008 and 2009 seems to work just fine.
Let's Go!
Let's start with a simple Hello World
program and build our chess program from there. As you'll soon see, you actually learn quite a bit from the Hello World
program. Here is the code for Hello World
in JavaFX:
Hello World Program Written in JavaFX
println("Hello World!");
That's it! One line of code. This simple one line program has a lot to teach us. The equivalent program in Java is:
Hello World Program Written in Java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
No Main or Class Needed
So what have we learned from this? Well if you are coming from a Java background, you are used to putting everything in classes. That's because in Java, no code can exist outside of a class. As you can see, this is not the case in JavaFX.
Further, in Java programs you must create a main
method that is declared public static void
that takes a single parameter String[] args
(the command line arguments). In JavaFX this is unnecessary. You simply place instructions in a JavaFX script file and run that file. No main
necessary.
If you need to access command line arguments, then you can do so using the special function called run
. run
is a special function that serves as a script's main entry point. The run
function looks like this:
"run" is an Optional Function that Acts as a JavaFX Script's Main Entry Point
function run(args: String[]) {
println("{args[0]}");
}
This program simply prints out the first command line argument. Surprisingly, it runs without error if no command line arguments are passed. It simply prints out a new line if no command line args are passed in.
Sequences vs Arrays
Let's talk about this run
function a little more. It takes a single parameter that is a sequence of String
objects called args
. Sequences in JavaFX look like arrays in Java. They are declared by placing brackets, []
, after the type. For example String[]
declares a sequence of Strings
. Sequences are immutable, just like arrays. However, you can write code to insert elements into a sequence and remove elements from a sequence like this:
You Can Add to and Remove from Sequences
var colors = ["Red"];
insert "Blue" into colors;
insert "Green" after colors[1];
insert "White" before colors[0];
delete "White" from colors;
delete colors[0];
println("{sizeof colors}");
Since sequences are immutable, Javafx ends up creating a new sequence when items are inserted into or removed from a sequence. This is similar to how Strings
work in Java. You can modify a String
in code, but since Strings
are immutable the runtime ends up creating a new String
for you if you modify a String
.
JavaFX has some neat ways of creating numeric sequences using the special ..
code. The following code gives several examples of building numeric sequences:
Examples of Creating Numeric Sequences in JavaFX
def seq1 = [1..10]; // seq1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def seq2 = [1..10 step 2]; // seq2 = [1, 3, 5, 7, 9]
def seq3 = [10..1 step -1]; // seq3 = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
The for
loop in JavaFX only works on sequences. It has the form for(varName in sequence) { ... }
. The following code shows how to convert from a simple Java for
loop to a JavaFX loop:
Differences Between Java and JavaFX "for" Loops
// Java for loop
// equivalent JavaFX for loop
for(i in [0..9]) {
println("Hello World!");
}
In JavaFX, you can use the indexof
keyword to get the index number of the item in the sequence. For example:
Looping Through a Numeric Sequence and Using the "indexof" keyword
for(i in [100..110 step2]) {
println("{indexof i} = {i}");
}
// Output
0 = 100
1 = 102
2 = 104
3 = 106
4 = 108
5 = 110
Variables and Parameters
You have now seen two different variable/parameter declarations in JavaFX code. The first was the args
parameter in the run
function (declared as type String[]
) and the second was the colors
variable in the sequence example (no type declaration). All variables and parameters must be declared in JavaFX, the type however is optional. If you don't declare the type of the variable, the runtime figures it out based on the type of object assigned to the variable. I personally prefer giving my variables a type and you can do this if you like by placing a :
after the name followed by the type. For example we can assign a type to our colors
variable like this:
Declaring a Variables Type is Optional
var colors: String[] = ["Red"];
Function parameters are simply declared with a variable name and an optional type. Variables are declared as either var
or def
. The difference between the two is that a var
can be modified while a def
cannot. A variable declared as a def
is similar to a variable declared as final
in Java. So the following is valid code:
Variables Defined as "def" Can be Modified
// valid code
var i = 7;
i = 2;
While the following will not compile:
Variables Defined as "def" Cannot be Modified
// invalid code, won't compile
def i = 7;
i = 2;
Function parameters cannot contain the var
or def
keywords. All function parameters are considered to be defs
and thus cannot be modified.
Functions
All functions must declare that they are a function by using the keyword function
. I found this requirement to be quite surprising. Scripting languages usually are known for their conciseness. This keyword seems unnecessary and it's a big word too, a whole 8 characters. It seems like the compiler should be able to tell a function when it sees it.
Every function has a return type. If you don't declare a return type, it defaults to Void
. That is not a typo, in Java void
is spelled with a lower case 'v', while in Javafx it is spelled Void
with a capital 'V'.
Function return types are placed similarly to types in variables and parameters. We declare the return type by placing a :
after the closing parenthesis followed by the return type. For example the following function returns an Integer
:
A "function" in JavaFX that Returns an "Integer"
function foo(i: Integer): Integer {
i * 4;
}
Built-in Data Types
As you saw in the last code example, the keyword Integer
represents an integer. In Java this is equivalent to an int
. If you need a decimal, you can use the built in type Number
which represents a floating point number. Usually these will be the two numeric types that you will use, but if you need variables of a particular size you can use any of the following built in numeric types: Byte
, Short
, Number
, Integer
, Long
, Float
, Double
, and Character
(all capitalized in the first letter).
Other built in data types are Boolean
which is just like bool
in Java, Duration
which is a duration of time, and String
. When assigning to a Duration
type, you basically give the amount of time followed by the time units. For example:
Different "durations" in JavaFX
100ms; // equal to 100 milliseconds
200s; // 200 seconds
300m; // 300 minutes
400h; // 400 hours
Strings
work similarly in JavaFX and Java. JavaFX String
objects have the same methods as Java String
objects but concatenating Strings
works differently. Here are some examples that show String
operations in Java followed by the equivalent operations in JavaFX:
"String" Concatenations in Both Java and JavaFX
// Java code
String strA = "A";
String strB = "B";
String strC = strA + strB;
String strD = strC.replaceAll("A", "C");
// equivalent JavaFX code
var strA: String = "A";
var strB: String = "B";
var strC: String = "{strA}{strB}";
var strD: String = strC.replaceAll("A", "C");
Expressions
This is the second time we have seen the user of {}
inside of a String
. When placed inside of a string {}
converts the enclosed expression into a String
. We saw this in the run
function where we printed out the first command line argument with the code println("{args[0]}")
. We also use this for concatenating Strings
as in the previous code example. We can place any expression inside of the {}
. For example, the following code prints The value of foo(7)is 49
:
Converting Expressions to "Strings" in JavaFX
function foo(i: Integer): Integer {
i * i;
}
def i = 7;
println("The value of foo({i}) is {foo(i)}");
The {i}
converts the value of i
to a String
while {foo(i)}
converts the result of foo(i)
to a String
. These are concatenated with the Strings
"The value of foo(", ") is ", and ")". The equivalent Java code would be:
Converting Expressions to "Strings" in Java
int foo(int i) {
return i * i;
}
int i = 7;
System.out.println("The value of foo(" + i + ") is " + Integer.toString(foo(i)));
Return the Optional Keyword
In case you haven't noticed, I have written two foo()
functions that each return Integers
. However, neither function used the return
keyword. The return
keyword is optional in JavaFX. If you do not include the return
keyword, the value of the last executed expression is returned. If you prefer, you can use the return
keyword as in the following example:
You Can Still Use the "return" Keyword If You Want
function foo(i: Integer): Integer {
return i * i;
}
def i = 7;
println("The value of foo({i}) is {foo(i)}");
Graphical User Interface
Enough basics, let's start making our chess program. The first thing we will need to do is create a window for our application. In Java you do this with the JFrame
class. The equivalent class in JavaFX is Stage
. The following shows how to create a window in both Java and JavaFX:
Creating Windows in Java and JavaFX
// Java code
import java.io.*;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("Chess");
frame.setBounds(100, 100, 400, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String workingDir = System.getProperty("user.dir");
String iconFilename =
workingDir + File.separator + "res" + File.separator + "Icon32.png";
ImageIcon icon = new ImageIcon(iconFilename);
frame.setIconImage(icon.getImage());
frame.setVisible(true);
}
}
// JavaFX code
import java.io.*;
import java.lang.*
import javafx.scene.image.*;
import javafx.stage.*;
def stage: Stage = Stage {
title: "Chess"
x: 100, y: 100, width: 100, height: 100
icons: [ Image {url: "{__DIR__}res{File.separator}Icon32.png" } ]
onClose: function() {
System.exit(0);
}
}
JavaFX is Buggy
Here are screenshots of each of the two applications. The Java application is the one with the custom chess icon, while the JavaFX application has the default Java application icon. This is the first of several bugs that I found in JavaFX. I did some Google searching and it looks like you cannot load a custom application icon for your program in JavaFX. Here are the forums that discuss the bug, bug1 and bug2. According to the JavaFX 1.1 docs you should be able to set the icon for a Stage
. Unfortunately it doesn't seem to work. So we will have to do with the default Java icon for now. Hopefully this issue will get resolved soon.
JavaFX Version 1.1 is Still Buggy, You Cannot Change the Icon for the Main Window
Creating Objects
In Java, new classes are created with the new
keyword. In JavaFX, you don't have to use the new
keyword. You typically create a new object by assigning a variable to the name of the class and then assigning initial values for the class attributes inside of braces ({}
). You set the initial name of a class attribute by giving the attribute name, followed by a :
followed by the initial value for that class attribute. You can place commas between each assignment of values to class attributes, but this is optional. In the example above, I only used commas between the attributes that I assigned on the same line (the x
, y
, width
, and height
attributes). The icons
attribute is a sequence so I place the icon for the Stage
inside brackets, []
. The icon is an instance of an Image
object. So I create an instance of this class similarly by initializing the class attributes inside of curly braces. In this case, I set the url
for the location of the image to use for the icon. The onClose
attribute is initialized to point to a function. The function simply calls System.exit(0)
.
You can still use the new
keyword to instantiate classes if you like. For example here is a JavaFX program that works identically to the previous example, but uses new
to create the Stage
. Classes in JavaFX have no constructors. Since Stage
is a JavaFX class, we cannot assign initial values to the object by passing values to the constructor (there is no constructor). So we assign values to the class attributes after creating the new object as shown in the code below:
You Can Still Use the "new" Keyword to Create JavaFX Objects, But I Don't Like it As Much
import java.io.*;
import java.lang.*;
import javafx.scene.image.*;
import javafx.stage.*;
def stage: Stage = new Stage();
stage.title = "Chess";
stage.x = 100;
stage.y = 100;
stage.width = 400;
stage.height = 400;
stage.icons = [ Image { url: "{__DIR__}res{File.separator}Icon32.png" } ];
stage.onClose = function() {
System.exit(0);
};
When creating Java objects, it makes sense to create them with the new
keyword since Java objects have constructors and you can then pass parameters to these constructors by using the new
keyword. When creating JavaFX objects, I prefer to NOT use the new
keyword. I think the code looks a lot cleaner when I create objects without it and instantiate all of the class attributes using the enclosing curly braces, {}
, as shown in the first example.
Saving Application Settings
I always like saving applications settings in the user's home directory. In the case of GUI applications, the least that I will do is to save the location and size of the applications main window. Java has been around for a long time and already has a set of classes to help us accomplish this task. Let's first see how to do this in Java and then we will do the same thing in JavaFX.
Saving Application Settings in Java
// Java code
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
// get the users home directory
String homeDir = System.getProperty("user.home");
// these are declared final so they can be accessed in the
// anonymous inner class below
final String settingsFilename =
homeDir + File.separator + "mySettings.properties";
final Properties props = new Properties():
// Load the saved settings
try {
FileInputStream input = new FileInputStream(settingsFilename);
props.load(input);
input.close();
} catch(Exception ignore) {
// we ignore the exception since we expect the
// settings file to not exist sometimes
// on the first run of the application it will definitely not exist
}
int savedX;
try {
savedX = Integer.parseInt(props.getProperty("xPos", "100"));
} catch(NumberFormatException e) {
savedX = 100;
}
// similar code to load savedY, savedWidth, savedHeight left out for brevity
// Create and show the window, same code as before except on close we do nothing
// We also make the JFrame final so it can be accessed
// in the anonymous inner class
final JFrame frame = new JFrame("Chess");
frame.setBounds(savedX, savedY, savedWidth, savedHeight);
// different from previous example
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
String workingDir = System.getProperty("user.dir");
String iconFilename = workingDir + File.separator +
"res" + File.separator + "Icon32.png";
ImageIcon icon = new ImageIcon(iconFilename);
frame.setIconImage(icon.getImage());
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
@Override public void windowClosing(WindowEvent e) {
// Save settings on exit
Rectangle frameBounds = frame.getBounds();
props.setProperty("xPos", frameBounds.x);
props.setProperty("yPos", frameBounds.y);
props.setProperty("width", frameBounds.width);
props.setProperty("height", frameBounds.height);
try {
FileOutputStream output = new FileOutputStream(settingsFile);
props.store(output, "Saved settings");
output.close();
} catch(Exception ignore) {
// if the settings can't be saved we'll
// just use the defaults next time
}
// exit the application
System.exit(0);
}
});
}
}
Properties
We use the Properties
Java class to save the applications settings. This class contains a dictionary of key/value pairs. We use the load
method to load the saved properties and the store
method to save the properties. We call the getProperty
method to get the value for the specified key. We also pass in a default value parameter, this value is returned if the key is not set in the Properties
object. We save properties
using the setProperty
method. This is one of several ways of saving application settings in Java.
WindowListener
We load the application settings before we display the window so that we can set the saved position and location of the window. We save the application settings by adding a WindowListener
to the JFrame
. Our WindowListener
will be notified when the user tries to close the application window. When this occurs, we store the size and location of the JFrame
in our Properties
object and save it to the user's home directory. We then exit the application by calling System.exit(0)
.
Saving Settings in JavaFX
We can use the same classes to save application settings in our JavaFX class. The only difference is we do not need to add a WindowListener
, we simply have to update the onClose
function of our Stage
class to save the application settings before exiting the application. This is shown in the code below:
Saving Application Settings in JavaFX
import java.io.*;
import java.lang.*;
import java.util.*;
import javafx.scene.image.*;
import javafx.stage.*;
// find the settings file in the users home directory
def homeDir = System.getProperty("user.home");
def settingsFile = "{homeDir}{File.separator}"mySettings.properties";
// load the settings into the Properties class
def props: Properties = new Properties();
try {
def input: FileInputStream = new FileInputStream(settingsFile);
props.load(input);
input.close();
} catch(ignore: Exception) {
// if the file doesn't exist, that's OK we will just use the default values
}
// read the saved settings
var savedX: Number;
try {
savedX = Double.parseDouble(props.getProperty("xPos", "100"));
} catch(e: NumberFormatException) {
savedX = 100;
}
// similarly load savedY, savedWidth, and savedHeight settings
// create our window as before, only add saveSettings() call to onClose function
def stage: Stage = Stage {
title: "Chess"
x: savedX, y: savedY, width: savedWidth, height: savedHeight
icons: [ Image {url: "{__DIR__}res{File.separator}Icon32.png" } ]
onClose: function() {
saveSettings(); // save settings before exiting
System.exit(0);
}
}
function saveSettings(): Void {
props.setProperty("xPos", "{stage.x}");
props.setProperty("yPos", "{stage.y}");
props.setProperty("width", "{stage.width}");
props.setProperty("height", "{stage.height}");
try {
def output: FileOutputStream = new FileOutputStream(settingsFile);
props.store(output, "Saved Chess Settings");
output.close();
} catch(ignore: Exception) {
// if the settings can't be saved we'll just use the defaults next time
}
}
As you can see, we are able to use the same Java classes to save our application settings in JavaFX as we use in our Java apps. This is one of the best features of JavaFX, I can use the large library of existing Java classes in my JavaFX programs. My experience working with the Java Framework is thus applicable to my JavaFX programming.
One thing to notice is that the x
, y
, width
, and height
attributes of the Stage
class are all defined as Numbers
. Thus, when I save them to disk, they are all saved with decimal points. This seems odd to me since the size and position of the Stage
object is specified in number of pixels. It doesn't make sense to use a decimal since you can only specify whole pixels, 1.5 pixels makes no sense, it is either 1 or 2 pixels. I'm not sure if this is a bug or not. I've already submitted two bug reports to JavaFX. I'm not sure if another bug report is warranted here or not. It just seems like a weird decision to use Number
instead of Integer
.
The Chess Program
Now that we have our window up, the next step is to make a chess board. I will be creating a custom chess board GUI component in JavaFX. If I were doing this in Java, my chess board would extend JPanel
and I would add this JPanel
to my JFrame
. In JavaFX, my chess board will extend Scene
and will be added to my Stage
object.
Before I create my Board extends Scene
class, let me explain a little about the board I want to create. In building a JavaFX program, I want to take advantage of the graphical powers of this new language. In this case, I want to use SVG graphics for my chess pieces so they look good in any resolution. This means my chess board is going to have to be resizable by the user, and it has to look good regardless of the bounds of the application's main window. The following screen shot should explain how I want the board to look when I resize the main window.
The Chess Board Resizes and Repositions Itself Based on the Size of the Main Window
The board tries to take up as much of the window as possible while remaining a square. If the board is wider than it is tall, or taller than it is wide, then the board is centered in the window. If the window is square, the board will not take up the entire window but will take up most of the window. It will always leave a small border around the board. The width and height of this border is at least the size of a single square on the chess board. So our board has the following 3 attributes:
squareSize
- The size of a single square on the chess boardxOffset
- If the window is wider than it is tall, this tells us how far we have to move the board to make it centered in the windowyOffset
- If the window is taller than it is wide, this tells us how far we have to move the board to make it centered in the window.
JavaFX Bindings
Binding is a new feature in JavaFX that allows you to bind the value of one variable to another variable or to bind the value of a method to another variable. Binding is performed using the bind
keyword. Since the value of the squareSize
, xOffset
, and yOffset
attributes depend on the size of the chess board, we will bind these values to the size of the chess board. Here is the code for our chess board:
Our Chess Board uses "bind" to Dynamically Resize and Reposition Itself
import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
public class Board extends Scene {
def LIGHT_COLOR: Color = Color.web("lemonchiffon");
def DARK_COLOR: Color = Color.web("brown");
public-read var squareSize = bind {
if(width > height) {
height / 10;
} else {
width / 10;
}
}
public-read var xOffset = bind {
if (width > height) {
(width - height) / 2;
} else }
0;
}
}
public-read var yOffset = bind {
if (width > height) {
0;
} else {
(height - width) / 2;
}
}
def board = [ Coord.A8, Coord.B8, Coord.C8, Coord.D8,
Coord.E8, Coord.F8, Coord.G8, Coord.H8,
Coord.A7, Coord.B7, Coord.C7, Coord.D7,
Coord.E7, Coord.F7, Coord.G7, Coord.H7,
Coord.A6, Coord.B6, Coord.C6, Coord.D6,
Coord.E6, Coord.F6, Coord.G6, Coord.H6,
Coord.A5, Coord.B5, Coord.C5, Coord.D5,
Coord.E5, Coord.F5, Coord.G5, Coord.H5,
Coord.A4, Coord.B4, Coord.C4, Coord.D4,
Coord.E4, Coord.F4, Coord.G4, Coord.H4,
Coord.A3, Coord.B3, Coord.C3, Coord.D3,
Coord.E3, Coord.F3, Coord.G3, Coord.H3,
Coord.A2, Coord.B2, Coord.C2, Coord.D2,
Coord.E2, Coord.F2, Coord.G2, Coord.H2,
Coord.A1, Coord.B1, Coord.C1, Coord.D1,
Coord.E1, Coord.F1, Coord.G1, Coord.H1 ];
postinit {
for (square in board) {
def i: Integer = indexof square;
insert Rectangle {
fill: if (square.getIsWhite()) LIGHT_COLOR else DARK_COLOR
x: bind xOffset + ((i mod 8) + 1) * squareSize
y: bind yOffset + ((i / 8) + 1) * squareSize
width: bind squareSize
height: bind squareSize
} into content;
}
}
}
Coord
is simply an enum
that makes it easier for me to place pieces on the board. Coord
also keeps track of whether or not the square at that coordinate is a dark or light colored square. In chess, the queen is always placed on her color and the lower left square on the board is always dark. This helps you keep from misplacing your king and queen on the board. JavaFX cannot create enum
s, so the Coord
class is written in Java. This is why you need both a Java and a JavaFX compiler to build this project. You must build the Coord.java file using the Java compiler before you can build the JavaFX files using the JavaFX compiler.
This class uses a lot of binding. The first bind we see is for the class attribute squareSize
. squareSize
in this case is bound to an expression. If the value of the expression changes, the value of the squareSize
attribute will automatically change to equal the new value of the expression. The expression that this attribute is bound to is a code block. The value of this code block is equal to the last line executed in the code block. In this case, the last line executed depends on the values of the width
and height
attributes of the Scene
. The value of squareSize
will be set to either height / 10;
or width / 10;
.
Our Board
class will be placed in our Stage
class (the main window for our application). Our Board
will fill the entire client area of the Stage
, this is basically the entire window excluding the frame. If we change the size of the window, the width
and height
attributes of our Board
class will be modified. As these values are changed, the JavaFX runtime will automatically adjust the value of the squareSize
attribute for us.
Similarly, the xOffset
and yOffset
attributes are also bound to the values of width
and height
.
postinit Instead of Constructors
The next set of binds
that we see are in the postinit
code block. Like I said earlier, JavaFX classes do not contain constructors. You often need to have code executed when an object is first instantiated. JavaFX allows you to specify code to be executed on object instantiation through the use of the postinit
keyword. Code placed within the postinit
code block will automatically be executed on the instantiation of the object, similar to a constructor call in Java.
In the Boards postinit
block, we create a Rectangle
for each square on the chess board. The location and size of each of the Rectangles
on the board is bound to the values of the squareSize
, xOffset
, and yOffset
. This is done by placing the bind
keyword after the x
, y
, width
, and height
attributes of each of the Rectangles
used for each of the squares and placing the expression that the attribute should be bound to after the bind
keyword.
JavaFX has a New Set of Access Modifiers
The Board
class uses an access modifier that does not exist in the Java programming language, public-read
. JavaFX uses a new set of access modifiers which I will describe right now:
- default - If you do not use an access modifier for a variable or function it defaults to having script-only access. This means you can only access the variable/function from within the current script file. JavaFX has no
private
access modifier, the default access modifier in JavaFX is equivalent toprivate
in Java. (Note: I use the terms variable and function since JavaFX does not require you to use classes, but if you are using a class I guess the better terms would be attribute and method.) package
- This makes the variable or function available to any other code in the same package. This is similar to the default access modifier in Java (Java does not use thepackage
keyword as an access modifier.protected
- This makes the variable/function available to any other code in the same package and also to all subclasses. Java uses the same keyword and it means the same thing.public
- This makes the variable/function available to everybody for both read and write access. Java uses the same keyword and it means the same thing.public-read
- We used this access modifier in ourBoard
class. This access modifier can only be applied to variables, it cannot be applied to functions.This gives everyone read access to the variable while restricting write access to the script. Java does not have a comparable access modifier. You can get the same functionality in Java by declaring the attributeprivate
and providing apublic
method that retrieves the value of the attribute. An example of this is given in the code below.public-init
- A variable marked aspublic-init
can be written to by anyone when the object is first created. After the object is created, it can be read by anyone but can only be written to by the script that defines the variable. Like thepublic-read
access modifier, thispublic-init
access modifier can only be used on a variable, not on a function. An equivalent access modifier does not exist in Java. You can get the same functionality in Java by declaring the attributeprivate
, providing apublic
method that retrieves the value of the attribute, and allowing the attributes initial value to be set through a parameter to the constructor. An example of this is given in the code below:
"public-read" and "public-init" JavaFX Code Translated to Java Code
// JavaFX code
public-read size: Integer;
public-init height: Integer;
// Similar code in Java
public class MyClass {
// only this class can write to the attribute size
private int size;
private int height;
// other classes can only write to height through this constructor
// after the object is created only this class can write to height
public MyClass(int initHeight) {
height = initHeight;
}
// everybody can read the value of size
public int getSize() {
return size;
}
// everybody can read the value of height
public int getHeight() {
return height;
}
}
There is one thing that I can do in Java which I was unable to do in JavaFX. In Java, I will often have a class attribute declared as final
, I can do this in JavaFX using the def
keyword. However, in Java I can give anyone permission to set this attribute's initial value by passing in a value through the constructor. In JavaFX, you cannot do this. I tried declaring a variable as public-init def
, but this produces an error if I don't initialize the variable right there.
In JavaFX, You Cannot Initialize a "def" (i.e. final) Variable from Another Class
// the following is valid Java code for which there
// seems to be no comparable code in JavaFX
public class Test {
public final int i;
public Test(int initI) {
i = initI;
}
}
// the following JavaFX code will not compile
public class Test {
public-init def i: Integer;
}
def test: Test = Test { i: 7 }
// here is the error message
Test.fx:2: The 'def' of 'i' must be initialized with a value here.
Perhaps you meant to use 'var'?
public-init def i: Integer
1 error
// if I initialize the attribute i, I get more error messages
class Test {
public-init def i: Integer = 4;
}
def test: Test = Test { i: 7 }
// here are the error messages
Test.fx:2: modifier public-init not allowed on a def
public-init def i: Integer = 4;
Test.fx:5: You cannot change the value(s) of 'i'
because it was declared as a 'def', perhaps it should be a 'var'?
def test: Test = Test { i: 7 }
2 errors
It seems useful to be able to have a variable declared as public-init def
. I wanted to use this for my chess pieces by initializing a piece as either black or white when I create the piece. Once a piece's color is set, it can never be changed so I wanted to declare it as a def
but was unable to do so. I had to declare it a public-init var
and had to make sure and not change it after it is first initialized. This is the second design decision made in JavaFX which just seems wrong to me. (The first being the decision to declare the attributes x
, y
, width
, and height
of the class Stage
as Numbers
instead of Integers
.)
SVG Chess Pieces
The chess pieces that I use in this chess program come from openclipart.org. This website has a good selection of open source SVG graphics that you can use in your applications. Many thanks to them for supplying me with pretty graphics to use in my program since I am not much of an artist. I was originally under the impression that JavaFX could directly load SVG graphics. This is not the case. In order to use the graphics, I had to convert them from SVG to the JavaFX graphics format. In order to convert SVG to JavaFX graphics, you need to download the JavaFX 1.1 Production Suite from javafx.com. You use the "SVG to JavaFX Graphics Converter" program that comes with this suite of tools to convert the SVG graphics to JavaFX graphics. Here is a screen shot of the chess pieces used in this program:
The SVG Chess Piece Graphics Used
Loading and Displaying the Chess Pieces
I came across another JavaFX bug when trying to load my JavaFX graphics formatted chess pieces. According to the documentation, I should be able to load the pieces with the following code:
This Code Should Load JavaFX Graphics Files, But It Does Not Work
// load the JavaFX graphics file
var pieceNode = FXDLoader.load("{__DIR__}res/WKing.fxz");
// add the graphic to a Group object
var group = Group {
content: [
pieceNode
]
}
// add the graphic to the board
insert group into board.content
The code listed above does not work. __DIR__
is a special constant that returns the URL of the directory that contains the current JavaFX source file. In all of the sample code that I saw, all resources (graphics, icons, etc.) for JavaFX programs are accessed relative to __DIR__
. In most cases, this works. For example, to load an image you can enter code like the following:
Loading an Image in JavaFX
var myImage = Image { url: "{__DIR__}images/SampleImage.jpg" backgroundLoading: true};
However if you try to load an image using the FXDLoader
class using this method, it will fail. The problem is the value returned from __DIR__
is a URL. This means spaces are converted to %20. FXDLoader
fails with a file not found message. The fix for the problem is to convert the %20s back to spaces as in the following code:
Fix to Load the JavaFX Graphics
// bug fix, convert %20 to space so FXDLoader can load the graphics
def rootDir = "{__DIR__}".replaceAll("%20", " ");
// load the JavaFX graphics file
var pieceNode = FXDLoader.load("{rootDir}res/WKing.fxz");
// add the graphic to a Group object
var group = Group {
content: [
pieceNode
]
}
// add the graphic to the board
insert group into board.content
So I submitted another bug report saying that FXDLoader
needs to be updated to handle URLs just as in the case of the class Image. Sun's sample code recommends using __DIR__
to load other resources. It should work the same with JavaFX Graphics objects using the FXDLoader
class.
Custom Mouse Cursor in JavaFX
Better get this wrapped up soon, this article is getting kinda long. The last thing I did which might be useful to JavaFX programmers is load a custom mouse cursor. I wanted my chess program to highlight the piece that the mouse is over and to change the mouse cursor to the open hand shown in the figure below and to change to the closed hand when the piece is clicked and dragged. I also highlight the square over which the piece will be placed if it drops the piece. This is useful in cases where the user hovers over a square corner you might not know which square the piece will be dropped in without this UI hint.
Highlighted Pieces and Custom Cursors on Mouse Over and Mouse Drag
JavaFX has its own set of default cursors available for use. If you want to create your own custom cursor, you have to create a new java.awt.Cursor
object for your custom cursor as in the following code:
Custom Cursor
import java.awt.*;
import java.net.*;
import javax.swing.*;
import javafx.scene.Cursor;
public class CustomCursor extends Cursor {
public-init var imageURL: String;
public-init var cursorName: String;
public override function impl_getAWTCursor(): java.awt.Cursor {
var toolkit = Toolkit.getDefaultToolkit();
var image: Image = toolkit.getImage(new URL(imageURL));
var point: Point = new Point(16, 16);
var cursor: java.awt.Cursor =
toolkit.createCustomCursor(image, point, cursorName);
}
}
This code is a little confusing because we are using two different Cursors
. First, our new class CustomCursor
extends the JavaFX Cursor
class (javafx.scene.Cursor
) class. Second, our class creates a new Java Cursor
object (java.awt.Cursor
). So these are two different Cursor
classes. One thing I noticed is the impl_getAWTCursor()
method that I am overriding is not listed in the JavaFX 1.1 API documentation. It could be because the JavaFX API documentation is crap right now (it doesn't feel nearly as complete as the Java API documentation) or maybe this is a hidden API that should not be overridden.
As far as the JavaFX API documentation being worse than the Java API documentation, I guess that's to be expected. Java has been around a lot longer and is a much more mature language. However, there is one thing I really dislike about the JavaFX API docs. In the Java docs, all of the classes are listed in a single HTML pane. So if I am searching for a particular class, I can easily find it in my browser. In JavaFX, if you know the class name that you are looking for but you do not know the package that it is in, it can be quite difficult to find in the documentation. In the JavaFX API docs, they don't list all of the classes in the side pane (as in the Java docs) but they list all of the package names in the side pane. You have to click on a package name to see the classes in that package. So if you want to find the documentation for the Cursor
class and you don't know that it is in the javafx.scene
package, it can be difficult to find. The docs may look prettier, but more usable they are not.
Back to loading our custom cursor. The following code loads our custom cursor into the chess program:
Loading the Custom Cursor
def rootDir = "{__DIR__}".replaceAll("%20", " ");
def grabCursor = CustomCursor {
imageURL: "{rootDir}res/grab.png"
cursorName: "Grab"
}
def grabbingCursor = CustomCursor {
imageURL: "{rootDir}res/grabbing.png"
cursorName: "Grabbing"
}
var currentCursor = Cursor.Default;
// Inside of the Piece class we create custom mouse listener
// functions to change the cursor
onMouseEntered: function(e: MouseEvent) {
currentCursor = grabCursor;
board.cursor = currentCursor;
}
onMouseExited: function(e: MouseEvent) {
currentCursor = Cursor.Default;
board.cursor = currentCursor;
}
// similar for other mouse functions released, pressed, clicked
Bye
Well that's it. I found JavaFX to be a fun language to work with, however it feels buggy. I found some bugs, some design decisions which just seem wrong, the API documentation is not as complete as the documentation for Java, and a lot of the code that exists online was written for an earlier version and does not work with the latest version (1.1) of JavaFX. Given the number of bugs and the fact that the language has changed a lot from earlier versions, you might want to wait until the product matures before you build a major application in JavaFX. However, if you are just writing a simple Chess application (or some other simple program), it is a fun language to work with.
My First Article!
I've been a CodeProject member for almost 9 years now and I finally got around to publishing my first article. I'm hoping to finally graduate (MS in CS) this May. I've been working full time while going to school part time for a long time now. Hopefully after graduating I will have more time to write some more articles on CodeProject. I hope you enjoyed the article and appreciate your votes if you did.
History
- Version 1.0 - April 16, 2009
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
About the Author
El Bob-O Member | Started coding in 1994 and haven't stopped since. Received my BS in Computer Science from Trinity University in 1999. Hope to receive my MS in Computer Science from the University of Texas at San Antonio May of 2009. Have been working full time as a Software Engineer since 2000 writing code in C, C++, C#, MFC, Java, and Python.
|
Yang Sering Dibaca:
-
Sales promotion girls our company is looking for sales promotion girls products: hair accessories the spg will be responsible for the prod...
-
Some of the wildlife sanctuaries offer special tours for photographers, but even without the advantages these tours offer (often being al...
0 comments: