JavaFX FXML 액션 이벤트 구현

JavaFX는 다양한 방식으로 이벤트 처리를 구현할 수 있다. 여기에서는 그 기본으로 “태그에 의한 구현"과 “컨트롤러에 의한 구현 “에 대해 설명한다.

<fx:script>으로 이벤트 처리 생성

FXML에는 XML 기반으로 GUI를 만들 수 있다. 하지만 실제로 만들 때에 “이것으로 이벤트 처리는 어떻게 만드는거야?“라고 생각한 사람도 있을지도 모른다. XML는 Java로 부터 분리된 형태로 GUI를 만드는 것이기 때문에, 그것도 Java에서 실행되는 코드에 연결하는 구조가 필요하다.

여기에는 몇 가지 방법이 있다. 우선 가장 간단한 <fx:script> 태그를 사용한 구현"부터 해보록 하자.

FXML에는 사실 이벤트 처리 코드까지 작성할 수 있다. 이는 다음과 같은 형태가 된다.

<fx:script>
function 함수(인수) {
    // 처리를 기술
}
</fx script>

이와 같은 형태로 수행할 처리를 함수로 지정 정의한다. 그리고 이 함수를 사용하는 컨트롤에 속성으로 지정한다. 액션 이벤트라면

onAction="함수(인수);"

이런 식으로 컨트롤의 태그에 속성을 작성한다. 이렇게 하면 컨트롤에서 액션 이벤트가 발생하면 지정된 함수를 호출 실행 되도록 한다.

“그런데 function ……는, Java에서는 찾을 수가 없다"라고 생각할 수 있다. 실은 이것은 Java가 아니라 “JavaScript 함수"이다. 즉, FXML를 사용하면, Java를 사용하지 않고, JavaScript으로 이벤트 처리를 작성할 수 있는 것이다. 약간의 처리를 빠르게 구현하고자 한다면 매우 뛰어난 방식이다.

<fx:script>에서 액션을 구현

그러면, 실제로 <fx:script> 태그를 사용한 액션 이벤트의 처리를 만들어 보자. 전에 사용한 프로젝트를 그대로 작성을 변경하는 식으로 해보기로 한다.

FXML 파일 (app.fxml)를 열고, 아래와 같이 소스 코드를 재 작성하자. 그 외에 다른 것 (Java 소스 코드 등)는 일체 변경할 필요가 없다.

<?xml version="1.0" encoding="UTF-8"?>
 
<?language javascript?>
<?import java.lang.*?>
<?import java.net.URL ?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
 
<BorderPane xmlns="http://javafx.com/javafx"
        xmlns:fx="http://javafx.com/fxml">
    <fx:script>
    function doAction(event){
        var str = field1.getText();
        str = "당신은 쓴 글은 '" + str + "' 이것입니다.";
        label1.setText(str);
    }
    </fx:script>
    <stylesheets>
        <URL value="@app.css" />
    </stylesheets>
    <top>
        <Label fx:id="label1" text="This is FXML!" />
    </top>
    <center>
        <TextField fx:id="field1" />
    </center>
    <bottom>
        <Button onAction="doAction(event);" text="Click" />
    </bottom>
</BorderPane>

이제 실행해 보자.

이 예제에는 Button을 클릭하면 TextField에 입력된 텍스트를 추출하여 Label에 간단한 메시지를 표시한다. 아주 간단한 것이지만, 액션 구현의 기본은 알 것이다. 여기에서는 몇 가지 포인트가 있다. 순서대로 정리하여 보겠다.

<?language javascript?>

처음에 이와 같은 태그가 추가되어 있다. 이것은 <fx:script>으로 작성하는 코드가 JavaScript임을 지정하는 것이다. “그럼 다른 언어도 사용할 수 있나?“라고 묻는 다면, 물론 사용할 수 있다.

Groovy나 Clojure 등, Java 가상 머신으로 동작하는 스크립트 언어로 구현된 것이 있으면 이를 지정할 수도 있다. 하지만 Java8에 표준으로 내장되어있는 것은 JavaScript 뿐이므로, 우선은 이것을 사용하는 것을 생각하자.

<fx:script>

<BorderPane> 태그 안에 <fx:script> 태그가 포함되어 있다. FXML에서는 Pane 태그가 루트 태그로 작성된 때문에, <fx:script> 태그는 반드시 그 안에 포함되어 있어야 한다. 이 <fx:script> 태그에는 극히 일반적인 JavaScript 스크립트가 적혀 있다.

