컴퓨터프로그래밍및실습 (2022년)/1128

From DISLab
Jump to navigation Jump to search

JavaFX 개요

  1. AWT
    • Native UI 컴포넌트 사용
    • 운영체제 마다 UI 모양이 다름
  2. Swing
    • 운영체제가 제공하는 native UI 사용 안 함
    • 운영체제가 새롭게 제공하는 UI 지원의 어려움
  3. JavaFX
    • Abode의 flash, Microsoft의 silverlight의 대항마
    • JDK 7부터 지원. JDK 8 권장.
    • JDK 11부터는 별도로 설치해야 함.
    • 화면 레이아웃과 스타일, 애플리케이션 로직 분리
    • Java 코드와 분리해서 스타일 시트(CSS)로 외관 작성 → 개발자와 디자이너의 동시 개발 가능
    • Java 코드에서도 레이아웃과 애플리케이션 로직을 분리하고 싶다면 레이아웃은 FXML로 작성, 로직은 Java로 작성
    • JavaFX 애플리케이션 구성 요소
[레이아웃]
자바 코드 파일
또는 FXML 파일
[외관 및 스타일]
CSS 파일
[리소스]
그림 파일
동영상 파일
...
[비즈니스 로직]
자바 코드 파일

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

JavaFX 라이프사이클(life cycle)

  • 예제 코드
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) throws Exception {
        System.out.println(Thread.currentThread().getName() + ": main() 호출");
        launch(args);        
    }
}
  • 실행 결과
main: main() 호출
JavaFX Application Thread: AppMain() 호출
JavaFX-Launcher: init() 호출
JavaFX Application Thread: start() 호출
JavaFX Application Thread: stop() 호출 ← 프로그램을 끝내야 실행된다.

메일 클래스 실행 매개값 얻기

C:> java AppMain --ip=192.168.0.5 --port=50001
  • main()에서 launch(args)를 넘겨 받음
  • init() 메소드에서 아래와 같이 실행할 수 있음
Parameters params = getParameters();
List<String> list = params.getRaw();
Map<String, String> map = params.getNamed();

무대(Stage)와 장면(Scene)

  • 윈도우 : Stage
  • Stage에는 하나의 Scene을 가질 수 있음
  • Scene은 직접 생성해야 함
AppMain 실행화면
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();
        root.setPrefWidth(350);
        root.setPrefHeight(150);
        root.setAlignment(Pos.CENTER);
        root.setSpacing(20);

        Label label = new Label();
        label.setText("Hello, JavaFX");
        label.setFont(new Font(50));

        Button button = new Button();
        button.setText("확인");
        button.setOnAction(event->Platform.exit());

        root.getChildren().add(label);
        root.getChildren().add(button);

        Scene scene = new Scene(root); // VBox를 루트 컨테이너(root container)로 해서 Scene 생성

        primaryStage.setTitle("AppMain입니다");
        primaryStage.setScene(scene); // 윈도우에 장면 설정
        primaryStage.show();        
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

JavaFX 레이아웃

프로그램적 레이아웃

프로그램적 레이아웃
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class AppMain extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        HBox hbox = new HBox();
        hbox.setPadding(new Insets(10));
        hbox.setSpacing(10);

        TextField textField = new TextField();
        textField.setPrefWidth(200);

        Button button = new Button();
        button.setText("확인");

        ObservableList list = hbox.getChildren();
        list.add(textField);
        list.add(button);

        Scene scene = new Scene(hbox);

        primaryStage.setTitle("AppMain");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

FXML 레이아웃

  • FXML 파일 (root.fxml)
FXML 레이아웃
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.HBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>

<HBox xmlns:fx="http://javafx.com/fxml">
    <padding>
        <Insets top="10" right="10" bottom="10" left="10"/>
    </padding>
    <spacing>10</spacing>

    <children>
        <TextField>
            <prefWidth>200</prefWidth>
        </TextField>

        <Button>
            <text>확인</text>
        </Button>
    </children>
</HBox>
  • AppMain 클래스
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class AppMain extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("root.fxml")); // ← FXML 로드
        Scene scene = new Scene(root);

        primaryStage.setTitle("AppMain");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

레이아웃(layout) 여백: 패딩(padding)과 마진(margin)

구분 HBox의 패딩 Button의 마진
개념 CP-17.3.3-padding.png CP-17.3.3-margin.png
자바 코드 HBox hbox = new HBox();
hbox.setPadding(new Insets(50));
Button button = new Button();
HBox.setMargin(button, new Insets(50));
FXML 태그 <HBox>
<padding>
<Insets topRightBottomLeft="50"/>
</padding>
</HBox>
<Button>
<HBox.margin>
<Insets topRightBottomLeft="50"/>
</Hbox.margin>
</Button>

  • Margin, Padding 설정 방법
