JavaFx 소개

1. 소개

JavaFX는 Java로 리치 클라이언트 애플리케이션을 빌드하기위한 라이브러리입니다. Java를 지원하는 거의 모든 장치에서 실행되는 GUI 응용 프로그램을 설계하기위한 API를 제공합니다 .

이 튜토리얼에서는 몇 가지 주요 기능과 기능에 중점을두고 다루겠습니다.

2. JavaFX API

Java 8, 9 및 10에서는 JavaFX 라이브러리 작업을 시작하는 데 추가 설정이 필요하지 않습니다. 프로젝트는 JDK 11부터 JDK에서 제거됩니다.

2.1. 건축물

JavaFX는 Prism으로 알려진 렌더링을 위해 하드웨어 가속 그래픽 파이프 라인을 사용합니다 . 또한 그래픽 사용을 완전히 가속화하기 위해 내부적으로 DirectXOpenGL을 사용하여 소프트웨어 또는 하드웨어 렌더링 메커니즘을 활용합니다 .

JavaFX에는 기본 운영 체제에 연결하기 위한 플랫폼 종속 Glass windowing 툴킷 계층이 있습니다 . 운영 체제의 이벤트 큐를 사용하여 스레드 사용을 예약합니다. 또한 창, 이벤트, 타이머를 비동기 적으로 처리합니다.

미디어 엔진은 미디어와 HTML / CSS 지원을 재생할 수 있습니다.

JavaFX 애플리케이션의 주요 구조가 어떻게 생겼는지 살펴 보겠습니다.

여기에 두 가지 주요 컨테이너가 있습니다.

  • Stage 는 응용 프로그램의 기본 컨테이너이자 진입 점입니다 . 기본 창을 나타내며 start () 메서드의 인수로 전달됩니다.
  • Scene 은 이미지보기, 버튼, 그리드, 텍스트 상자와 같은 UI 요소를 보관하기위한 컨테이너입니다.

장면 교체하거나 서로 전환 될 수 장면 . 이것은 장면 그래프 로 알려진 계층 적 개체의 그래프를 나타냅니다 . 해당 계층 구조의 각 요소를 노드라고합니다. 단일 노드에는 ID, 스타일, 효과, 이벤트 핸들러, 상태가 있습니다.

또한 장면 에는 레이아웃 컨테이너, 이미지, 미디어도 포함됩니다.

2.2. 스레드

시스템 수준 에서 JVM은 응용 프로그램을 실행하고 렌더링하기위한 별도의 스레드를 만듭니다 .

  • 프리즘 렌더링 스레드 – 장면 그래프를 개별적으로 렌더링합니다 .
  • 응용 프로그램 스레드 – 모든 JavaFX 응용 프로그램의 기본 스레드입니다. 모든 라이브 노드와 구성 요소가이 스레드에 연결됩니다.

2.3. 라이프 사이클

javafx.application.Application의 클래스는 다음과 같은 라이프 사이클 메소드가 있습니다

  • init () – 응용 프로그램 인스턴스가 생성 된 후 호출 됩니다. 이 시점에서 JavaFX API는 아직 준비되지 않았으므로 여기에서 그래픽 구성 요소를 만들 수 없습니다.
  • start (Stage stage) – 모든 그래픽 구성 요소가 여기에 생성됩니다. 또한 그래픽 활동의 기본 스레드가 여기에서 시작됩니다.
  • stop () – 애플리케이션 종료 전에 호출됩니다. 예를 들어 사용자가 기본 창을 닫을 때입니다. 응용 프로그램을 종료하기 전에 일부 정리를 위해이 메서드를 재정의하는 것이 유용합니다.

정적 launch () 메서드는 JavaFX 응용 프로그램을 시작합니다.

2.4. FXML

JavaFX는 특수 FXML 마크 업 언어를 사용하여보기 인터페이스를 만듭니다.

이것은 비즈니스 로직에서 뷰를 분리하기위한 XML 기반 구조를 제공합니다. XML은 장면 그래프 계층을 자연스럽게 표현할 수 있기 때문에 여기에 더 적합 합니다.