주목해 주었으면 것은 여기에서 사용하고있는 객체이다. “label1”, “text1"라는 객체가 이용되고 있지만, 이것들은 모두 다음에 작성된 <Label><TextField>의 “fx:id” 이름임을 알 수 있다.

<fx:script>에 작성된 스크립트 내에서 컨트롤는 fx:id로 지정된 이름의 변수로 액세스 할 수 있도록 되어 있다. 그래서 사용하는 컨트롤에는 반드시 fx:id를 달고 있어야 한다. 이 점만 지킨다면 스크립트 내에서 자유롭게 컨트롤을 사용할 수 있다.

getText과 setText

스크립트 내에는 getText으로 텍스트를 얻고, setText으로 설정하고 있다. 이것으로 이미 본 적이 있는 메소드이다. 그렇다 Java 소스 코드 내에서 컨트롤의 인스턴스를 사용할 때 사용했었다.

스크립트에서 사용되는 컨트롤의 객체도 Java 인스턴스와 전부 동일한 메소드를 가지고 있어, 같은 감각으로 그것들을 호출 할 수 있다. 새롭게 기억 할 필요는 전혀 없다

스크립트 외부 파일에 대해

여기에서는 <fx:script> 태그에 스크립트를 작성했지만 스크립트가 길어지면 다른 파일에 스크립트를 분리하는 것이 좋다. 이러한 경우는 “source"라는 속성을 지정한다. 예를 들면,

<fx:script source="script.js"/>

이런 형태로 작성하면 FXML 파일과 같은 위치에 있는 “script.js"를 로드하여 사용할 수 있게 된다. 실행하는 스크립트가 길어지게 되면 source를 사용하여 다른 파일으로 분리해 보도록 하자.

FXML으로 컨트롤러 사용

JavaScript는 손쉽고 편리하지만, 아무래도 구체적인 처리는 Java로 쓰고 싶다는 사람이 당연히 많을 것이다. 그러한 경우는 Java 클래스에 처리를 정의해 두고, 그것을 FXML의 컨트롤에 할당 할 수 있다.

이러한 “구체적인 이벤트 처리하는 클래스"를 일반적으로 컨트롤러라고 한다. 컨트롤러 클래스는 기본적으로 POJO(Pure Old Java Object)이며, 어떤 클래스도 상속하지 받지 않은 간단한 클래스로 정의한다.

컨트롤러는 FXML의 Pane 클래스에 “fx:controller"라는 속성으로 지정한다. 이렇게 하면 해당 클래스가 컨트롤러로 구성되어 그 클래스에있는 메소드를 그대로 doAction 등의 이벤트 처리에 대한 속성에 지정할 수 있게됩니다.

그럼, 컨트롤러는 만들어 보자. 우선 FXML를 다시 작성해야 한다. app.fxml을 아래와 같이 수정한다.

<?xml version="1.0" encoding="UTF-8"?>
 
<?language javascript?>
<?import java.lang.*?>
<?import java.net.URL ?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
 
<BorderPane xmlns="http://javafx.com/javafx"
        xmlns:fx="http://javafx.com/fxml"
        fx:controller="com.devkuma.javafx.AppController">
    <stylesheets>
        <URL value="@app.css" />
    </stylesheets>
    <top>
        <Label fx:id="label1" text="This is FXML!" />
    </top>
    <center>
        <TextField fx:id="field1" />
    </center>
    <bottom>
        <Button fx:id="btn1" onAction="#doAction" text="Click" />
    </bottom>
</BorderPane>

이번에는 AppController라는 컨트롤러 클래스를 만들어 사용하는 형태로 되어 있다. Pane 태그를 보면,

<BorderPane xmlns="http://javafx.com/javafx" 
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="com.devkuma.javafx.AppController">

이와 같이 작성되어 있다. fx:controller=“com.devkuma.javafx.AppController"에 의해 이 AppController 클래스가 컨트롤러로 지정된다.

또한 <Button> 태그를 보면 onAction 속성이 미묘하게 수정이 되었다는 것을 알 수 있을 것이다.

<Button onAction="#doAction" text="Click" />