// top, right, bottom, left를 모두 동일한 값으로 설정할 때
// Insets(double topRightBottomLeft)
new Insets(50);

// top, right, bottom, left를 다를 값으로 설정할 때
// Insets(double top, double right, double bottom, double left)
new Insets(10, 20, 30, 40);
  • 패딩과 마진 적용 예
패딩과 마진 적용 결과
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class AppMain extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        // 패딩 설정
        HBox hbox = new HBox();
        hbox.setPadding(new Insets(50, 10, 10, 50));
        Button button = new Button();
        button.setPrefSize(100, 100);
        
        // 마진 설정
        // HBox hbox = new HBox();
        // Button button = new Button();
        // button.setPrefSize(100, 100);
        // HBox.setMargin(button, new Insets(10, 10, 50, 50));

        hbox.getChildren().add(button);

        Scene scene = new Scene(hbox);

        primaryStage.setTitle("AppMain");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

FXML 작성 규칙

프로그램적 레이아웃 자바 코드 FXML 레이아웃 태그
HBox hbox = new HBox();
hbox.setPadding(new Inset(10, 10, 10, 10));
hbox.setSpacing(10);

<HBox xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets top="10" right="10" bottom="10" left="10"/>
</padding>
</HBox>
TextField textField = new TextField();
textField.setPrefWidth(200);
<TextField>
<prefWidth>200</prefWidth>
</TextField>
Button button = new Button();
button.setText("확인");
<Button>
<text>확인</text>
</Button>
ObsetvableList list = hbox.getChildren();
list.add(textField);
list.add(button);




<children>
<TextField>
<prefWidth>200</prefWidth>
</TextField>
<Button>
<text>확인</text>
</Button>
</children>

패키지(package) 선언

자바 코드 FXML 태그
import javafx.scene.layout.HBox; <?import javafx.scene.layout.HBox?>
import javafx.scene.control.*; <?import javafx.scene.control.*?>

  • <?import?>가 들어가는 위치는 <?xml ...>와 루트 컨테이너 태그 사이이다.
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.*?>

<루트 컨테이너 xmlns:fx="http://javafx.com/fxml">
...
</루트 컨테이너>
  • import를 제대로 하지 않으면 not a valid type 메시지와 함께 javafx.fxml.LoadException 발생

태그(tag) 선언

  • 시작 태그와 끝 태그가 매칭되어야 한다.
자바 코드 FXML
Button button = new Button();
button.setText("확인");
<Button>
<text>확인</text>
</Button>

속성(attribute) 선언

  • 속성은 "나 '로 감싸야 한다.
<태그이름 속성명="값" 속성명='값'> ... </태그이름>
  • 속성명은 Setter 메소드 명이 옴
  • 모든 Setter가 사용될 수 있는 것은 아님. 기본 타입(boolean, byte, short, char, int, long, float, double)의 값을 세팅하거나, String을 세팅하는 Setter만 올 수 있음
자바 코드 FXML (Setter 태그) FXML (Setter 속성)
Button button = new Button();
button.setText("확인");
<Button>
<text>확인</text>
</Button>
<Button text="확인"/>

객체 선언

클래스 속성
  • 생성자에 매개 변수가 있고, 매개 변수에 @NamedArg(javafx.beans.NamedArg) 어노테이션이 적용되어 있으면 속성명이나 자식 태그로 작성할 수 있음
<클래스 매개변수="값">

<클래스>
<매개변수>값</매개변수>
</클래스>

    • HBox를 패딩할 때 setPadding(Insets value) 메소드를 사용하는데
    • Insets는 기본 생성자가 없고,
    • Insets(double topRightBottomLeft) 또는 Insets(double top, double right, double bottom, double left)만 있음
    • 이 경우 아래와 같이 선언 가능함
프로그램적 레이아웃 자바 코드 FXML 레이아웃 태그
HBox hbox = new HBox();
hbox.setPadding(new Inset(10, 10, 10, 10));
hbox.setSpacing(10);

<HBox>
<padding>
<Insets top="10" right="10" bottom="10" left="10"/>
</padding>
</HBox>

클래스 fx:value
  • 클래스가 valueOf(String) 메소드를 제공하는 경우
<클래스 fx:value="값" />
기본 코드 FXML
String.valueOf("Hello, World!");
Integer.valueOf("1");
Double.valueOf("1.0");
Boolean.valueOf("false");
<String fx:value="Hello, World!"/>
<Integer fx:value="1"/>
<Double fx:value="1.0"/>
<Boolean fx:value="false"/>


