JavaFX 액션 이벤트 사용

우선 가장 기본적인 GUI인 입력 필드와 푸시 버튼을 사용하여 버튼 클릭시 이벤트 처리(액션 이벤트)에 대해 설명한다. 또한 Java 8의 “람다 식"을 사용하여 구현에 대해 설명한다.

TextField와 Button

GUI의 기본이라고 하면, 역시 “사용자 입력"과 “명령 실행"일 것이다. 이것들에 대해 일반 응용 프로그램에서 가장 친숙한 것을 뽑는다면 “입력 필드"와 “푸시 버튼"이 있다. 이번에는 이것에 이용하여 보자.

TextField에 대해서

우선, 입력 필드이다. 가장 많이 사용되는 것은 텍스트 한 줄 입력을 위한 거시다. 이것은 javafx.scene.control 패키지 “TextField"라는 클래스로 되어 있다. 이 클래스는 다음과 같이 인스턴스를 생성한다.

new TextField()
new TextField(초기 )

인수없이 new하는 것이 기본이다. String 인수를 지정하면, 해당 텍스트를 초기 값으로 필드에 설정 해준다. 입력된 텍스트는 다음과 같이 주고 받을 수 있다.

텍스트 얻기

String 변수 = textField.getText();

텍스트 변경

textField.setText(텍스트);

이 밖에 TextField에는 “프롬프트 텍스트(Prompt Text)“라는 것도 사용할 수 있다. 이 필드에 아무것도 쓰여지지 않고 선택도 되지 않을 때 회색으로 메시지를 표시하는 것이다. 흔하게 입력 필드 등에서 “이름 입력”, “주소 입력” 등과 같이 가이드가 되는 텍스트가 흐리게 표시되어 있는 것을 본적이 있을 것이다. 그와 같다.

프롬프트 텍스트 설정

textField.setPromptText(텍스트);

Button에 대해서

푸시 버튼은 javafx.scene.control 패키지 “Button"이라는 클래스로 되어 있다. 이것도 다음과 같이 인스턴스를 생성할 수 있다.

new Button()
new Button(표시 텍스트)

인수에 텍스트로 String를 지정하고 new로 생성을 하면, 그 텍스트를 버튼에 표시한다. 푸시 버튼을 사용하는 경우에는 “전송” 또는 “클릭"과 같이 버튼에 텍스트를 일반적으로 표시하기 때문에, 이 텍스트를 입력하는 것이 기본이라고 생각해도 좋을 것이다. 또한 TextField와 마찬가지로 getText/setText 표시 텍스트를 조작하는 것도 가능하므로 new Button()으로 버튼을 생성하고, 이후에 표시 텍스트를 설정할 수도 있다.

아래 소스 코드 예제는 Label, TextFieldButton 같은 GUI를 사용하는 응용 프로그램이다. 각각 인스턴스 생성 후에 BorderPane에 넣어서 표시하고 있다.

package com.devkuma.javafx;
 
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
 
public class App extends Application {
    Label label;
    TextField field;
    Button button;
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) throws Exception {
        label = new Label("This is JavaFX!");
        field = new TextField();
        button = new Button("Click");
        BorderPane pane = new BorderPane();
        pane.setTop(label);
        pane.setCenter(field);
        pane.setBottom(button);
        Scene scene = new Scene(pane, 320, 120);
        stage.setScene(scene);
        stage.show();
    }
 
}

setOnAction와 EventHandler

푸시 버튼 (Button)은 단지 표시를 위한 것이 아니라, 클릭하여 어떤 처리를 수행하기 위해 사용한다. 이 처리는 Button에 포함되어 있는 “이벤트"를 이용하여 실행한다.

이벤트란 다양한 조작을 하고 프로그램의 상태가 변화하거나 했을 때 발생하는 신호와 같은 것이다. 각각의 컨트롤(TextField 및 Button 등)는 어떤 이벤트에 해당하는지 정해져 있어서 그 이벤트 처리를 위한 기능을 수행하는 메소드를 포함하고 있다.

Button 컨트롤을 클릭했을 때의 이벤트는 “액션 이벤트"라고 한다. 이는 그 컨트롤이 가장 일반적인 형태로 이용되는 경우에 처리를 위한 이벤트이다. 예를 들면, 푸시 버튼이라면 “클릭"이라는 동작이 가장 기본적인 조작일 것이다. 이러한 조작을 할 때에 액션 이벤트라는 것이 발생하게 된다.

액션 이벤트는 Button에 포함되어 있는 다음과 같은 메소드를 사용하여 설정할 수 있다.

button.setOnAction("EventHandler");

발생한 이벤트의 처리는 javafx.event 패키지에 있는 “EventHandler"인터페이스라는 것을 사용한다.

EventHandler의 구조

EventHandler에는 이벤트가 발생했을 때 호출되는 메소드가 미리 준비되어 있다. 이 메소드를 재정의(override)함으로써 해당 이벤트 발생시 처리를 구현할 수 있다. 이것은 다음과 같은 형태로 사용할 수 있다.

public void handle (Event e) {
    // 여기에 처리를 준비한다
}

이용시에는 EventHandler를 implements 한 클래스를 준비하거나, 익명 클래스(Anonymous Class)으로 new를 해서 인스턴스 생성하여 인수로 메소드에 넣는 방식으로 구현하게 될 것이다.

