JavaFx - 계산기

1. JavaFx의 개요

 

#AWT

 Java 1버전 때부터 AWT(Abstract window Toolkit)이라는 API를 제공해왔다. 하지만 이 API는 운영체제가 제공하는

네이티브 UI 컴포넌트를 이용했기 때문에 운영체제에 따라 UI의 모양이 서로 달랐고, 종류도 제한적이었다.

레이아웃 잡기도 힘들었으며 종류가 제한적이다보니 만들 수 있는 것도 다양하지 못했다.

 

#Swing

  AWT의 운영체제에 따라 UI모양이 달랐던 단점을 해결한 API로서 모든 운영체제에서 동일한 UI를 갖도록 자신만의 UI를 가질 수 있게되었다. 하지만 리소스를 너무많이 사용했다. 운영체제 자바에서 모든 UI를 모두 드로잉하다보니 2000년대 초반 컴퓨터로는 메모리글 너무 많이 잡아먹는 상황이었다.

 

 또한 운영체제가 버전업이 되면서 운영체제가 제공하는 UI가 상당히 이뻐져서 Swing에서 제공하는 UI가 디자인적으로 좋은 평가를 받지 못했다.

 

#JavaFx

 JavaFx는 데스크탑 뿐만 아니라 임베디드 시스템에도 적용할 수 있도록 가볍게 엔진을 만들었다. 가벼운 엔진임에도 풍부한 UI를 제공하며 레이아웃, 스타일, 애플리케이션 로직을 분리하여 개발 할 수 있다보니 협업하는 개발에서 큰 장점을 지닌다.  Swing같은 경우는 분리해서 작업이 불가능했지만 JavaFx는 분리해서 개발 할 수 있다는 장점을 지닌다.

 

2.JavaFx 개발

 제일 먼저 해야하는 작업은 JavaFx 라이브러리 내에 있는 Application을 상속하고 나서 start를 재정의 하는 것이다.

start 메소드는 UI를 생성하고 UI창을 실행시켜주는 역할을 한다. 

 

start는 직접호출할 수 없고 메인메소드에서 launch를 호출함으로써 내부적으로 start를 호출하게 만들어줘야한다.

그 이유는 Application은 UI를 생성하는 별도의 JavaFX Application 스레드가 존재하기 때문이다.

 

따라서  Main을 실행시켜주는 Main 스레드 이외에 UI생성용 스레드를 launch 메소드 안에서 만들고 자바 FX Application 스레드가 start를 호출할수 있게끔 하고 있다. 그렇기 때문에 Main메소드에서 start 메소드를 호출하면 안된다.

 

3. JavaFX 라이프 사이클

1. Application이라는 클래스는 추상클래스 이기 때문에 반드시 start를 재정의한다.

2. 클래스가 JVM에서 실행되기 위해서 main 메소드를 작성한다.

3. Main메소드안에 Application이 가지고 있는 정적 메소드인 launch를 호출한다.

4. launch 메소드는 내부적으로 JavaFx Application 스레드를 만든다고 했고 그 스레드가 start메소드를 호출한다.

5. primaryStage는 처음 실행했을 때 첫 window에 해당한다.

6. primaryStage.show(); 하면 창이하나 출력된다.

main 메소드에서 launch 메소드를 호출하고 

lauch메소드가 호출이되면 Main 클래스의 기본생성자가 호출되면서 main객체가 생성된다.

Main객체가 생성된 이후에는 자동으로 init이라는 메소드를 호출하게된다.

 

init 메소드는 Application 클래스가 가지고 있는 메소드로서 상속을 받았기 때문에 init이 호출이 되어도 예외가 발생하지 않는 것이다.

 

init메소드 다음에는 start메소드가 호출되고 호출되고 나면 window를 사용할 수 있게 되는 것이다.

 

window를 종료할때는 Platform.exit 메소드를 호출하거나 windows x버튼을 누르면 stop이라는 메소드가 자동호출된다.

 

필요에 따라서 init이라는 메소드나 stop 메소드를 재정의해서 사용할 수 있다.

 

 

4.JavaFx 프로그래밍 예제 ( 계산기 )

1. Main.java

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {
    public Main(){ // 기본생성자 선언
        System.out.println(Thread.currentThread().getName() + ": Main()호출");
    }

    @Override
    public void init() throws Exception {
        System.out.println(Thread.currentThread().getName() + ":init()호출"); // init메소드는 자바FX런처가 실행함
    }

    @Override
    public void start(Stage primaryStage) throws Exception{ //start 메소드 : ui창을 생성하고 실행하는 메소드
                                                            //Stage라는 용어가 있는데 극장에는 무대가 있는 것처럼 윈도우를 Stage라는 용어로쓰고있음
        Parent root = FXMLLoader.load(getClass().getResource("design.fxml")); //현재 클래스 위치에서 fxml 파일을 로드
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }


    public static void main(String[] args) { //메인 메소드에서 Application이 가지고있는 정적메소드인 launch메소드를 호출해서 내부적으로 start메소드를 호출하게 해야함
        launch(args);                         //그 이유는 JavaFx Application은 UI를 생성하는 스레드가 별도로 존재함. 그게 JavaFx Appliaction 스레드이다.
                                            //그 스레드를 launch안에서 만들어서 JavaFx Appliacation 스레드가 start를 호출할 수 있게끔 작성해야함
    }
}

 

