JavaFX 속성 이벤트 처리

JavaFX 컨트롤에는 값을 관리하는 속성에 전용 클래스가 제공되어 있으며, 거기에 이벤트 리스너를 설정하여 값이 변경시에 처리할 수 있다. 여기에서는 ToggleGroup, ComboBox, Slider에 대한 속성의 이벤트 처리 방식을 설명한다.

ToggleGroup의 ChangeListener처리

이전에 ListeView 대해 선택 상태가 변경 되었을 때의 이벤트 처리를 만들었다. 이것은 선택 상태를 관리하는 속성에 이벤트 리스너를 설정하였다.

이러한 “속성이 변경되었을 때에 ChangeListener에서 이벤트 처리를 한다"는 방식은, JavaFX 컨트롤의 기본적인 이벤트 처리 방식이다. 이 기본 개념을 알면 다른 컨트롤러에서도 유사한 방식으로 이벤트 처리를 설정할 수 있다.

우선 “라디오 버튼을 조작했을 때의 이벤트 처리"에서 생각해 보자. 라디오 버튼은 ToggleGroup라는 그룹 관리 클래스를 사용하여 여러 라디오 버튼을 하나로 관리하고 있었다. 라디오 버튼이 선택되면 이 ToggleGroup의 선택 상태를 나타내는 속성 값이 변경된다.

이 속성은 selectedToggleProperty라는 것으로, ReadOnlyObjectProperty 클래스의 인스턴스가 설정되어 있다. 이에 addListenerChangeListener를 설정함으로써 선택이 변경되었을 때 처리 할 수 있도록 되어 있다.

그러면 실제로 간단한 샘플을 만들어 보자.

<?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.*?>
<?import javafx.collections.FXCollections?>
<?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>
        <VBox>
            <fx:define>
                <ToggleGroup fx:id="group1" />
            </fx:define>
            <RadioButton text="Male" toggleGroup="$group1" userData="남자" selected="true" />
            <RadioButton text="Female" toggleGroup="$group1" userData="여자"/>
        </VBox>
    </center>
    <bottom>
    </bottom>
</BorderPane>

먼저 FXML에서 라디오 버튼을 제공한다. 위와 같은 형태로 ToggleGroup을 포함하는 2개의 라디오 버튼이 있다. 여기에서는 “userData"라는 속성이 포함되어 있다. 이것은 라디오 버튼에 자신의 데이터를 갖게 위한 것으로, 나중에 사용하기 때문에 작성해 두자. 그 외에는 특히 아무것도 특별한 것은 하지 않는 일반 라디오 버튼이다.

ChangeListener을 ToggleGroup에 설정

그럼, 작성한 FXML를 읽어 들여, ToggleGroup 이벤트 처리를 포함한 컨트롤러 클래스를 만들어 보자.

아래에 간단한 예제 코드를 준비해 두었다.

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ToggleGroup group1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
 
        group1.selectedToggleProperty().addListener((ObservableValue<? extends Toggle> 
            observ, Toggle oldVal, Toggle newVal)->{
            String oldStr = (String)oldVal.getUserData();
            String newStr = (String)newVal.getUserData();
            label1.setText(oldStr + "->" + newStr);
        });
    }
 
}

실행하여 라디오 버튼을 클릭하여 보자. 그러면 윈도우 상단의 라벨에 “남자 -> 여자"라는 텍스트가 표시된다. 선택 전과 선택 후에 각각의 RadioButton에 설정된 userData을 표시하고 있는 것을 알 수 있다.

여기에서는 ToggleGroup의 selectedToggleProperty에 설정된 ReadOnlyObjectProperty에 다음과 같은 형태로 리스너를 설정하고 있다.

ReadOnlyObjectProperty.addListener((ObservableValue<? extends Toggle> observ,
    Toggle oldVal, Toggle newVal) -> {
    // 수행할 작업
});

여기에서는 람다 식을 사용하여 설정 방법을 적어 보겠다. ChangeListener에는 changed라는 메소드 하나만 정의되어 있다. 이것은 다음과 같은 형태를 하고 있다.

void changed(ObservableValue<? extends T> observable, T oldValue, T newValue)

ObservableValue는 제네릭 형을 설정 할 수 있다. ToggleGroupselectedtoggleProperty에 포함 된 경우 “Toggle"라는 클래스를 상속하는 형태로 ObservableValue가 제공된다. 그 외에 oldValue, newValueToggle 인스턴스로 전달된다.

Toggle이라는 클래스 (정확하게는 인스턴스이지만)은 ON/OFF로 다루어지는 값을 관리하기 위한 클래스이다. 이 Toggle에서 변경된 값에 대한 정보를 검색한다. 여기에서는 getUserData라는 메소드를 사용하고 있다. 이것으로 부터 그 Toggle로 설정되어 있는 UserData 속성의 값을 꺼낸다. 이는 FXML에 추가되어 있는 “userData” 속성의 값이다.

ReadOnlyObjectProperty, ObservableValue, Toggle와 낯선 클래스가 몇개 등장해서 이해하기 어려울지도 모르지만, 기본적인 속성 변경 이벤트 리스너 처리는 모두 유사한 형태로 되어 있기 때문에 이러한 이벤트 처리에 익숙해지면 어떤 것도 유사한 방식으로 처리할 수 있다.

ComboBox의 SelectionModel 이벤트 처리

계속해서 ComboBox에 대해 설명하겠다. 이것은 사실은 ListView 알면 거의 같은 방식으로 처리 할 수 있다.