EventHandler와 handleEvent

EventHandler는 하나의 메소드만 포함된 단순한 인터페이스이다. public void handle(Event e)라는 메소드를 정의하는 것만으로 필요한 처리를 할 수 있다. 다만, 이를 그대로 사용되는 경우는 거의 없다.

EventHandler에 있는 handle 메소드에는 발생한 이벤트를 “Event"라는 클래스의 인스턴스 인수로 받을 수 있게 되어 있다. 이 Event는 이벤트의 종류마다 많은 서브 클래스를 가지고 있으며, EventHandler에서는 어떤 이벤트용 Event 하위 클래스를 전달되는지를 전부를 한데 모아 설정할 수 있도록 되어 있다.

예를 들어, 익명 클래스(Anonymous Class)를 이용하여 EventHandler를 생성하는 경우에는 다음과 같이 작성할 것이다.

new EventHandler() {
    @Override
    public void handle (Event e) {
        // 여기에 처리 코드를 작성한다.
    }
}

액션 이벤트는 ActionEvent라는 Event의 서브 클래스가 인수로 전달된다. setOnAction는 다음과 같이 작성하는 것이 일반적이다.

new EventHandler<ActionEvent>() {
    public void handle (ActionEvent e) {
        // 여기에 처리 코드를 작성한다.
    }
}

일반적은 형태로 ActionEvent를 지정하고 handle 메소드에는 ActionEvent가 인수로 전달되도록 작성한다. 이것으로 액션 이벤트를 처리하는 EventHandler를 만들 수 있게 되었다.

그러면 실제로 ButtonEventHandler를 지정한 이벤트 처리 예제를 만들어 보자.

package com.dekuma.spring;
 
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
 
public class App extends Application {
    Label label;
    TextField field;
    Button button;
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) throws Exception {
        label = new Label("This is JavaFX!");
        field = new TextField();
        button = new Button("Click");
        // 엑션 이벤트 처리 지정
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent e) {
                String msg = "you typed: " + field.getText();
                label.setText(msg);
            }
        });
        BorderPane pane = new BorderPane();
        pane.setTop(label);
        pane.setCenter(field);
        pane.setBottom(button);
        Scene scene = new Scene(pane, 320, 120);
        stage.setScene(scene);
        stage.show();
    }
 
}

표시된 버튼을 클릭하면 상단 필드에 입력한 텍스트를 꺼내 메시지를 라벨에 표시하는 예제이다. setOnAction에서 new EventHandler<ActionEvent>()을 설정하고있는 것을 알 수 있을 것이다.

람다 식으로 더 간결하게!

이것으로 Button을 클릭했을 때의 액션 이벤트는 완성이다. 하지만 익명 클래스를 이용한 쓰기는 좀 귀찮다. 이를 좀 더 간단하게 사용 방법을 설명한다.

EventHandler 인터페이스에는 handle라는 메소드가 하나 포함되어 있다. 이러한 “메소드 하나밖에 없는 인터페이스"는 Java8에서는 “함수 인터페이스"라고 한다. 이것은 함수 객체 (함수를 값으로 처리하는 것)의 대용품(?)처럼 처리할 수 있도록 되어 있다.

그것을 가능하게 하는 것이 “람다 식"이다. Java 8에서 람다 식을 사용한 적이 없는 사람도 많을 것이니 간단하게 정리해 보도록 하겠다.

람다 표현식은 함수 인터페이스의 익명 클래스에 인한 인스턴스 구현을 매우 간단하게 작성 할 수 있도록 한 기능이다. 이 setOnAction 예로 들어보면, 다음과 같이 변경 가능하다.

일반적인 작성

button.setOnAction(new EventHandler<ActionEvent>() {
    public void handle(ActionEvent e) {
        // 여기에 처리 작성
    }
});

람다 식 작성

button.setOnAction((ActionEvent) -> {
    // 여기에 처리 작성
});

매우 심플하게 되는 것을 알 수 있을 것이다. 람다 식에는 메소드를 작성할 필요가 없다. 함수 인터페이스의 경우에는 호출 메소드가 하나 밖에 없기 때문에, 그 메소드가 호출 될 때 자동으로 판단하게 된다.

아래 예제는 이전 샘플을 람다 식으로 변경하여 작성되었다.

package com.devkuma.javafx;
 
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
 
public class App extends Application {
    Label label;
    TextField field;
    Button button;
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) throws Exception {
        label = new Label("This is JavaFX!");
        field = new TextField();
        button = new Button("Click");
        // 액션 이벤트 처리 지정
        button.setOnAction((ActionEvent)-> {
            String msg = "you typed: " + field.getText();
            label.setText(msg);
        });
        BorderPane pane = new BorderPane();
        pane.setTop(label);
        pane.setCenter(field);
        pane.setBottom(button);
        Scene scene = new Scene(pane, 320, 120);
        stage.setScene(scene);
        stage.show();
    }
 
}

이벤트 처리의 구현 부분이 매우 알기 쉽게 되어 있는가? 모처럼 Java 8에서 JavaFX를 사용해야 하니, 꼭 람다 식을 이용한 쓰는 법을 기억두도록 하자.




최종 수정 : 2017-09-19