ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] JavaFX - Intro
    CSE/Java 2015. 12. 14. 12:34

    JavaFX는 여러 절로 구성되어 있습니다.





    Intro

    JavaFX 레이아웃(Layout)

    JavaFX 컨테이너(Container)

    JavaFX 이벤트 처리 & 속성 감시, 바인딩

    JavaFX 컨트롤(Control)

    JavaFX 메뉴바와 툴바 & 다이얼로그

    JavaFX 스레드 동시성




    Intro

     JavaFX는 크로스 플랫폼(Cross-Platform)에서 실행하는 리치 클라이언트 어플리케이션(Rich Client Application)을 개발하기 위한 그래픽과 미디어 패키지를 말합니다. JavaFX는 Java 7부터 JDK에 포함되어 있기 때문에 별도의 SDK 설치 없이도 사용 할 수 있습니다. 


     JavaFX는 Swing보다 더 가벼워졌고 더 강력해졌기 때문에 Swing을 대체하는 새로운 라이브러리로 자리매김을 하고 있습니다. JavaFX는 데스크톱뿐만 아니라 임베디드 장비에서 실행하는 UI 어플리케이션을 개발할 수 있도록 가볍고 풍부한 UI를 제공하고 있습니다.


     JavaFX는 어플리케이션에서 UI 생성, 이벤트 처리, 멀티미디어 재생, 웹 뷰 등과 같은 기능은 JavaFX API로 개발하고 그 이외의 기능은 자바 표준 API를 활용해서 개발할 수 있습니다. 멀티 스레딩과 같은 서버와의 통신 기능은 자바 표준 API를 사용하면서 JavaFX에서 제공하는 풍부한 UI 기능을 활용할 수 있다는 점에서 매우 큰 장점이 될 수 있습니다.


     JavaFX는 화면 레이아웃과 스타일, 어플리케이션 로직을 분리할 수 있기 때문에 디자이너와 개발자들이 협력해서 JavaFX 어플리케이션을 개발할 수 있는 구조를 가지고 있습니다. 자바 코드와 분리해서 스타일 시트(CSS)로 외관을 작성할 수 있기 때문에 분리가 가능합니다. 다음은 JavaFX 어플리케이션을 구성하는 파일 단위 요소를 보여줍니다.



     






    JavaFX 어플리케이션 개발 시작

     JavaFX 어플리케이션을 개발하려면 제일 먼저 메인 클래스를 작성해야 합니다.


     메인 클래스

      JavaFX 어플리케이션을 시작시키는 메인 클래스는 추상 클래스인 javafx.application.Application을 상속받고, start() 메소드를 재정의(Overriding)해야 합니다. 그리고 main() 메소드에서 Application의 launch() 메소드를 호출해야 합니다. launch() 메소드는 메인 클래스의 객체를 생성하고, 메인 윈도우를 생성한 다음 start() 메소드를 호출하는 역할을 합니다.





     위에서 언급했듯이 별도의 SDK 설치 없이 개발이 가능하나, 초기 JAR 파일을 Build Path에 잡아줘야 자동 import가 됩니다.


     개발 환경을 구성하기 위해서 아래 순서를 따라주세요.


      1. JavaFX 해당 프로젝트의 오른쪽 클릭해서 Properties 클릭

      2. 아래와 같은 화면이 나오면 왼쪽 메뉴에서 Java Build Path 클릭하고, 오른쪽 화면에서 Libraries 탭 클릭

      3. 아래 화면에서 Add External JARs 클릭









     4. 경로는 java/jdk/jre/lib/ext 순입니다. 경로내에서 "jfxrt.jar" 찾아서 열기

      * 제 기준으로 C:\Program Files\Java\jdk1.8.0_65\jre\lib\ext 경로입니다.








     



     5. 다음 OK 클릭해주시면 됩니다.

     6. 아래 예제를 통해 상속받는 Application이나 Stage를 로드하는데 문제가 없는지 확인한 후 실행해봅니다.




     * AppMain.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package javaFX;
     
    import javafx.application.Application;
    import javafx.stage.Stage;
     
    public class AppMain extends Application {
        @Override
        public void start(Stage primaryStage) throws Exception {
            primaryStage.show();    // 윈도우 보여주기
        }
        
        public static void main(String[] args) {
            launch(args);            // AppMain 객체 생성 및 메인 윈도우 생성
        }
    }
     
    cs





     간단한 윈도우가 하나 생성되는 것을 볼 수 있습니다. JavaFX에서는 윈도우를 무대(javafx.stage.Stage)로 표현합니다. launch() 메소드에서 생성된 메인 윈도루를 start()의 primaryStage 파라미터로 제공하는데, start() 메소드의 맨 마지막에서 primaryStage.show() 메소드를 호출함으로써 메인 윈도우가 보여집니다.


     








     JavaFX 라이프사이클(LifeCycle)

      JavaFX 어플리케이션은 Application.launch() 로 시작하여 아래와 같은 순으로 진행됩니다.










      - 메인 클래스의 기본 생성자를 호출해서 객체를 생성

      - init() 메소드 호출(메인 클래스의 실행 파라미터를 얻어 어플리케이션이 이용할 수 있게 해줌)

      - int() 끝나고 start() 메소드 호출하여 메인 윈도우 실행

      - JavaFX App 종료



      JavaFX App의 종료 조건은 세 가지가 있습니다.

      - 마우스로 마지막 윈도우(Stage)의 닫기 버튼 클릭 시

      - 자바 코드로 마지막 윈도우의 close() 메소드 호출 시

      - 자바 코드로 Platform.exit() 또는 System.exit(0)을 호출 시



      JavaFX App은 종료되기 직전에 stop() 메소드를 호출하는데, stop() 메소드는 앱이 종료되기 전에 자원을 정리(파일 닫기, 네트워크 끊기)할 기회를 줍니다. init() 과 stop() 메소드는 옵션으로 필요한 경우에 재정의(Override)해서 사용하면 됩니다.


      주목할 점은 라이프사이클의 각 단계에서 호출되는 메소드는 서로 다른 스레드 상에서 실행된다는 점입니다. JVM이 생성한 main 스레드가 Application.launch() 를 실행하면 launch() 메소드는 다음과 같은 이름을 가진 두 스레드를 생성하고 시작시킵니다.


      - JavaFX-Launcher: init() 실행

      - JavaFX Application Thread: 메인 클래스 기본 생성자, start() 및 stop() 실행




      JavaFX App에서 윈도우(Stage)를 비롯한 UI 생성 및 수정 작업, 입력 이벤트 처리 등은 모두 JavaFX Application Thread에서 관장합니다. 그 이유는 JavaFX API는 스레드에 안전하지 않아서 멀티 스레드가 동시에 UI를 생성하거나 수정하게 되면 문제가 발생하기 때문입니다. 그래서 JavaFX Application Thread만 UI를 생성하거나 수정할 수 있도록 되어 있고, 다른 스레드가 UI에 접근하게 되면 예외가 발생합니다. init() 메소드에서 UI를 생성하는 코드를 작성하면 안되는데, 그 이유는 init() 메소드가 JavaFX-Launcher 스레드에서 실행되기 때문입니다. 


      다음 예제는 기본 생성자, init(), start(), stop() 메소드가 어떤 스레드 상에서 실행되는지 볼 수 있는 예제입니다.




     * AppMain.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    package javaFX;
     
    import javafx.application.Application;
    import javafx.stage.Stage;
     
    public class AppMain extends Application {
     
        public AppMain() {
            System.out.println(Thread.currentThread().getName() + ": AppMain() 호출");
        }
     
        @Override
        public void init() throws Exception {
            System.out.println(Thread.currentThread().getName() + ": init() 호출");
        }
     
        @Override
        public void start(Stage primaryStage) throws Exception {
            System.out.println(Thread.currentThread().getName() + ": start() 호출");
            primaryStage.show(); // 윈도우 보여주기
        }
     
        @Override
        public void stop() throws Exception {
            System.out.println(Thread.currentThread().getName() + ": stop() 호출");
        }
     
        public static void main(String[] args) {
            System.out.println(Thread.currentThread().getName() + ": main() 호출");
            launch(args); // AppMain 객체 생성 및 메인 윈도우 생성
        }
    }
     
    cs

      

     









     메인 클래스 실행 파라미터 얻기

      init() 메소드의 역할은 메인 클래스의 실행 파라미터를 얻어 앱의 초기값으로 이용할 수 있도록 하는 것이라고 언급했습니다. 다음과 같이 메인 클래스를 실행할 때 -- 실행 옵션을 주었다고 가정해봅시다.



    1
    2
    java AppMain --ip=192.168.214.22 --port=50020
     
    cs




      Application.launch()는 main() 메소드의 파라미터를 그대로 넘겨 받는데, 이 파라미터는 init() 메소드에서 다음과 같은 두 가지 방법으로 얻을 수 있습니다.

    1
    2
    3
    4
    Parameters params = getParameters();
     
    List<String> list = params.getRaw();  // 1
    Map<StringString> map = params.getNamed();   // 2 
    cs



      Parameters의 getRaw() 메소드는 ip와 port 을 요소로 갖는 List 컬렉션을 리턴하고, getNamed() 메소드는 ip를 키로 개체값을 저장하고, port를 키로해서 개체값을 저장하는 Map 컬렉션을 리턴합니다.







     무대(Stage)와 장면(Scene)
      JavaFX는 윈도우를 무대(Stage)로 표현합니다. 무대는 한 번에 하나의 장면을 가질 수 있는데, JavaFX는 장면을 javafx.scene.Scene 클래스로 표현합니다. 메인 윈도우는 start() 메소드의 primaryStage 파라미터로 전달되지만, 장면(Scene)은 직접 생성해야 합니다. Scene을 생성하려면 UI의 루트 컨테이너인 javafx.scene.Parent가 필요합니다.


    1
    2
    Scene scene = new Scene(Parent root);
     
    cs



      Parent는 추상 클래스이기 때문에 하위 클래스로 객체를 생성해서 제공해야 합니다. 주로 javafx.scene.layout 패키지의 컨테이너들이 사용됩니다. 실제로 UI 컨트롤들이 추가되는 곳은 Parent가 되고, Parent 폭과 높이가 장면의 폭과 높이가 됩니다.

      장면을 생성한 후에는 윈도우에 올려야 하는데, Stage의 setScene() 메소드를 사용합니다. setScene() 메소드는 파라미터로 받은 Scene을 윈도우 내용으로 설정합니다.


    1
    primaryStage.setScene(scene);
    cs


      
      다음 예제는 Parent 하위 클래스인 javafx.scene.layout.VBox을 이용해서 Scene을 생성하고 메인 윈도우(primaryStage)의 장면으로 설정했습니다. VBox에는 Label과 Button 컨트롤을 배치했습니다.



     * AppMain.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    package javaFX;
     
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.Label;
    import javafx.scene.layout.VBox;
    import javafx.scene.text.Font;
    import javafx.stage.Stage;
     
    public class AppMain extends Application {
     
        @Override
        public void start(Stage primaryStage) throws Exception {
            VBox root = new VBox(); // parent 하위 객체인 vBox 샐성
            root.setPrefWidth(300);
            root.setPrefHeight(200);
            root.setAlignment(Pos.CENTER); // 컨트롤을 중앙 정렬
            root.setSpacing(20); // 컨트롤 수직 간격
     
            Label label = new Label(); // Label 컨트롤 생성
            label.setText("Hi, F(x)? ");
            label.setFont(new Font(50));
     
            Button button = new Button();
            button.setText("Confirm");
            button.setOnAction(event -> Platform.exit()); // 클릭 이벤트 처리
     
            // 라벨과 버튼을 VBox의 자식으로 추가
            root.getChildren().add(label);
            root.getChildren().add(button);
     
            // VBox를 루트 컨테이너로 해서 Scene 생성
            Scene scene = new Scene(root);
     
            primaryStage.setTitle("AppMain");
            primaryStage.setScene(scene);
            primaryStage.show();
        }
     
        public static void main(String[] args) {
            launch(args);
        }
    }
     
    cs







      VBox는 수직 방향으로 자식 컨트롤을 배치하는 컨테이너로 먼저 Label을 배치하고 그 아래에 Button을 배치했습니다. Button 클릭시 이벤트 발생은 ActionEvent를 람다식으로 처리한 것입니다.








     * 이 포스트은 서적 '이것이 자바다' 를 참고하여 작성한 포스트입니다.


    댓글

Designed by Tistory.