클래스 fx:constant
  • 클래스에 정의된 상수값을 얻고 싶을 경우
<클래스 fx:constant="상수" />
기본 코드 FXML
Button button = new Button();
button.setMaxWidth( Double.MAX_VALUE );


<Button>
<maxWidth>
<Double fx:constant="MAX_VALUE"/>
</maxWidth>
<Button>

클래스 fx:factory
  • 어떤 클래스는 new 연산자로 객체를 생성할 수 없고,
  • 정적 메소드(이를 factory 메소드라 부른다)로 객체를 얻어야 하는 경우가 있음
<클래스 fx:factory="정적메소드" />
  • 예: ObservableList의 구현 객체는 javafx.collections.FXCollections의 정적 메소드인 observableArrayList(E... items) 메소드로 얻을 수 있다.
기본 코드 FXML
ComboBox combo = new ComboBox();
combo.setItems(FXCollections.observableArrayList("공개", "비공개"));





<ComboBox>
<Items>
<FXCollections fx:factory="observableArrayList">
<String fx:value="공개"/>
<String fx:value="비공개"/>
</FXCollections>
</items>
</ComboBox>

FXML 로딩과 Scene 생성

  • FXML 파일을 작성한 후 이를 이용하여 객체를 만들어야 한다. 이를 FXML loading이라고 한다.
  • javafx.fxml.FXMLLoader를 이용
    • 두 개의 load() 메소드 : 정적 메소드, 인스턴스 메소드
Parent root = FXMLLoader.load(getClass().getResource("xxx.fxml"));
  • getClass() - 현재 클래스 리턴
  • getResource() - 클래스가 위치하는 곳에서 상대 경로로 리소스의 URL을 리턴
  • load() - FXML 파일을 로딩
FXMLLoader loader = new FXMLLoader(getClass().getResource("xxx.fxml"));
Parent root = (Parent)loader.load();
  • load() - Parent 타입을 리턴함. 이것은 FXML 파일에서의 루트 태그로 선언된 컨테이너임
  • 만을 루트 태그가 <HBox> 라면 다음과 같이 작성해도 됨
HBox hbox = (HBox) FXMLLoader.load(getClass().getResource("xxx.fxml"));
  • 예제 코드
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class AppMain extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("root.fxml"));
        Scene scene = new Scene(root);

        primaryStage.setTitle("AppMain");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

JavaFX Scene Builder

Scene Builder로 root.fxml 띄움
  • Scene Builder 띄우는 방법
    • fxml 파일을 선택한다.
    • vscode에서 Ctrl-Shift-P를 누른다.

JavaFX 컨테이너

컨테이너 설명
AnchorPane 컨트롤을 좌표로 배치하는 레이아웃
BorderPane 위, 아래, 오른쪽, 왼쪽, 중앙에 컨트롤을 배치하는 레이아웃
FlowPane 행으로 배치하되 공간이 부족하면 새로운 행에 배치하는 레이아웃
GridPane 그리드로 배치하되 셀의 크기가 고정적이지 않은 레이아웃
StackPane 컨트롤을 겹쳐서 배치하는 레이아웃
TilePane 그리드로 배치하되 고정된 셀의 크기를 갖는 레이아웃
HBox 수평으로 배치하는 레이아웃
VBox 수직으로 배치하는 레이아웃

AnchorPane 컨테이너

CP-17.4.1-1.png

  • AnchorPane에서 사용할 수 있는 주요 설정
태그 및 속성 설명 적용
PrefWidth 폭을 설정 AnchorPane
PrefHeight 높이를 설정 AnchorPane
layoutX 컨트롤의 X 좌표 컨트롤
layoutY 컨트롤의 Y 좌표 컨트롤
<children> 컨트롤을 포함 AnchorPane

AnchorPane (Scene Builder)
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>


<AnchorPane xmlns:fx="http://javafx.com/fxml" prefHeight="150.0" prefWidth="300.0">
   <children>
      <Label layoutX="42.0" layoutY="28.0" text="아이디" />
      <Label layoutX="42.0" layoutY="66.0" text="패스워드" />
      <TextField layoutX="120.0" layoutY="24.0" />
      <PasswordField layoutX="120.0" layoutY="62.0" />
      <Button layoutX="97.0" layoutY="106.0" text="로그인" />
      <Button layoutX="164.0" layoutY="106.0" text="취소" />
   </children>
</AnchorPane>

HBox와 VBox 컨테이너

