First steps with JavaFX
Table of Contents
This tutorial will introduce
- Clean separation of application UI and logic
- Using Builders supporting Fluent API
- Effects
- Layouts
- Property Bindings
- EventHandler
Abstract
The Golden Ratio (Golden Mean, Golden Section) is defined as φ = (√5 + 1) / 2.
Objective
Create an UI to find the golden section by using a slider. Add value labels and an indicator to get green when golden action is hit.
The Model
Create observable double a and b values and a boolean value to indicate whether the golden section has been hit:
private DoubleProperty aValue = new SimpleDoubleProperty(0.0); private DoubleProperty bValue = new SimpleDoubleProperty(0.0); private BooleanProperty sectioAurea = new SimpleBooleanProperty(false);
Method to calculate the golden section:
private void reCalculate(){
double b = aValue.substract(100).multiply(-1).get();
setBValue(b);
double result1 = aValue.get()/bValue.get();
result1 = Math.round(result1*100.0)/100.0;
double result2 = (aValue.get()+bValue.get())/aValue.get();
result2 = Math.round(result2*100.0)/100.0;
sectioAurea.set(result1 == result2);
}
Please note: get() returns the primitive double value, getValue() respectively returns the Double instance.
Next attach listeners to re-caculate the golden section indicator on value changes:
private void attachListener(){ChangeListener onChangeListener = new ChangeListener(){
@Override
public void changed(ObservableValue arg0, Number arg1, Number arg2) {
reCalculate();
}
};
getAValueProperty().addListener(onChangeListener);
getBValueProperty().addListener(onChangeListener);
}
Here is the complete model code width appropriate getter and setter methods:
package de.jensd.javafx.aurea;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
/**
*
* @author Jens Deters
*/
public class SectioModel {
private DoubleProperty aValue = new SimpleDoubleProperty(0.0);
private DoubleProperty bValue = new SimpleDoubleProperty(0.0);
private BooleanProperty sectioAurea = new SimpleBooleanProperty(false);
public SectioModel() {
attachListener();
}
private void attachListener(){
ChangeListener onChangeListener = new ChangeListener(){
@Override
public void changed(ObservableValue arg0, Number arg1, Number arg2) {
reCalculate();
}
};
getAValueProperty().addListener(onChangeListener);
getBValueProperty().addListener(onChangeListener);
}
public DoubleProperty getAValueProperty() {
return aValue;
}
public double getAValue() {
return aValue.get();
}
public void setAValue(double d) {
aValue.set(d);
}
public DoubleProperty getBValueProperty() {
return bValue;
}
public double getBValue() {
return bValue.get();
}
public void setBValue(double d) {
bValue.set(d);
}
public BooleanProperty getSectioAureaProperty() {
return sectioAurea;
}
public boolean isSectioAurea() {
return sectioAurea.get();
}
private void reCalculate(){
double b = aValue.substract(100).multiply(-1).get();
setBValue(b);
double result1 = aValue.get()/bValue.get();
result1 = Math.round(result1*100.0)/100.0;
double result2 = (aValue.get()+bValue.get())/aValue.get();
result2 = Math.round(result2*100.0)/100.0;
sectioAurea.set(result1 == result2);
}
}
The UI
Title Label and usage hint
Text titleText = TextBuilder.create().
text("Sectio Aurea").
effect(ds).
font(Font.font(null, FontWeight.BOLD, 32)).
build();
Text descriptionText = TextBuilder.create().
text("Move the slider until your hit the golden section.\nHint: use the cursor keys for exact control.").
textAlignment(TextAlignment.CENTER).
build();
Indicator
Stop[] falseStops = new Stop[]{new Stop(0.0, Color.WHITE), new Stop(0.3, Color.RED), new Stop(1.0, Color.DARKRED)};
falsePaint = new RadialGradient(0.0, 0.0, -10.0, -10.0, 50, false, CycleMethod.NO_CYCLE, falseStops);
Stop[] trueStops = new Stop[]{new Stop(0.0, Color.WHITE), new Stop(0.3, Color.GREENYELLOW), new Stop(1.0, Color.DARKGREEN)};
truePaint = new RadialGradient(0.0, 0.0, -10.0, -10.0, 50, false, CycleMethod.NO_CYCLE, trueStops);
ellipse = EllipseBuilder.create().
radiusX(30).
radiusY(30).
fill(falsePaint).
build();
DropShadow ds = new DropShadow();
ds.setOffsetY(3.0f);
ds.setColor(Color.color(0.4f, 0.4f, 0.4f));
ellipse.setEffect(ds);
Slider
slider = SliderBuilder.create(). min(model.getAValue()). max(model.getBValue()). showTickMarks(true). showTickLabels(true). majorTickUnit(20). minorTickCount(3). prefWidth(300). blockIncrement(0.1f). build();
Status Labels
aValueText = TextBuilder.create().build(); bValueText = TextBuilder.create().build(); aureaText = TextBuilder.create().build();
Solve Button
solveButton = ButtonBuilder.create().text("Solve").build();
Add the solve action
solveButton.setOnAction(new EventHandler() {
@Override
public void handle(ActionEvent arg0) {
model.setAValue(61.8);
}
});
Put it all together
Using the GridPane layout:
GridPane root = new GridPane();
root.setPadding(new Insets(20, 20, 20, 20));
root.setGridLinesVisible(true);</h2>
RowConstraints rowInfo = new RowConstraints(100);
ColumnConstraints colInfo = new ColumnConstraints(100);
root.getRowConstraints().
add(rowInfo);
for (int j = 0; j <= 2; j++) {
root.getColumnConstraints().
add(colInfo);
}
GridPane.setHalignment(titleText, HPos.CENTER);
GridPane.setHalignment(descriptionText, HPos.CENTER);
GridPane.setHalignment(slider, HPos.CENTER);
GridPane.setHalignment(aValueText, HPos.CENTER);
GridPane.setHalignment(ellipse, HPos.CENTER);
GridPane.setHalignment(bValueText, HPos.CENTER);
GridPane.setHalignment(solveButton, HPos.CENTER);
GridPane.setConstraints(titleText, 0, 0, 3, 1);
GridPane.setConstraints(descriptionText, 0, 1, 3, 1);
GridPane.setConstraints(slider, 0, 2, 3, 1);
GridPane.setConstraints(aValueText, 0, 3);
GridPane.setConstraints(ellipse, 1, 3);
GridPane.setConstraints(bValueText, 2, 3);
GridPane.setConstraints(solveButton, 2, 4);
Connect UI to Model
model.getAValueProperty().
bindBidirectional(slider.valueProperty());
aValueText.textProperty().
bind(model.getAValueProperty().
asString("%.1f"));
bValueText.textProperty().
bind(model.getBValueProperty().
asString("%.1f"));
aureaText.textProperty().
bind(model.getSectioAureaProperty().
asString());
<h2>Connect listener to refresh the indicator:</h2>
<pre>model.getSectioAureaProperty().
addListener(new ChangeListener() {</pre>
@Override
public void changed(ObservableValue arg0, Boolean arg1, Boolean arg2) {
refreshIndicator();
}
});
The complete UI code
package de.jensd.javafx.aurea;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBuilder;
import javafx.scene.control.Slider;
import javafx.scene.control.SliderBuilder;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.RowConstraints;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.Paint;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.EllipseBuilder;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextBuilder;
import javafx.stage.Stage;
/**
*
* @author Jens Deters
*/
public class SectioAureaApplication extends Application {
private SectioModel model = new SectioModel();
private Slider slider;
private Text aValueText;
private Text bValueText;
private Text aureaText;
private Ellipse ellipse;
private Paint falsePaint;
private Paint truePaint;
private Button solveButton;
public static void main(String[] args) {
Application.launch(SectioAureaApplication.class, args);
}
@Override
public void start(Stage stage) throws Exception {
stage.setTitle("Sectio Aurea");
model = new SectioModel();
model.setAValue(0);
model.setBValue(100.9);
slider = SliderBuilder.create().
min(model.getAValue()).
max(model.getBValue()).
showTickMarks(true).
showTickLabels(true).
majorTickUnit(20).
minorTickCount(3).
prefWidth(300).
blockIncrement(0.1f).
build();
aValueText = TextBuilder.create().
build();
bValueText = TextBuilder.create().
build();
aureaText = TextBuilder.create().
build();
solveButton = ButtonBuilder.create().
text("Solve").
build();
Stop[] falseStops = new Stop[]{new Stop(0.0, Color.WHITE), new Stop(0.3, Color.RED), new Stop(1.0, Color.DARKRED)};
falsePaint = new RadialGradient(0.0, 0.0, -10.0, -10.0, 50, false, CycleMethod.NO_CYCLE, falseStops);
Stop[] trueStops = new Stop[]{new Stop(0.0, Color.WHITE), new Stop(0.3, Color.GREENYELLOW), new Stop(1.0, Color.DARKGREEN)};
truePaint = new RadialGradient(0.0, 0.0, -10.0, -10.0, 50, false, CycleMethod.NO_CYCLE, trueStops);
ellipse = EllipseBuilder.create().
radiusX(30).
radiusY(30).
fill(falsePaint).
build();
DropShadow ds = new DropShadow();
ds.setOffsetY(3.0f);
ds.setColor(Color.color(0.4f, 0.4f, 0.4f));
ellipse.setEffect(ds);
Text titleText = TextBuilder.create().
text("Sectio Aurea").
effect(ds).
font(Font.font(null, FontWeight.BOLD, 32)).
build();
Text descriptionText = TextBuilder.create().
text("Move the slider until your hit the golden section.\nHint: use the cursor keys for exact control.").
textAlignment(TextAlignment.CENTER).
build();
// Put it all together
GridPane root = new GridPane();
root.setPadding(new Insets(20, 20, 20, 20));
root.setGridLinesVisible(false);
RowConstraints rowInfo = new RowConstraints(100);
ColumnConstraints colInfo = new ColumnConstraints(100);
root.getRowConstraints().
add(rowInfo);
root.getRowConstraints().
add(rowInfo);
for (int j = 0; j <= 2; j++) {
root.getColumnConstraints().
add(colInfo);
}
GridPane.setHalignment(titleText, HPos.CENTER);
GridPane.setHalignment(descriptionText, HPos.CENTER);
GridPane.setHalignment(slider, HPos.CENTER);
GridPane.setHalignment(aValueText, HPos.CENTER);
GridPane.setHalignment(ellipse, HPos.CENTER);
GridPane.setHalignment(bValueText, HPos.CENTER);
GridPane.setHalignment(solveButton, HPos.CENTER);
GridPane.setConstraints(titleText, 0, 0, 3, 1);
GridPane.setConstraints(descriptionText, 0, 1, 3, 1);
GridPane.setConstraints(slider, 0, 2, 3, 1);
GridPane.setConstraints(aValueText, 0, 3);
GridPane.setConstraints(ellipse, 1, 3);
GridPane.setConstraints(bValueText, 2, 3);
GridPane.setConstraints(solveButton, 2, 4);
root.getChildren().
addAll(titleText, descriptionText, slider, aValueText, ellipse, bValueText, solveButton);
bindProperties();
stage.setScene(new Scene(root, 350, 380));
stage.show();
}
private void refreshIndicator() {
if (model.getSectioAureaProperty().
get()) {
ellipse.fillProperty().
set(truePaint);
} else {
ellipse.fillProperty().
set(falsePaint);
}
}
private void bindProperties() {
model.getAValueProperty().
bindBidirectional(slider.valueProperty());
aValueText.textProperty().
bind(model.getAValueProperty().
asString("%.1f"));
bValueText.textProperty().
bind(model.getBValueProperty().
asString("%.1f"));
aureaText.textProperty().
bind(model.getSectioAureaProperty().
asString());
model.getSectioAureaProperty().
addListener(new ChangeListener() {
@Override
public void changed(ObservableValue arg0, Boolean arg1, Boolean arg2) {
refreshIndicator();
}
});
solveButton.setOnAction(new EventHandler() {
@Override
public void handle(ActionEvent arg0) {
model.setAValue(61.8);
}
});
}
}
_____________________________________________________
here’s the NetBeans project: SectioAurea.zip



Leave a Reply