2. Model.java

package sample;

public class Model { // MVC 디자인 패턴 , 연산을 처리하는 단위

    public int calculate(String operator, int x, int y){
        if(operator.equals("+")){
        return x+y;
        } else if(operator.equals("-")){
            return x-y;
        } else if(operator.equals("*")){
            return x*y;
        }else{
            return x/y;
        }
    }
}

 

 

3. design.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>


<VBox fx:controller="sample.Controller" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="380.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <StackPane prefHeight="150.0" prefWidth="200.0">
         <children>
            <Rectangle arcHeight="5.0" arcWidth="5.0" fill="#1f93ff26" height="80.0" stroke="BLACK" strokeType="INSIDE" width="315.0" />
            <Text fx:id="result" strokeType="OUTSIDE" strokeWidth="0.0" text="0" textAlignment="RIGHT" wrappingWidth="286.587890625">
               <font>
                  <Font name="Arial Black" size="25.0" />
               </font>
            </Text>
         </children>
      </StackPane>
      <HBox alignment="TOP_CENTER" prefHeight="70.0" prefWidth="200.0">
         <children>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="7" onAction="#operand">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
               <HBox.margin>
                  <Insets right="10.0" />
               </HBox.margin>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="8" onAction="#operand">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
               <HBox.margin>
                  <Insets right="10.0" />
               </HBox.margin>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="9" onAction="#operand">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
               <HBox.margin>
                  <Insets right="10.0" />
               </HBox.margin>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="/" onAction="#operator">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
            </Button>
         </children>
         <VBox.margin>
            <Insets bottom="10.0" />
         </VBox.margin>
      </HBox>
      <HBox alignment="TOP_CENTER" prefHeight="70.0" prefWidth="200.0">
         <children>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="4" onAction="#operand">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
               <HBox.margin>
                  <Insets right="10.0" />
               </HBox.margin>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="5" onAction="#operand">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
               <HBox.margin>
                  <Insets right="10.0" />
               </HBox.margin>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="6" onAction="#operand">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
               <HBox.margin>
                  <Insets right="10.0" />
               </HBox.margin>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="*" onAction="#operator">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
            </Button>
         </children>
         <VBox.margin>
            <Insets bottom="10.0" />
         </VBox.margin>
      </HBox>
      <HBox alignment="TOP_CENTER" prefHeight="70.0" prefWidth="200.0">
         <children>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="1" onAction="#operand">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
               <HBox.margin>
                  <Insets right="10.0" />
               </HBox.margin>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="2" onAction="#operand">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
               <HBox.margin>
                  <Insets right="10.0" />
               </HBox.margin>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="3" onAction="#operand">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
               <HBox.margin>
                  <Insets right="10.0" />
               </HBox.margin>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="-" onAction="#operator">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
            </Button>
         </children>
         <VBox.margin>
            <Insets bottom="10.0" />
         </VBox.margin>
      </HBox>
      <HBox alignment="TOP_CENTER" prefHeight="70.0" prefWidth="200.0">
         <children>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="140.0" text="0" onAction="#operand">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
               <HBox.margin>
                  <Insets right="10.0" />
               </HBox.margin>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="=" onAction="#operator">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
               <HBox.margin>
                  <Insets right="10.0" />
               </HBox.margin>
            </Button>
            <Button mnemonicParsing="false" prefHeight="60.0" prefWidth="60.0" text="+" onAction="#operator">
               <font>
                  <Font name="Arial Black" size="20.0" />
               </font>
            </Button>
         </children>
      </HBox>
   </children>
</VBox>​

 

4. controller.java

package sample;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.text.Text;

public class Controller {

    private String operator = "";
    private int x=0;
    private Model model = new Model();

    @FXML
    private Text result;


    @FXML
    public void operand(ActionEvent event) {
        result.setText(result.getText() + ((Button)event.getSource()).getText());
    }

    @FXML
    public void operator(javafx.event.ActionEvent event) {
        if( ((Button) event.getSource()).getText().equals("=")){
            result.setText(model.calculate(operator,x, Integer.parseInt(result.getText())) +"");
        }else{
            operator = (((Button) event.getSource()).getText());
            x = Integer.parseInt(result.getText());
            result.setText("");
        }
    }
}​

 

<결과물>