Giới thiệu về JavaFx

1. Giới thiệu

JavaFX là một thư viện để xây dựng các ứng dụng khách phong phú với Java. Nó cung cấp một API để thiết kế các ứng dụng GUI chạy trên hầu hết mọi thiết bị có hỗ trợ Java.

Trong hướng dẫn này, chúng tôi sẽ tập trung vào và đề cập đến một số khả năng và chức năng chính của nó.

2. API JavaFX

Trong Java 8, 9 và 10, không cần thiết lập bổ sung để bắt đầu làm việc với thư viện JavaFX. Dự án sẽ bị xóa khỏi JDK bắt đầu với JDK 11.

2.1. Ngành kiến ​​trúc

JavaFX sử dụng đường ống đồ họa tăng tốc phần cứng để kết xuất, được gọi là Prism . Hơn nữa, để tăng tốc hoàn toàn việc sử dụng đồ họa, nó sử dụng cơ chế kết xuất phần mềm hoặc phần cứng, bằng cách sử dụng nội bộ DirectXOpenGL .

JavaFX có lớp bộ công cụ Glass windowing phụ thuộc vào nền tảng để kết nối với hệ điều hành gốc . Nó sử dụng hàng đợi sự kiện của hệ điều hành để lập lịch sử dụng luồng. Ngoài ra, nó xử lý không đồng bộ các cửa sổ, sự kiện, bộ hẹn giờ.

Công cụ MediaWeb cho phép phát lại phương tiện và hỗ trợ HTML / CSS.

Hãy xem cấu trúc chính của ứng dụng JavaFX trông như thế nào:

Ở đây, chúng tôi nhận thấy hai vùng chứa chính:

  • Giai đoạn là vùng chứa chính và là điểm vào của ứng dụng . Nó đại diện cho cửa sổ chính và được truyền như một đối số củaphương thức start () .
  • Cảnh là một vùng chứa để chứa các phần tử giao diện người dùng, chẳng hạn như Chế độ xem hình ảnh, Nút, Lưới, Hộp văn bản.

Các Scene có thể được thay thế hoặc chuyển sang một cảnh . Điều này đại diện cho một đồ thị của các đối tượng phân cấp, được gọi là Đồ thị Cảnh . Mỗi phần tử trong hệ thống phân cấp đó được gọi là một nút. Một nút duy nhất có ID, kiểu, hiệu ứng, trình xử lý sự kiện, trạng thái.

Ngoài ra, Scene còn chứa các vùng chứa bố cục, hình ảnh, phương tiện.

2.2. Chủ đề

Ở cấp hệ thống, JVM tạo các luồng riêng biệt để chạy và hiển thị ứng dụng :

  • Luồng kết xuất lăng kính - chịu trách nhiệm hiển thị Đồ thị cảnh riêng biệt.
  • Chuỗi ứng dụng - là chuỗi chính của bất kỳ ứng dụng JavaFX nào. Tất cả các nút trực tiếp và các thành phần được gắn vào luồng này.

2.3. Vòng đời

Lớp javafx.application.Application có các phương thức vòng đời sau:

  • init () - được gọi sau khi cá thể ứng dụng được tạo . Tại thời điểm này, JavaFX API chưa sẵn sàng, vì vậy chúng tôi không thể tạo các thành phần đồ họa ở đây.
  • start (Giai đoạn sân khấu) - tất cả các thành phần đồ họa được tạo ở đây. Ngoài ra, chuỗi chính cho các hoạt động đồ họa bắt đầu ở đây.
  • stop () - được gọi trước khi ứng dụng tắt; ví dụ, khi người dùng đóng cửa sổ chính. Sẽ rất hữu ích nếu bạn ghi đè phương thức này để dọn dẹp trước khi kết thúc ứng dụng.

Phương thức khởi chạy tĩnh () khởi động ứng dụng JavaFX.

2.4. FXML

