JavaFX - First App with Modern Graphical User Interface
What Will I Learn?
In this tutorial, I will guide you how to build beautiful apps using UI Controls of JavaFX. Many developers just know Java Swing for building apps with graphical user interface, but JavaFX libraries is more complete, powerful and awesome as the best GUI of Java libraries (APIs) ever. By using JavaFX, developers can build various apps with the modern user interface completely with many features, such as audio, video, 2D graphics, 3D graphics and even animations.
- JavaFX UI Controls
- JavaFX Behaviour (Event Handler)
- Java™ Map Object, which Maps Keys to Values
Requirements
Any current JDK.
Prototype layer as seen below:
- Name and Address as the javafx.scene.control.TextField.
- Date of Birth as the javafx.scene.control.DatePicker
- Gender as the javafx.scene.control.RadioButton
- Degree as the javafx.scene.control.ChoiceBox
- Submit as the javafx.scene.control.Button
- Table as the javafx.scene.control.TableView
Difficulty
- Intermediate
Tutorial Contents
Overview
- Create New Project
- Create Entity Class of Student
- Set up of Grid Layout Pane
- Set up All the Required Components
- Create Submit Handler
- Test!
1) Create New Project
- Open your IntelliJ IDEA.
- Select: File ⇢ New ⇢ Project...
- On the field of Project name, type
Test
. Or you can enter another name as you wish. Finally, clickFinish
.
After this project is successfully created, it will look like the following project structure,
⇾ .idea ⇾ out ⇾ src ↳ sample ↳ Controller ↳ Main ↳ sample.fxml
Next, we will only focus on the src/sample
directory to write the source codes. For the moment we will only render all components programmatically, so we do not need the sample.fxml
file yet.
2) Create Entity Class of Student
Right click on
src/sample
directory. Choose: New ⇢ Java Class.Then on the pop up dialog, type
entities.Entity
right on field of Name, then click OK.As defined earlier, we'll provide five fields, such as:
- The fields of
name
,degree
andaddress
asjava.lang.String
. - A field of
birthDate
asjava.util.Date
. - A field of
female
asboolean
.
And these are all the Setters that always return the current object, and the validators inside them,
public Student setName(String value) { if(value != null && value.length() > 0) { this.name = value; return this; } throw new IllegalArgumentException("Name shouldn't be empty"); } public Student setBirthDate(String value) { try { birthDate = FORMATTER.parse(value); return this; } catch(NullPointerException e) { throw new IllegalArgumentException("Date of Birth shouldn't be empty"); } catch(Throwable e) { throw new IllegalArgumentException(String.format("Incorrect Date of Birth (%s)", e.getMessage())); } } public Student setDegree(Object value) { String degree; if(value != null && (degree = (String) value).length() > 0) { this.degree = degree; return this; } throw new IllegalArgumentException("Please, select your current degree"); } public Student setAddress(String value) { if(value != null && value.length() > 0) { this.address = value; return this; } throw new IllegalArgumentException("Address shouldn't be empty"); } /** * Set gender of Student. * @param value {@code True} for Male, otherwise Female. * @return This current object. */ public Student setGender(boolean value) { this.female = !value; return this; }
Slightly different from the Getters as common, we will also provide a global static variables that stores the name of each getters. They will be needed later when defining columns in the table. Here are all the getters:
public static final String NAME = "name"; public String getName() { return name; } public static final String BIRTH_DATE = "birth"; public String getBirth() { LocalDate date = LocalDate.ofInstant(birthDate.toInstant(), java.time.ZoneId.systemDefault()); /** * Or, for JDK < 9 * java.time.ZonedDateTime zdt = birthDate.toInstant().atZone(java.time.ZoneId.systemDefault()); * LocalDate date = zdt.toLocalDate(); */ return String.format("%d, %d %s", date.getYear(), date.getDayOfMonth(), date.getMonth().getDisplayName(TextStyle.FULL, Locale.UK)); } public static final String GENDER = "gender"; public String getGender() { return isFemale()? "Female" : "Male"; } public static final String DEGREE = "degree"; public String getDegree() { return degree; } public static final String ADDRESS = "address"; public String getAddress() { return address; } public Date getBirthDate() { return (Date) birthDate.clone(); } public boolean isFemale() { return female; }
There are some issues when converting
java.util.Date
object tojava.time.LocalDate
. In JDK 9, the following statement works well:LocalDate date = LocalDate.ofInstant(birthDate.toInstant(), java.time.ZoneId.systemDefault());
Fortunately, for you who are working using JDK with earlier version of the current one (JDK < 9), there are another alternative way to convert, which are like the following:
java.time.ZonedDateTime zdt = birthDate.toInstant().atZone(java.time.ZoneId.systemDefault()); LocalDate date = zdt.toLocalDate();
- The fields of
3) Set up of Grid Layout Pane
Do some change in the void start(javafx.stage.Stage)
method at sample.Main
as JavaFX Application. Next, create a static method: javafx.scene.layoutPane initialize()
and then type this code:
@Override
public void start(Stage primaryStage) {
Scene scene = new Scene(initialize(), 800, 550);
primaryStage.setScene(scene);
primaryStage.setTitle("Student Form");
primaryStage.setMinWidth(350);
primaryStage.setMinHeight(200);
primaryStage.show();
}
private static Pane initialize() {
GridPane grid = new GridPane();
grid.setVgap(10);
grid.setHgap(10);
grid.setAlignment(Pos.CENTER);
grid.setPadding(new Insets(15, 25, 15, 25));
{
//Register all components here
}
return grid;
}
Here we use javafx.scene.layout.GridPane as the main layout, because it's easy to use. Our perspective to arrange the components based on position of rows and columns.
4) Set up All the Required Components
We need to register all components in a map object, CONTROLS
as instance of java.util.HashMap. Insert all of these codes within initialize
method as we have previously defined. And here step by step:
Main Title and Field of Name
final Map<String, Control> CONTROLS = new java.util.HashMap<>(); Control control; Text label; (label = new Text("Student")).setId("mainTitle"); grid.add(label, 0, 0, 2, 1); { (label = new Text("Name")).getStyleClass().add("baseLabel"); grid.add(label, 0, 1); grid.add(control = new TextField(), 1, 1, 2, 1); ((TextInputControl) control).setPromptText("Enter your full name"); CONTROLS.put(Student.NAME, control); }
Result
Field of Birthday
{ (label = new Text("Date of Birth")).getStyleClass().add("baseLabel"); grid.add(label, 0, 2); DatePicker picker = new DatePicker(); picker.setPromptText("Enter your birthday"); grid.add(picker, 1, 2, 2, 1); CONTROLS.put(Student.BIRTH_DATE, picker); }
Result
Field of Gender
{ ToggleGroup gender = new ToggleGroup(); RadioButton[] options = { new RadioButton("Male"), new RadioButton("Female") }; for(RadioButton option : options) option.setToggleGroup(gender); (label = new Text("Gender")).getStyleClass().add("baseLabel"); grid.add(label, 0, 3); grid.add(options[0], 1, 3); grid.add(options[1], 2, 3); CONTROLS.put("male", options[0]); CONTROLS.put("female", options[1]); }
Result
- Field of Degree
{ ChoiceBox<String> degrees = new ChoiceBox<>(); degrees.getItems().addAll("Computer Science", "Mathematics ", "Biomedical Engineering", "Nursing", "Psychology and Counseling"); (label = new Text("Degree")).getStyleClass().add("baseLabel"); grid.add(label, 0, 4); grid.add(control = degrees, 1, 4, 2, 1); CONTROLS.put(Student.DEGREE, control); }
Fields of Address
{ (label = new Text("Address")).getStyleClass().add("baseLabel"); grid.add(label, 0, 5); grid.add(control = new TextField(), 1, 5, 2, 1); ((TextInputControl) control).setPromptText("Enter your current address"); CONTROLS.put(Student.ADDRESS, control); }
Result
Button of Submit
Button submit = new Button("Submit"); { submit.setStyle("-fx-background-color:gold;-fx-text-fill:black;-fx-font:normal bold 20px 'Calibri'"); HBox container = new HBox(15); container.setAlignment(Pos.BOTTOM_RIGHT); container.getChildren().add(submit); grid.add(container, 2, 6); }
Result
Main Table
TableView<Student> table = new TableView<>(); table.setPrefWidth(800); TableColumn<Student, String> column; {//Register column of Name table.getColumns().add(column = new TableColumn<>("Name")); column.setMinWidth(180); column.setCellValueFactory(new PropertyValueFactory<>(Student.NAME)); } {//Register column of Birth date table.getColumns().add(column = new TableColumn<>("Date of Birth")); column.setMinWidth(120); column.setCellValueFactory(new PropertyValueFactory<>(Student.BIRTH_DATE)); } {//Register column of Gender table.getColumns().add(column = new TableColumn<>("Gender")); column.setMinWidth(80); column.setCellValueFactory(new PropertyValueFactory<>(Student.GENDER)); } {//Register column of Degree table.getColumns().add(column = new TableColumn<>("Degree")); column.setMinWidth(125); column.setCellValueFactory(new PropertyValueFactory<>(Student.DEGREE)); } {//Register column of Address table.getColumns().add(column = new TableColumn<>("Address")); column.setMinWidth(200); column.setCellValueFactory(new PropertyValueFactory<>(Student.ADDRESS)); } grid.add(table, 0, 7, 5, 1);
Result
5) Create Submit Handler
When the Submit button is clicked, then:
- Make sure all fields are filled correctly, and give the user focus on the unfilled field.
- Save all the Student data into the table.
- Reset all controls.
Then, create a static class of: SubmitHandler
that implements the javafx.event.EventHandler<ActionEvent>
interface, and type in the following code:
private static final class SubmitHandler implements EventHandler<ActionEvent> {
private final Map<String, Control> CONTROLS;
private final TableView<Student> TABLE;
private SubmitHandler(Map<String, Control> controls, TableView<Student> table) {
CONTROLS = controls;
TABLE = table;
}
@Override
public void handle(ActionEvent event) {
Control control = null;
String value = null;
RadioButton[] options;
try {
Student student = new Student()
.setName(((TextInputControl)
(control = CONTROLS.get(Student.NAME))
).getText());
{//Set Date of Birth
DatePicker datePicker = (DatePicker) CONTROLS.get(Student.BIRTH_DATE);
value = ((TextInputControl) (control = datePicker.getEditor())).getText();
student.setBirthDate(value);
value = null;
}
{//Set Gender
options = new RadioButton[] {
(RadioButton) CONTROLS.get("male"),
(RadioButton) CONTROLS.get("female")
};
boolean b = options[0].isSelected();
if(!b && !options[1].isSelected()) {
control = options[0];
throw new UnsupportedOperationException("Please, select your gender");
}
student.setGender(b);
}
student.setDegree(
((ChoiceBox<?>) (control = CONTROLS.get(Student.DEGREE))).getValue()
).setAddress(
((TextInputControl) (control = CONTROLS.get(Student.ADDRESS))).getText()
);
TABLE.getItems().add(student);
} catch(Throwable e) {
if(control != null) {
control.requestFocus();
if(value != null)
((TextInputControl) control).selectPositionCaret(value.length());
}
return;
}
{//Reset all controls
((TextInputControl) CONTROLS.get(Student.NAME)).setText(null);
((DatePicker) CONTROLS.get(Student.BIRTH_DATE)).setValue(null);
for(RadioButton option : options)
option.setSelected(false);
((ChoiceBox<?>) CONTROLS.get(Student.DEGREE)).setValue(null);
((TextInputControl) CONTROLS.get(Student.ADDRESS)).setText(null);
NOTIFIER.setText(null);
}
}
}
Finally, register this action to Submit
button,
submit.setOnAction(new SubmitHandler(CONTROLS, table));
Here, we already know why we need to list all the components into the map object. The goal is the Handler can references and update to the existing components directly.
6) Test!
Here , I try to submit the form by leaving the Degree empty, and the result is:
Final Touches
Notifier
Create a static label as the Notifier of user carelessness,
private static final Text NOTIFIER = new Text();
Then, add it to the layout,
grid.add(NOTIFIER, 3, 6);
Finally, catch every error at the SubmitHandler made by the user and display its message .
catch(Throwable e) { NOTIFIER.setText(e.getMessage()); . . . }
Styles
Add a CSS file as
src/sample/main.css
, and type this following:.baseLabel { -fx-font-size: 15px; -fx-font-weight: bold; -fx-fill: #888; } .redLabel { -fx-font-size: 15px; -fx-font-weight: bold; -fx-fill: #b22222; } #mainTitle { -fx-font-size: 42px; -fx-font-family: "Cambria"; -fx-font-weight: bold; -fx-fill: #818181; -fx-effect: innershadow( three-pass-box , rgba(0, 0, 0, 0.7) , 6, 0.0 , 0 , 2 ); }
Apply this styles using this following statement on
start(javafx.stage.Stage)
method:scene.getStylesheets().add(Main.class.getResource("main.css").toExternalForm());
Thank you!
Share with Heart.
Appendix
src/sample/Main
package sample;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import sample.entities.Student;
import java.util.Map;
public class Main extends Application {
private static final Text NOTIFIER = new Text();
static {
NOTIFIER.getStyleClass().add("redLabel");
NOTIFIER.setWrappingWidth(350);
}
@Override
public void start(Stage primaryStage) {
Scene scene = new Scene(initialize(), 800, 550);
scene.getStylesheets().add(Main.class.getResource("main.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.setTitle("Student Form");
primaryStage.setMinWidth(350);
primaryStage.setMinHeight(200);
primaryStage.show();
}
private static Pane initialize() {
GridPane grid = new GridPane();
grid.setVgap(10);
grid.setHgap(10);
grid.setAlignment(Pos.CENTER);
grid.setPadding(new Insets(15, 25, 15, 25));
final Map<String, Control> CONTROLS = new java.util.HashMap<>();
Control control;
Text label;
{//Main Title
(label = new Text("Student")).setId("mainTitle");
grid.add(label, 0, 0, 2, 1);
}
{//Field of Name
(label = new Text("Name")).getStyleClass().add("baseLabel");
grid.add(label, 0, 1);
grid.add(control = new TextField(), 1, 1, 2, 1);
((TextInputControl) control).setPromptText("Enter your full name");
CONTROLS.put(Student.NAME, control);
}
{//Field of Birthday
(label = new Text("Date of Birth")).getStyleClass().add("baseLabel");
grid.add(label, 0, 2);
DatePicker picker = new DatePicker();
picker.setPromptText("Enter your birthday");
grid.add(picker, 1, 2, 2, 1);
CONTROLS.put(Student.BIRTH_DATE, picker);
}
{//Field of Gender
ToggleGroup gender = new ToggleGroup();
RadioButton[] options = {
new RadioButton("Male"),
new RadioButton("Female")
};
for(RadioButton option : options)
option.setToggleGroup(gender);
(label = new Text("Gender")).getStyleClass().add("baseLabel");
grid.add(label, 0, 3);
grid.add(options[0], 1, 3);
grid.add(options[1], 2, 3);
CONTROLS.put("male", options[0]);
CONTROLS.put("female", options[1]);
}
{//Field of Degrees
ChoiceBox<String> degrees = new ChoiceBox<>();
degrees.getItems().addAll("Computer Science", "Mathematics ", "Biomedical Engineering", "Nursing", "Psychology and Counseling");
(label = new Text("Degree")).getStyleClass().add("baseLabel");
grid.add(label, 0, 4);
grid.add(control = degrees, 1, 4, 2, 1);
CONTROLS.put(Student.DEGREE, control);
}
{//Field of Address
(label = new Text("Address")).getStyleClass().add("baseLabel");
grid.add(label, 0, 5);
grid.add(control = new TextField(), 1, 5, 2, 1);
((TextInputControl) control).setPromptText("Enter your current address");
CONTROLS.put(Student.ADDRESS, control);
}
Button submit = new Button("Submit");
{//Submit
submit.setStyle("-fx-background-color:gold;-fx-text-fill:black;-fx-font:normal bold 20px 'Calibri'");
HBox container = new HBox(15);
container.setAlignment(Pos.BOTTOM_RIGHT);
container.getChildren().add(submit);
grid.add(container, 2, 6);
grid.add(NOTIFIER, 3, 6);
}
{
TableView<Student> table = new TableView<>();
submit.setOnAction(new SubmitHandler(CONTROLS, table));
table.setPrefWidth(800);
TableColumn<Student, String> column;
{//Register column of Name
table.getColumns().add(column = new TableColumn<>("Name"));
column.setMinWidth(180);
column.setCellValueFactory(new PropertyValueFactory<>(Student.NAME));
}
{//Register column of Birth date
table.getColumns().add(column = new TableColumn<>("Date of Birth"));
column.setMinWidth(120);
column.setCellValueFactory(new PropertyValueFactory<>(Student.BIRTH_DATE));
}
{//Register column of Gender
table.getColumns().add(column = new TableColumn<>("Gender"));
column.setMinWidth(80);
column.setCellValueFactory(new PropertyValueFactory<>(Student.GENDER));
}
{//Register column of Degree
table.getColumns().add(column = new TableColumn<>("Degree"));
column.setMinWidth(125);
column.setCellValueFactory(new PropertyValueFactory<>(Student.DEGREE));
}
{//Register column of Address
table.getColumns().add(column = new TableColumn<>("Address"));
column.setMinWidth(200);
column.setCellValueFactory(new PropertyValueFactory<>(Student.ADDRESS));
}
grid.add(table, 0, 7, 5, 1);
}
return grid;
}
private static final class SubmitHandler implements EventHandler<ActionEvent> {
private final Map<String, Control> CONTROLS;
private final TableView<Student> TABLE;
private SubmitHandler(Map<String, Control> controls, TableView<Student> table) {
CONTROLS = controls;
TABLE = table;
}
@Override
public void handle(ActionEvent event) {
Control control = null;
String value = null;
RadioButton[] options;
try {
Student student = new Student()
.setName(((TextInputControl)
(control = CONTROLS.get(Student.NAME))
).getText());
{//Set Date of Birth
DatePicker datePicker = (DatePicker) CONTROLS.get(Student.BIRTH_DATE);
value = ((TextInputControl) (control = datePicker.getEditor())).getText();
student.setBirthDate(value);
value = null;
}
{//Set Gender
options = new RadioButton[] {
(RadioButton) CONTROLS.get("male"),
(RadioButton) CONTROLS.get("female")
};
boolean b = options[0].isSelected();
if(!b && !options[1].isSelected()) {
control = options[0];
throw new UnsupportedOperationException("Please, select your gender");
}
student.setGender(b);
}
student.setDegree(
((ChoiceBox<?>) (control = CONTROLS.get(Student.DEGREE))).getValue()
).setAddress(
((TextInputControl) (control = CONTROLS.get(Student.ADDRESS))).getText()
);
TABLE.getItems().add(student);
} catch(Throwable e) {
NOTIFIER.setText(e.getMessage());
if(control != null) {
control.requestFocus();
if(value != null)
((TextInputControl) control).selectPositionCaret(value.length());
}
return;
}
{//Reset all controls
((TextInputControl) CONTROLS.get(Student.NAME)).setText(null);
((DatePicker) CONTROLS.get(Student.BIRTH_DATE)).setValue(null);
for(RadioButton option : options)
option.setSelected(false);
((ChoiceBox<?>) CONTROLS.get(Student.DEGREE)).setValue(null);
((TextInputControl) CONTROLS.get(Student.ADDRESS)).setText(null);
NOTIFIER.setText(null);
}
}
}
public static void main(String[] args) {
launch(args);
}
private void show(Stage primaryStage) throws Throwable {
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
}
}
Posted on Utopian.io - Rewarding Open Source Contributors
Thank you for the contribution. It has been approved.
You can contact us on Discord.
[utopian-moderator]
Hey @shreyasgune, I just gave you a tip for your hard work on moderation. Upvote this comment to support the utopian moderators and increase your future rewards!
Nice post .....your distribution is perfect
Hey @murez-nst I am @utopian-io. I have just upvoted you!
Achievements
Suggestions
Get Noticed!
Community-Driven Witness!
I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!
Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x