마지막으로,로드 할 .fxml의 파일을, 우리는 사용 FXMLLoader의 장면 계층 구조의 객체 그래프에서 클래스, 결과를.

3. 시작하기

실용성을 높이기 위해 사람 목록을 검색 할 수있는 작은 응용 프로그램을 만들어 보겠습니다.

먼저 도메인을 나타내는 Person 모델 클래스를 추가해 보겠습니다 .

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

int, Stringboolean 값 을 래핑하기 위해 javafx.beans.property 패키지 에서 SimpleIntegerProperty, SimpleStringProperty, SimpleBooleanProperty 클래스를 사용하는 방법에 주목하십시오 .

다음으로 Application 추상 클래스 를 확장하는 Main 클래스를 만들어 보겠습니다 .

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

메인 클래스 는 프로그램의 진입 점 인 start () 메서드를 재정의합니다 .

그런 다음 FXMLLoaderSearchController.fxml 에서 AnchorPane 으로 개체 그래프 계층 구조를로드합니다 .

새로운 Scene을 시작한 후에 는 기본 스테이지로 설정합니다 . 또한 창의 제목을 설정하고 표시 () 합니다.

JavaFX Launcher 없이 JAR 파일을 실행할 수 있도록 main () 메서드 를 포함하는 것이 유용합니다 .

3.1. FXML보기

이제 SearchController XML 파일에 대해 자세히 살펴 보겠습니다 .

검색 애플리케이션의 경우 키워드와 검색 버튼을 입력 할 수있는 텍스트 필드를 추가합니다.

AnchorPane 은 여기서 루트 컨테이너이며 그래프 계층 구조의 첫 번째 노드입니다. 창 크기를 조정하는 동안 자식을 앵커 포인트로 재배치합니다. FX : 컨트롤러 속성 와이어 마크 업으로 자바 클래스입니다.

사용할 수있는 다른 내장 레이아웃이 있습니다.

  • BorderPane – 레이아웃을 위쪽, 오른쪽, 아래쪽, 왼쪽, 가운데의 5 개 섹션으로 나눕니다.
  • HBox – 가로 패널에 자식 구성 요소 정렬
  • VBox – 자식 노드가 세로 열로 정렬됩니다.
  • GridPane – 행과 열이있는 그리드를 만드는 데 유용합니다.

이 예제에서는 수평 HBox 패널 내부 에서 Label 을 사용하여 텍스트를 배치하고 TextField 를 입력에 사용하고 Button을 사용했습니다 . 로 FX : 아이디 우리는 자바 코드에서 나중에 사용할 수 있도록 우리는 요소를 표시합니다.

의 VBox 우리는 검색 결과를 표시 볼 수있는 곳 패널입니다.

그런 다음이를 Java 필드에 매핑하기 위해 @FXML 주석을 사용합니다 .

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

@FXML 주석 필드를 채운initialize () 가 자동으로 호출됩니다. 여기에서 이벤트 리스너 등록, 스타일 추가 또는 텍스트 속성 변경과 같은 UI 구성 요소에 대한 추가 작업을 수행 할 수 있습니다.

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

사용자 정의 디자인을 적용하여 JavaFX 응용 프로그램의 UI를 변경할 수 있습니다.

기본적으로 JavaFX는 modena.css 를 전체 응용 프로그램에 대한 CSS 리소스로 사용 합니다. 이것은 jfxrt.jar 의 일부입니다 .

기본 스타일을 재정의하려면 장면에 스타일 시트를 추가 할 수 있습니다.

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

인라인 스타일을 사용할 수도 있습니다. 예를 들어, 특정 노드에 대한 스타일 속성을 설정하려면 :

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

8. 결론

이 간단한 글은 JavaFX API의 기초를 다룹니다. 내부 구조를 살펴보고 아키텍처, 라이프 사이클 및 구성 요소의 핵심 기능을 소개했습니다.

결과적으로 우리는 간단한 GUI 애플리케이션을 배웠고 이제 만들 수 있습니다.

그리고 항상 그렇듯이 튜토리얼의 전체 소스 코드는 GitHub에서 사용할 수 있습니다.