컨트롤러의 메서드는 “#메서드 이름"형태로 지정한다. 그러면 AppController 클래스의 doAction 메소드가 이 Button의 액션 이벤트 처리에 설정된다.

컨트롤러 구현

그럼, 컨트롤러 클래스를 작성하자. 이번에는 com.devkuma.javafx 패키지에 AppController.java라는 파일명으로 파일을 생성한다. 그래고 아래와 같이 소스 코드를 작성한다.

package com.devkuma.javafx;
 
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
 
public class AppController {
    @FXML Label label1;
    @FXML TextField field1;
     
    @FXML
    protected void doAction(ActionEvent ev){
        String str = field1.getText();
        str = "당신은 쓴 글은 '" + str + "' 이것입니다.";
        label1.setText(str);
    }
}

이것으로 이전의 <fx:script> 태그를 사용한 것과 같은 효과를 낼 수 있다.

그럼 이것도 포인트를 데리러 해설 해 둡시다.

그럼 소스 코드의 주요하게 봐야 하는 포인트를 살펴 보겠다.

@FXML Label label1;
@FXML TextField field1;

클래스에는 Label와 TextField가 인스턴스 필드로 사용되고 있다. 그런데 이 변수들에 대한 인스턴스를 할당하는 과정은 어디에도 찾아 볼수 없다. 이는 “@FXML"라는 어노테이션을 붙이는 것으로, FXML에 같은 이름의 fx:id의 인스턴스가 바인딩 된다.

@FXML
protected void doAction (ActionEvent ev) {...}

액션 메소드도 역시 @FXML 어노테이션이 있어서 바인딩 된다. 메소드는 protected 접근자로 해야 하므로 주의하도록 하자. 또한 Event 클래스의 인스턴스를 인수로 제공한다. 액션 이벤트라면 ActionEvent 인스턴스가 전달된다.

이 후에는 특별히 주의해야 할 점은 없다. @FXML을 지정한 필드에 인스턴스를 할당되기 때문에, Java 코드에서 그것들을 조작하는 처리를 하도록 평범하게 작성하면 된다. 이제 Java로 작성하게 되서 편하게 되었다.

setOnAction에서 액션 이벤트를 구현

컨트롤러를 사용한 액션 이벤트의 구현은 매우 간단하지만, FXML 측에 onAction을 작성을 해야 한다. AWT와 Swing에서는 Java 코드에서 이벤트 처리를 구현하는 것이 기본이었다.

이전에 FXML를 사용하지 않고, Application 클래스에서 모든 처리를 작성한 경우에는 setOnAction메소드 액션 이벤트를 통합 할 수 있었다. FXML와 컨트롤러를 사용하는 경우에도 이 점은 동일하다. 그러나 setOnAction을 하는 것이 Application 클래스가 아닌 컨트롤러 측이 된다는 점이 다르다.

그럼 이 방식으로도 구현을 해보자. 먼저 FXML의 <Button> 태그에서 onActio 속성을 제거하고, 컨트롤러 클래스의 소스 코드를 아래와 같이 변경을 하자.

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML TextField field1;
    @FXML Button btn1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        btn1.setOnAction((AtionEvent)->{
            String str = field1.getText();
            str = "당신은 쓴 글은 '" + str + "' 이것입니다.";
            label1.setText(str);
        });
    }
 
}

이를 실행해서 버튼을 클릭해 보면, 처리가 제대로 수행되는 것을 확인 할 수 있다.

이 예제에서는 이전에 컨트롤러와는 미묘하게 클래스의 정의가 변화한 것을 알 수 있을 것이다. javafx.fxmlInitializable라는 인터페이스라 implements되어 있다. 이것는 FXML의 초기화 처리에 관한 기능을 부가하기 위한 것이다.

클래스에는 initialize 메소드가 추가되어 있다. 여기에 FXML 관련된 초기화 처리를 작성한다. 이 메소드가 호출되는 시점에는 @FXML 어노테이션이 붙은 인스턴스 필드가 생성되어 할당되어 있으므로, 그대로 Button의 setOnAction를 호출하여 액션 이벤트를 할당 할 수 있다.

우선, 이것으로 FXML 기반으로 액션 이벤트를 구현할 수 있게 되었다. 실제로 여러가지 처리를 동작 시켜보고, 동작을 확인해 보도록 하자.




최종 수정 : 2017-09-19