태그 및 속성 설명 적용
prefWidth 폭을 설정 HBox, VBox
prefHeight 높이를 설정 HBox, VBox
alignment 컨트롤의 정렬을 설정 HBox, VBox
spacing 컨트롤의 간격을 설정 HBox, VBox
fillWidth 컨트롤의 폭 확장 여부 설정 VBox
fillHeight 컨트롤의 높이 확장 여부 설정 HBox
<children> 컨트롤을 포함 HBox, VBox
<HBox.hgrow>
<Priority fx:constant="ALWAYS"/>
</HBox.hgrow>
HBox의 남은 폭을 채움 컨트롤
<VBox.vgrow>
<Priority fx:constant="ALWAYS"/>
</VBox.vgrow>
VBox의 남을 높이를 채움 컨트롤

  • root2.fxml
    • 이 패키지의 하위 폴더로 images를 만들고 여기에 javafx.png 파일을 복사해 넣어둔다.
      javafx.png
images 폴더
<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.Double?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.Priority?>


<VBox xmlns:fx="http://javafx.com/fxml">
   <padding>
      <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
   </padding>

   <children>
      <ImageView fitWidth="200.0" preserveRatio="true"> <!-- 그림의 비율에 맞게 높이를 설정 -->
        <image>
          <Image url="@images/javafx.png" /> <!-- 현재 경로 기준으로 상대경로로 파일 지정 -->
        </image>
      </ImageView>
      <HBox alignment="CENTER" spacing="20.0">
         <children>
            <Button text="이전" />
            <Button text="다음">
              <HBox.hgrow><Priority fx:constant="ALWAYS"/></HBox.hgrow> <!-- 오른쪽 남은 공간을 버튼이 모두 채우도록 설정 -->
              <maxWidth><Double fx:constant="MAX_VALUE"/></maxWidth> <!-- 버튼의 폭을 자동으로 확장하기 위해 설정-->
            </Button>
         </children>
         <VBox.margin>
           <Insets top="10.0" />
         </VBox.margin>
      </HBox>
   </children>
</VBox>
  • 예제
실행 결과
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class AppMain extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("root2.fxml"));
        Scene scene = new Scene(root);

        primaryStage.setTitle("AppMain");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

BorderPane 컨테이너

  • top, bottom, left, right, center 셀에 컨트롤을 배치하는 컨테이너
  • 다른 컨테이너를 배치할 수도 있음
  • 각 셀에는 하나의 컨트롤 또는 컨테이너만 배치할 수 있음

CP-17.4.4-1.png

태그 및 속성 설명 적용
prefWidth 폭을 설정 BorderPane
prefHeight 높이를 설정 BorderPane
<top> top에 배치될 컨트롤을 포함 BorderPane
<bottom> bottom에 배치될 컨트롤을 포함 BorderPane
<right> right에 배치될 컨트롤을 포함 BorderPane
<left> left에 배치될 컨트롤을 포함 BorderPane
<center> center에 배치될 컨트롤을 포함 BorderPane

실행 결과
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>


<BorderPane prefHeight="200.0" prefWidth="300.0" xmlns:fx="http://javafx.com/fxml">
   <top>
      <ToolBar prefHeight="40.0" prefWidth="200.0" BorderPane.alignment="CENTER">
        <items>
          <Button text="Button" />
          <Button text="Button" />
        </items>
      </ToolBar>
   </top>
   <center>
      <TextArea /> <!-- left와 right까지 확장-->
   </center>
   <bottom>
      <BorderPane>
         <center>
           <TextField /> <!-- top, bottom, left까지 확장-->
         </center>
         <right>
            <Button text="Button" />
         </right>
      </BorderPane>
   </bottom>
</BorderPane>

FlowPane 컨테이너

태그 및 속성 설명 적용
prefWidth 폭을 설정 FlowPane
prefHeight 높이를 설정 FlowPane
hgap 컨트롤의 수평 간격을 설정 FlowPane
vgap 컨트롤의 수직 간격을 설정 FlowPane
<children> 컨트롤을 포함 FlowPane

실행 결과
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.FlowPane?>


<FlowPane prefHeight="70.0" prefWidth="300.0" xmlns:fx="http://javafx.com/fxml">
   <padding>
      <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
   </padding>

   <children>
      <Button text="Button" />
      <Button text="Button" />
      <Button text="Button" />
      <Button text="Button" />
      <Button text="Button" />
      <Button text="Button" />
   </children>
</FlowPane>

TilePane 컨테이너

태그 및 속성 설명 적용


GridPane 컨테이너

태그 및 속성 설명 적용


StackPane 컨테이너

태그 및 속성 설명 적용

JavaFX 이벤트 처리

JavaFX 속성 감시와 바인딩

JavaFX 컨트롤

JavaFX 메뉴바(MenuBar)와 툴바(Toolbar)

JavaFX 다이얼로그(Dialog)

JavaFX CSS 스타일

JavaFX 스레드 동시성

화면 이동과 애니메이션