Pengantar JavaFx

1. Perkenalan

JavaFX adalah pustaka untuk membangun aplikasi klien yang kaya dengan Java. Ini menyediakan API untuk mendesain aplikasi GUI yang berjalan di hampir setiap perangkat dengan dukungan Java.

Dalam tutorial ini, kita akan fokus dan membahas beberapa kapabilitas dan fungsionalitas utamanya.

2. API JavaFX

Di Java 8, 9, dan 10 tidak diperlukan penyiapan tambahan untuk mulai bekerja dengan pustaka JavaFX. Proyek akan dihapus dari JDK dimulai dengan JDK 11.

2.1. Arsitektur

JavaFX menggunakan pipeline grafis yang dipercepat perangkat keras untuk rendering, yang disebut Prism . Terlebih lagi, untuk sepenuhnya mempercepat penggunaan grafik, ini memanfaatkan mekanisme rendering perangkat lunak atau perangkat keras, dengan menggunakan DirectX dan OpenGL secara internal .

JavaFX memiliki lapisan toolkit jendela Glass yang bergantung pada platform untuk terhubung ke sistem operasi asli . Ini menggunakan antrian acara sistem operasi untuk menjadwalkan penggunaan utas. Juga, itu secara asinkron menangani jendela, acara, pengatur waktu.

Mesin Media dan Web memungkinkan pemutaran media dan dukungan HTML / CSS.

Mari kita lihat seperti apa struktur utama aplikasi JavaFX:

Di sini, kami melihat dua wadah utama:

  • Dekor adalah wadah utama dan titik masuk aplikasi . Ini mewakili jendela utama dan diteruskan sebagai argumen dari metode start () .
  • Scene adalah wadah untuk menampung elemen UI, seperti Image Views, Buttons, Grids, TextBoxes.

The Adegan dapat diganti atau beralih ke lain Adegan . Ini mewakili grafik objek hierarki, yang dikenal sebagai Grafik Pemandangan . Setiap elemen dalam hierarki itu disebut node. Node tunggal memiliki ID, gaya, efek, penanganan peristiwa, statusnya.

Selain itu, Scene juga berisi wadah tata letak, gambar, media.

2.2. Benang

Di tingkat sistem, JVM membuat utas terpisah untuk menjalankan dan merender aplikasi :

  • Prisma rendering thread - bertanggung jawab untuk merender Scene Graph secara terpisah.
  • Utas aplikasi - adalah utas utama aplikasi JavaFX apa pun. Semua node dan komponen aktif terpasang ke utas ini.

2.3. Lingkaran kehidupan

Kelas javafx.application.Application memiliki metode siklus hidup berikut:

  • init () - dipanggil setelah instance aplikasi dibuat . Pada titik ini, JavaFX API belum siap, jadi kami tidak dapat membuat komponen grafis di sini.
  • start (Stage stage) - semua komponen grafis dibuat di sini. Juga, utas utama untuk aktivitas grafis dimulai di sini.
  • stop () - dipanggil sebelum aplikasi ditutup; misalnya, saat pengguna menutup jendela utama. Berguna untuk mengganti metode ini untuk beberapa pembersihan sebelum aplikasi dihentikan.

Metode launch () statis memulai aplikasi JavaFX.

2.4. FXML

JavaFX menggunakan bahasa markup FXML khusus untuk membuat antarmuka tampilan.

Ini menyediakan struktur berbasis XML untuk memisahkan tampilan dari logika bisnis. XML lebih cocok di sini, karena dapat secara alami mewakili hierarki Grafik Pemandangan .

Terakhir, untuk memuat file .fxml , kami menggunakan kelas FXMLLoader , yang menghasilkan grafik objek dari hierarki adegan.

3. Memulai

Agar lebih praktis, dan mari membangun aplikasi kecil yang memungkinkan pencarian melalui daftar orang.

Pertama, mari tambahkan kelas model Person - untuk mewakili domain kita:

public class Person { private SimpleIntegerProperty id; private SimpleStringProperty name; private SimpleBooleanProperty isEmployed; // getters, setters }

Perhatikan bagaimana, untuk membungkus nilai int, String dan boolean , kami menggunakan kelas SimpleIntegerProperty, SimpleStringProperty, SimpleBooleanProperty dalam paket javafx.beans.property .

Selanjutnya, mari buat kelas Utama yang memperluas kelas abstrak Aplikasi :

public class Main extends Application { @Override public void start(Stage primaryStage) throws Exception { FXMLLoader loader = new FXMLLoader( Main.class.getResource("/SearchController.fxml")); AnchorPane page = (AnchorPane) loader.load(); Scene scene = new Scene(page); primaryStage.setTitle("Title goes here"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } }

Kelas utama kita menggantikan metode start () , yang merupakan titik masuk untuk program.

Kemudian, FXMLLoader memuat hierarki grafik objek dari SearchController.fxml ke dalam AnchorPane .

Setelah memulai Scene baru , kami mengaturnya ke Stage utama . Kami juga menetapkan judul untuk jendela kami dan menampilkannya () .

Perhatikan bahwa akan berguna untuk menyertakan metode main () agar dapat menjalankan file JAR tanpa JavaFX Launcher .

3.1. Tampilan FXML

Sekarang mari selami lebih dalam ke file XML SearchController .

Untuk aplikasi pencarian kami, kami akan menambahkan bidang teks untuk memasukkan kata kunci dan tombol pencarian:

AnchorPane adalah wadah root di sini, dan simpul pertama dari hierarki grafik. Saat mengubah ukuran jendela, itu akan memposisikan ulang anak ke titik jangkar. The fx: controller kabel atribut kelas Java dengan markup.

Ada beberapa tata letak bawaan lain yang tersedia:

  • BorderPane - membagi tata letak menjadi lima bagian: atas, kanan, bawah, kiri, tengah
  • HBox - mengatur komponen anak dalam panel horizontal
  • VBox - node anak disusun dalam kolom vertikal
  • GridPane - berguna untuk membuat grid dengan baris dan kolom

In our example, inside of the horizontal HBox panel, we used a Label to place text, TextField for the input, and a Button. With fx: id we mark the elements so that we can use them later in the Java code.

The VBox panel is where we'll display the search results.

Then, to map them to the Java fields – we use the @FXML annotation:

public class SearchController { @FXML private TextField searchField; @FXML private Button searchButton; @FXML private VBox dataContainer; @FXML private TableView tableView; @FXML private void initialize() { // search panel searchButton.setText("Search"); searchButton.setOnAction(event -> loadData()); searchButton.setStyle("-fx-background-color: #457ecd; -fx-text-fill: #ffffff;"); initTable(); } }

After populating the @FXML annotated fields, initialize() will be called automatically. Here, we're able to perform further actions over the UI components – like registering event listeners, adding style or changing the text property.

In the initTable() method we'll create the table that will contain the results, with 3 columns, and add it to the dataContainer VBox:

private void initTable() { tableView = new TableView(); TableColumn id = new TableColumn("ID"); TableColumn name = new TableColumn("NAME"); TableColumn employed = new TableColumn("EMPLOYED"); tableView.getColumns().addAll(id, name, employed); dataContainer.getChildren().add(tableView); }

Finally, all of this logic described here will produce the following window:

4. Binding API

Now that the visual aspects are handled, let's start looking at binding data.

The binding API provides some interfaces that notify objects when a value change of another object occurs.

We can bind a value using the bind() method or by adding listeners.

Unidirectional binding provides a binding for one direction only:

searchLabel.textProperty().bind(searchField.textProperty());

Here, any change in the search field will update the text value of the label.

By comparison, bidirectional binding synchronizes the values of two properties in both directions.

The alternative way of binding the fields are ChangeListeners:

searchField.textProperty().addListener((observable, oldValue, newValue) -> { searchLabel.setText(newValue); });

The Observable interface allows observing the value of the object for changes.

To exemplify this, the most commonly used implementation is the javafx.collections.ObservableList interface:

ObservableList masterData = FXCollections.observableArrayList(); ObservableList results = FXCollections.observableList(masterData);

Here, any model change like insertion, update or removal of the elements, will notify the UI controls immediately.

The masterData list will contain the initial list of Person objects, and the results list will be the list we display upon searching.

We also have to update the initTable() method to bind the data in the table to the initial list, and to connect each column to the Person class fields:

private void initTable() { tableView = new TableView(FXCollections.observableList(masterData)); TableColumn id = new TableColumn("ID"); id.setCellValueFactory(new PropertyValueFactory("id")); TableColumn name = new TableColumn("NAME"); name.setCellValueFactory(new PropertyValueFactory("name")); TableColumn employed = new TableColumn("EMPLOYED"); employed.setCellValueFactory(new PropertyValueFactory("isEmployed")); tableView.getColumns().addAll(id, name, employed); dataContainer.getChildren().add(tableView); }

5. Concurrency

Working with the UI components in a scene graph isn't thread-safe, as it's accessed only from the Application thread. The javafx.concurrent package is here to help with multithreading.

Let's see how we can perform the data search in the background thread:

private void loadData() { String searchText = searchField.getText(); Task
    
      task = new Task
     
      () { @Override protected ObservableList call() throws Exception { updateMessage("Loading data"); return FXCollections.observableArrayList(masterData .stream() .filter(value -> value.getName().toLowerCase().contains(searchText)) .collect(Collectors.toList())); } }; }
     
    

Here, we create a one-time task javafx.concurrent.Task object and override the call() method.

The call() method runs entirely on the background thread and returns the result to the Application thread. This means any manipulation of the UI components within this method, will throw a runtime exception.

However, updateProgress(), updateMessage() can be called to update Application thread items. When the task state transitions to SUCCEEDED state, the onSucceeded() event handler is called from the Application thread:

task.setOnSucceeded(event -> { results = task.getValue(); tableView.setItems(FXCollections.observableList(results)); }); 

In the same callback, we've updated the tableView data to the new list of results.

The Task is Runnable, so to start it we need just to start a new Thread with the task parameter:

Thread th = new Thread(task); th.setDaemon(true); th.start();

The setDaemon(true) flag indicates that the thread will terminate after finishing the work.

6. Event Handling

We can describe an event as an action that might be interesting to the application.

For example, user actions like mouse clicks, key presses, window resize are handled or notified by javafx.event.Event class or any of its subclasses.

Also, we distinguish three types of events:

  • InputEvent – all the types of key and mouse actions like KEY_PRESSED, KEY_TYPED, KEY_RELEASED or MOUSE_PRESSES, MOUSE_RELEASED
  • ActionEvent – represents a variety of actions like firing a Button or finishing a KeyFrame
  • WindowEventWINDOW_SHOWING, WINDOW_SHOWN

To demonstrate, the code fragment below catches the event of pressing the Enter key over the searchField:

searchField.setOnKeyPressed(event -> { if (event.getCode().equals(KeyCode.ENTER)) { loadData(); } });

7. Style

Kita dapat mengubah UI aplikasi JavaFX dengan menerapkan desain khusus padanya.

Secara default, JavaFX menggunakan modena.css sebagai resource CSS untuk seluruh aplikasi. Ini adalah bagian dari jfxrt.jar .

Untuk mengganti gaya default, kita dapat menambahkan stylesheet ke adegan:

scene.getStylesheets().add("/search.css");

Kita juga bisa menggunakan gaya sebaris; misalnya, untuk menyetel properti gaya untuk node tertentu:

searchButton.setStyle("-fx-background-color: slateblue; -fx-text-fill: white;");

8. Kesimpulan

Tulisan singkat ini mencakup dasar-dasar JavaFX API. Kami mempelajari struktur internal dan memperkenalkan kapabilitas utama arsitektur, siklus proses, dan komponennya.

Hasilnya, kami belajar dan sekarang dapat membuat aplikasi GUI sederhana.

Dan, seperti biasa, kode sumber lengkap dari tutorial tersedia di GitHub.