ComboBox에도 선택 상태를 관리하는 모델 클래스가 설정된 속성이 있는데 이는 getSelectionModel으로 선택 모델 클래스를 얻을 수 있다.

그리고 그 selectedItemProperty에서 얻을 수 있는 속성에 addListener으로 이벤트 리스너를 설정하여 선택 변경시의 처리를 설정 할 수 있다.

그럼 이것도 살펴 보도록 하자. 이전에 FXML에 있던 <center> 태그 안에서 다음과 같이 작성한다.

<ComboBox fx:id="combo1">
<items>
    <FXCollections fx:factory="observableArrayList">
        <String fx:value="One" />
        <String fx:value="Two" />
        <String fx:value="Three" />
    </FXCollections>
</items>
</ComboBox>

이것으로 String 값을 항목으로 설정한 ComboBox가 준비되었다. 다음에는 이 이벤트 처리를 컨트롤러 클래스에서 제공하는 뿐이다.

아래와 같이 소스 코드를 작성해 보자.

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
 
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ComboBox<String> combo1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
 
        combo1.getSelectionModel().selectedItemProperty().
            addListener((ObservableValue<? extends String> observ,
                    String oldVal, String newVal)->{
            label1.setText(oldVal + "->" + newVal);
        });
    }
}

여기에서는 ComboBox에 제네릭 형으로 String를 지정하고 있다. 아래와 같은 형식이다.

@FXML ComboBox<String> combo1;

이것으로 String 값을 항목으로 가진 설정이 된다. 이벤트 리스너는 다음과 같이 설정한다.

combo1.getSelectionModel().selectedItemProperty().addListener ......

getSelectionModel에서 얻어진 선택 모델 selectedItemProperty에 addListener을 설정한다. 조금 까다롭다. 이벤트 리스너 설정은 다음과 같은 형태로 정의된다.

addListener((ObservableValue<? extends String> observ, String oldVal, String newVal) -> {
    // 이벤트 처리
});

첫번째 인수 ObservableValue<? extends String> observ이라는 형태로 정의되어 있다. ComboBox의 정의도 <String>가 지정되어 있기 때문에, ObservableValueextends String 클래스로 정의된다. 다음에는 변경 전과 변경 후의 값이 각각 String으로 전달된다.

ObservableValue는 거기에 저장된 값을 extends하는 형태로 작성된다"는 것은 정말 이상한 형태일 것이다. 어쨌든, 실제로 프로그램을 작성한 ObservableValue 슈퍼 클래스가 어떤 결정 때문이다. 조금 이해하기 어렵겠지만, “선택 모델에 설정된 클래스를 상속해서 ObservableValue은 준비된다"는 것을 명심하자.

슬라이더의 valueProperty를 이벤트 처리

슬라이더도 값이 변경되었을 때의 이벤트를 유사한 방식으로 처리 할 수 있다. Slider 클래스는 설정된 값은 valueProperty라는 속성으로 처리된다.

이것은 DoubleProperty라는 클래스의 인스턴스로 속성을 관리하는 기능이 포함되어 있다. 이 DoublePropertyaddListener에서 이벤트 리스너를 설정하여 값이 변경되었을 때 처리를 수행 할 수 있다.

그럼 이것도 간단한 샘플을 만들어 사용해 보자. 이전에 FXML에서 <center> 태그를 다음과 같이 작성하자.

<center>
    <Slider fx:id="slider1" min="0" max="100"/>
</center>

이것으로 슬라이더가 하나 표시되도록 되었다. 여기에 이벤트 처리를 설정하여 조작하면 실시간으로 표시가 업데이트 되도록 하자.

아래와 같이 간단한 예제를 작성하자.

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML Slider slider1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
 
        slider1.valueProperty().addListener((ObservableValue<? extends Number> 
                observ, Number oldVal, Number newVal)->{
            double oldnum = oldVal.doubleValue();
            double newnum = newVal.doubleValue();
            label1.setText(oldnum + "->" + newnum);
        });
    }
}

이것은 값을 변경 전과 변경 후의 값을 얻어와 표시하는 샘플이다. 슬라이더를 조작하면 “12.34567 -> 98.7654"와 같은 상태로, 이전 값과 새로운 값이 표시된다.

여기에서는 슬라이더에 이벤트 리스너 설정을 다음과 같은 형태로 작성하고 있다.

slider1.valueProperty().addListener ......

Slider 클래스의 valueProperty에서 얻은 DoubleProperty 인스턴스의 addListener에 이벤트 리스너를 설정한다. 이것은 다음과 같은 형태로 되어 있다.

addListener((ObservableValue <? extends Number> observ, Number oldVal, Number newVal) -> {
    // 이벤트 처리
});

ObservableValueNumber의 서브 클래스로 정의되어 있다. 그리고 전달되는 값은 Number 인스턴스가 있다. 그 다음은 전달된 Number에서 메소드를 호출하여 필요한 값 등을 취득 할뿐이다.

double oldnum = oldVal.doubleValue();
double newnum = newVal.doubleValue();

여기에서는 doubleValue에서 double 값으로 얻어 표시하고 있다. 정수 만들려고 한다면 intValue 메소드를 사용하면 된다.

이벤트 리스너와 ObservableValue의 관계가 이것으로 많이 알게 되었다고 생각한다. ObservableValue를 잘 다룰 수 있게 된다면, 속성의 이벤트 처리는 대체로 마스터하였다고 생각해도 될 것이다.




최종 수정 : 2017-09-19