JavaFX sử dụng ngôn ngữ đánh dấu FXML đặc biệt để tạo giao diện xem.

Điều này cung cấp một cấu trúc dựa trên XML để tách chế độ xem khỏi logic nghiệp vụ. Ở đây XML phù hợp hơn, vì nó có thể biểu diễn khá tự nhiên một hệ thống phân cấp Đồ thị cảnh .

Cuối cùng, để tải lên tệp .fxml , chúng tôi sử dụng lớp FXMLLoader , dẫn đến biểu đồ đối tượng của phân cấp cảnh.

3. Bắt đầu

Để trở nên thiết thực, chúng ta hãy xây dựng một ứng dụng nhỏ cho phép tìm kiếm thông qua danh sách mọi người.

Đầu tiên, hãy thêm một lớp mô hình Person - để đại diện cho miền của chúng tôi:

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

Lưu ý cách gói các giá trị int, Stringboolean , chúng ta đang sử dụng các lớp SimpleIntegerProperty, SimpleStringProperty, SimpleBooleanProperty trong gói javafx.beans.property .

Tiếp theo, hãy tạo lớp Chính mở rộng lớp trừu tượng Ứng dụng :

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); } }

Lớp chính của chúng tôi ghi đè phương thức start () , là điểm đầu vào của chương trình.

Sau đó, FXMLLoader tải cấu trúc phân cấp đồ thị đối tượng từ SearchController.fxml vào AnchorPane .

Sau khi bắt đầu một Cảnh mới , chúng tôi đặt nó ở Giai đoạn chính . Chúng tôi cũng đặt tiêu đề cho cửa sổ của mình và hiển thị () nó.

Lưu ý rằng sẽ hữu ích khi bao gồm phương thức main () để có thể chạy tệp JAR mà không cần Trình khởi chạy JavaFX .

3.1. Chế độ xem FXML

Bây giờ chúng ta hãy đi sâu hơn vào tệp XML của SearchController .

Đối với ứng dụng tìm kiếm của chúng tôi, chúng tôi sẽ thêm một trường văn bản để nhập từ khóa và nút tìm kiếm:

AnchorPane là vùng chứa gốc ở đây và là nút đầu tiên của hệ thống phân cấp đồ thị. Trong khi thay đổi kích thước cửa sổ, nó sẽ đặt lại vị trí con vào điểm neo của nó. Các fx: điều khiển thuộc tính dây lớp Java với các đánh dấu.

Có một số bố cục cài sẵn khác có sẵn:

  • BorderPane - chia bố cục thành năm phần: trên cùng, phải, dưới cùng, trái, giữa
  • HBox - sắp xếp các thành phần con trong một bảng điều khiển ngang
  • VBox - các nút con được sắp xếp theo cột dọc
  • GridPane - hữu ích để tạo lưới với các hàng và cột

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

Chúng tôi có thể thay đổi giao diện người dùng của ứng dụng JavaFX bằng cách áp dụng thiết kế tùy chỉnh cho nó.

Theo mặc định, JavaFX sử dụng modena.css làm tài nguyên CSS cho toàn bộ ứng dụng. Đây là một phần của jfxrt.jar .

Để ghi đè kiểu mặc định, chúng ta có thể thêm một biểu định kiểu vào cảnh:

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

Chúng tôi cũng có thể sử dụng kiểu nội tuyến; ví dụ: để đặt thuộc tính kiểu cho một nút cụ thể:

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

8. Kết luận

Bài viết ngắn gọn này trình bày những kiến ​​thức cơ bản về JavaFX API. Chúng tôi đã xem xét cấu trúc bên trong và giới thiệu các khả năng chính của kiến ​​trúc, vòng đời và các thành phần của nó.

Kết quả là chúng tôi đã học được và hiện có thể tạo một ứng dụng GUI đơn giản.

Và, như mọi khi, mã nguồn đầy đủ của hướng dẫn có sẵn trên GitHub.