06. JavaFX 속성 감시와 바인딩

JavaFX는 컨트롤의 속성(Property)을 감시하는 리스너를 설정할 수 있습니다. 예를 들어 Slidervalue 속성값을 감시하여, 값이 변경되면 Label의 폰트 크기나 이미지의 크기를 실시간으로 변경할 수 있습니다.


1. 속성 감시 (Property Monitoring)

JavaFX 컨트롤의 모든 속성은 XXXProperty 객체로 생성됩니다. 이는 Getter/Setter 외에도 속성 객체 자체를 리턴하는 xxxProperty() 메소드를 제공합니다.

예: TextField의 text 속성

TextFieldtext 속성은 StringProperty 타입입니다.

// StringProperty 타입의 text 속성 선언
private StringProperty text = new SimpleStringProperty();

// Getter와 Setter
public void setText(String newValue) { text.set(newValue); }
public String getText() { return text.get(); }

// StringProperty 객체를 리턴하는 메소드
public StringProperty textProperty() { return text; }

리스너 등록 (ChangeListener)

addListener() 메소드를 사용하여 속성 변경 시 실행될 코드를 등록할 수 있습니다.

slider.valueProperty().addListener(new ChangeListener<Number>() {
    @Override
    public void changed(ObservableValue<? extends Number> observable, 
                        Number oldValue, Number newValue) {
        // 속성값이 변경되었을 때 실행되는 코드
        System.out.println("변경전: " + oldValue + ", 변경후: " + newValue);
    }
});
  • oldValue: 변경 전 값
  • newValue: 변경 후 값 (새로운 값)

예제: Slider 값에 따른 폰트 크기 변경

root.fxml

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

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.control.Slider?>

<BorderPane xmlns:fx="http://javafx.com/fxml"
            fx:controller="sec06.exam01_property_listener.RootController"
            prefHeight="250.0" prefWidth="350.0">
    <center>
        <Label fx:id="label" text="JavaFX">
            <font>
                <Font size="0"/>
            </font>
        </Label>
    </center>
    <bottom>
        <Slider fx:id="slider"/>
    </bottom>
</BorderPane>

RootController.java

package sec06.exam01_property_listener;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.text.Font;

public class RootController implements Initializable {
    @FXML private Slider slider;
    @FXML private Label label;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // Slider의 value 속성 감시 설정
        slider.valueProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observable, 
                                Number oldValue, Number newValue) {
                // Label의 폰트 크기를 Slider의 값으로 변경
                label.setFont(new Font(newValue.doubleValue()));
            }
        });
    }
}

2. 속성 바인딩 (Property Binding)

속성 바인딩은 한 속성의 값이 변경되면 다른 속성의 값도 자동으로 변경되도록 연결하는 기능입니다.

1) 단방향 바인딩 (Unidirectional Binding)

한쪽 속성이 변경될 때 다른 쪽 속성도 따라서 변경되지만, 반대는 성립하지 않습니다.

  • target.bind(source): source가 변하면 target도 변함.
TextArea textArea1 = new TextArea();
TextArea textArea2 = new TextArea();
// textArea1이 변경되면 textArea2도 변경됨 (textArea2는 직접 수정 불가)
textArea2.textProperty().bind(textArea1.textProperty());

2) 양방향 바인딩 (Bidirectional Binding)

어느 한쪽이 변경되면 다른 쪽도 함께 변경됩니다.

  • target.bindBidirectional(source)
// 방법 1
textArea2.textProperty().bindBidirectional(textArea1.textProperty());

// 방법 2 (Bindings 클래스 사용)
Bindings.bindBidirectional(textArea1.textProperty(), textArea2.textProperty());

바인딩 해제: unbind() 또는 unbindBidirectional() 메소드 사용.

예제: 양방향 바인딩

RootController.java

@Override
public void initialize(URL location, ResourceBundle resources) {
    // 두 TextArea의 text 속성을 양방향 바인딩
    Bindings.bindBidirectional(textArea1.textProperty(), textArea2.textProperty());
}

3. Bindings 클래스

Bindings 클래스는 속성값의 연산, 변환, 조건문 등을 처리하여 바인딩할 수 있는 다양한 정적 메소드를 제공합니다.

메소드 설명
add, subtract, multiply, divide 사칙연산 수행 후 바인딩
max, min 최대/최소값 바인딩
greaterThan, greaterThanOrEqual 크기 비교 (true/false) 바인딩
lessThan, lessThanOrEqual 크기 비교 (true/false) 바인딩
equal, notEqual 동등 비교 (true/false) 바인딩
isEmpty, isNotEmpty 비어있는지 여부 확인
isNull, isNotNull Null 여부 확인
length 문자열 길이 바인딩
size 컬렉션(List, Map 등) 요소 수 바인딩
and, or, not 논리 연산 바인딩
convert 문자열로 변환하여 바인딩
select 중첩된 속성 탐색 및 바인딩

예제: 원을 항상 화면 중앙에 유지하기

Cannot divide by zero 등의 예외를 방지하고 반응형 UI를 만들기 위해 Bindings.divide()를 사용합니다.

// Circle의 중심 좌표(centerX, centerY)를 Root 컨테이너의 너비/높이의 절반으로 바인딩
circle.centerXProperty().bind(Bindings.divide(root.widthProperty(), 2));
circle.centerYProperty().bind(Bindings.divide(root.heightProperty(), 2));

RootController.java

package sec06.exam03_bindings;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import javafx.scene.shape.Circle;

public class RootController implements Initializable {
    @FXML private AnchorPane root;
    @FXML private Circle circle;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // 화면 크기가 변해도 항상 중앙에 위치하도록 바인딩
        circle.centerXProperty().bind(Bindings.divide(root.widthProperty(), 2));
        circle.centerYProperty().bind(Bindings.divide(root.heightProperty(), 2));
    }
}
서브목차