JavaFX ListView와 SelectionModel

ListView를 이용하려면 데이터를 처리하는 방법을 이해해야 한다. 그 기본과 선택된 항목을 관리하는 SelectionModel에 대해 설명한다.

ListView 만들기

기본적인 GUI 컨트롤의 사용법은 이미 설명했지만, JavaFX는 더 복잡한 컨트롤도 포함되어 있다. 특히 중요한 것이 “데이터를 처리하는 컨트롤"이다. 미리 준비한 데이터를 바탕으로 필요한 정보를 표시하고 조작하는 것이다.

그 대표라고도 말할 수 있는 것이 “리스트"이다. 여러 항목을 세로 스크롤 리스트로 표시 나열하는 GUI이다. JavaFX에는 이것을 “ListView"라는 컨트롤로 제공하고 있다.

우선 FXML를 이용하여 ListView를 만들어 보자. ListView<ListView>라는 태그를 사용하여 만든다. 이것 자체는 매우 간단하지만, 이것만으로는 빈 목록이 되어 버린다. 이 안에 표시 할 내용을 작성해 해주지 않으면 안된다.

<ListView>
    <items>
        <FXCollections fx:factory = "데이터 유형">
            ...... 데이터의 내용 ......
        </FXCollections>
    </items>
</ListView>

데이터의 내용까지 FXML으로 쓰면 이런 식이 될 것이다. ListView의 데이터는 <items>라는 태그로 제공한다. 이 가운데 <FXCollection>라는 태그를 추가한다. 여기에 구체적인 데이터의 내용을 작성하면 된다. 일반적으로 여기에 fx:factory라는 속성을 제공하고 데이터의 종류(FXCollection에 저장하는 클래스)를 지정한다.

이것은 다양하게 설정할 수 있지만, 우선은 observableArrayList라는 클래스를 지정해 둔다. 이것은 개체를 모은 ArrayList의 서브 클래스이다. 이 중에 예를 들어 <String> 태그를 사용하여 텍스트를 작성하면 String 데이터의 ArrayList`가 생기고, 그것이 표시 항목으로 설정된다.

실제 사용 예를 들면, 아래 코드는 여러 텍스트 항목을 표시하는 샘플이다.

<?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?>
<BorderPane xmlns="http://javafx.com/javafx"
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="com.tuyano.libro.AppController">
    <stylesheets>
        <URL value="@app.css" />
    </stylesheets>
    <top>
        <Label fx:id="label1" text="This is FXML!" />
    </top>
    <center>
        <ListView fx:id="list1">
            <items>
                <FXCollections fx:factory="observableArrayList">
                    <String fx:value="Windows" />
                    <String fx:value="Mac OS" />
                    <String fx:value="Linux" />
                </FXCollections>
            </items>
        </ListView>
    </center>
    <bottom>
        <Button text="Click" fx:id="btn1" />
    </bottom>
</BorderPane>

<ListView><items> 태그에 다음과 같은 형태로 데이터를 준비하고 있다.

<FXCollections fx:factory="observableArrayList">

그리고 이 가운데에 <String> 태그를 사용하여 표시할 텍스트를 지정한다. 간단한 텍스트 값이라면 이것으로 표시 가능하다.

Java 코드에서 ListView의 표시 내용 작성

이어서 FXML가 아닌 Java 소스 코드 내에서 ListView의 표시 내용을 작성해 나가는 방법을 살펴 보겠다.

ListView에 표시되는 항목은 “items"라는 속성으로 관리되고 있다. 이것은 표시되는 모든 항목을 정리하고 관리하는 것으로, 컬렉션 프레임워크의 인스턴스가 설정되어 있다.

이 items 값은 getItems에서 얻거나 setItems으로 변경할 수 있다. 또한 items 인스턴스에 저장되어 있는 각 항목의 데이터는 그 컬렉션 클래스의 메소드를 사용하여 관리 할 수 있다.

실제 사용 예제 소스 코드는 아래와 같다.

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ListView<String> list1;
    @FXML Button btn1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        list1.setItems(FXCollections.observableArrayList());
        list1.getItems().add("One");
        list1.getItems().add("Two");
        list1.getItems().add("Three");
    }
 
}

먼저, 이전 FXML에서 <ListView> 태그를 다음과 같이 수정하도록 하자.

<ListView fx:id="list1"></ListView>

이것으로 FXML에는 어떤한 항목도 추가되지 않았지만, 실행하면 “One”, “Two”, “Three"라는 항목이 목록에 표시되는 것을 확인할 수 있을 것이다.

여기에서는 Initializable 인터페이스를 이용하여, initialize 메소드에서 초기화 처리를 하고 있다.

list1.setItems(FXCollections.observableArrayList());

FXCollections.observableArrayList를 사용하여 컬렉션 클래스의 인스턴스를 만들고, 그것을 setItems로 설정합니다. 그 다음에는 items 인스턴스에 add로 표시할 텍스트를 추가해 나갈뿐 이다.

list1.getItems().add("One");
list1.getItems().add("Two");
list1.getItems().add ("Three");

사용 방법만 알면, 목록의 작성은 그리 어려운 것이 아님을 알 수 있다.

ListView의 클릭 이벤트 처리

ListView에 표시된 항목을 클릭하여 어떤 처리을 수행 시키고자 하는 같은 경우에는 어떻게 해야 할까? ListView에는 액션 이벤트가 제공되어 있는다. 그러므로 클릭하면 뭔가를 행하도록 다른 이벤트를 준비해야 한다.

여기에서는 MouseClick라는 이벤트를 이용해 보자. 이것은 이름에서 알수 있듯이 마우스로 클릭했을 때 발생하는 이벤트이다. 이 이벤트는 setOnMouseClicked라는 메소드를 사용하여 설정할 수 있다.

listView.setOnMouseClicked(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event) {
        // 수행할 처리
    }
});

이런식으로 작성을 하면 된다. 그러나 Java8에서는 이러한 이벤트의 기본은 람다 식을 사용하는 것이 기본이기 때문에, 일반적으로 다음과 같이 작성하게 될 것이다.

listView.setOnMouseClicked ((MouseEvent) -> {
    // 수행할 처리
}

이쪽이 매우 간단하다. 그럼 실제 사용 예제를 만들어 보자. 아래와 같이 리스트의 항목을 클릭하면 해당 텍스트를 Label1에 표시하는 예제이다. setOnMouseClicked를 이용하면 간단한 리스트의 항목을 클릭한 처리를 추가할 수 있다.

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
 
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ListView<String> list1;
    @FXML Button btn1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        list1.setItems(FXCollections.observableArrayList());
        list1.getItems().add("One");
        list1.getItems().add("Two");
        list1.getItems().add("Three");
         
        list1.setOnMouseClicked((MouseEvent)->{
            Object obj = list1.getSelectionModel().getSelectedItem();
            label1.setText(obj.toString());
        });
 
        btn1.setOnAction((AtionEvent)->{
            String obj = list1.getSelectionModel().getSelectedItem();
            label1.setText("you selected: \"" + obj + "\".");
        });
    }
 
}

ListView에서 항목을 선택했을 때의 이벤트 처리를 하려면, 사실 MouseClick는 최적은 아니다. 이 후에 설명하는 ChangeListener를 사용하는 편이 더 나은 것이다.

SelectionModel

먼저번 예제에서는 setOnMouseClick으로 선택된 항목의 텍스트를 꺼내오기 위해서는 다음과 같은 처리를 해야 했다.

String obj = list1.getSelectionModel().getSelectedItem();

getSelectionModel이라는 것을 호출해서, 반환되는 인스턴스의 getSelectedItem라는 메소드를 호출한다. 이것으로 선택된 항목의 오브젝트를 얻어 올 수 있는 것이다. 이것은 ListView의 “SelectionModel"이라는 것을 이해하지 않으면 무엇을 하고 있는지 모를 것이다.

“SelectioModel"라고 하는 것은, 선택된 항목을 관리하는 “모델 클래스"이다. JavaFX에는 다양한 데이터를 관리하기 위해 “모델"이라는 개념을 도입하고 있다. 모델은 동적으로 조작하는 데이터를 관리하기위한 것이다.

선택된 항목은 SelectionModel라는 모델 클래스를 사용하여 관리된다. ListView의 “getSelectionModel"메소드를 호출하는 것으로, 그 ListView에 포함된 SelectionModel 인스턴스를 얻을 수 있다.

SelectionModel 클래스에는 선택 항목에 대한 다양한 메소드가 제공되어 있다. 여기에서는 “getSelectedItem"라는 메소드를 사용하고 있다. 이것은 선택된 항목의 인스턴스를 반환하는 메서드이다. 이것으로 항목의 객체를 얻고, 거기에서 표시된 텍스트 등을 꺼내 올 수 있다.

저장하는 값과 제네릭 형에 대해

ListView는 제네릭 형(Generic Type)을 지원하고 있다. 앞의 예제 코드를 보면 ListView의 값을 저장 필드가 다음과 같이 되어 있는 것을 볼 수 있다.

@FXML ListView<String> list1;

이처럼 <String>을 추가하는 것으로, 저장하는 값을 String으로 제한 할 수 있다. getSelectedItem에서 값을 직접 String 변수에 할당한 것도 제네릭 형을 사용했기 때문이다.

SelectionModel에 ChangeListener을 설정

SelectionModel는 속성의 변경에 대응한 이벤트 처리 기능을 가지고 있다. 예를 들어, SelectionModel에는 SelectedItem라는 속성이 있고, 선택한 항목 등은 getSelectedItem 메소드로 꺼낼 수 있었다.

SelectedItem 속성에 “ChangeListener"라는 이벤트 리스너를 설정하여, 값이 변경되었을 때의 이벤트를 파악하고 처리하는 것이 가능하다. ChangeListener는 그 이름과 같이 값이 변경 될 때 발생하는 이벤트(ChangeEvent)를 처리하는 이벤트 리스너이다.

이것은 SelectionModel의 “selectedItemProperty"라는 메소드로 얻은 ReadOnlyObjectProperty라는 클래스의"addListener"메소드로 설정할 수 있다. 설정 방법의 형태는 다음과 같다.

selectionModel.selectedItemProperty().addListener(new ChangeListener() {

    @Override
    public void changed(ObservableValue observable, Object oldVal, Object newVal) {
        // 수행할 처리
    }
});

ChangeListenerchanged라는 메소드가 하나 포함되어 있다. 이 메소드는 ObservableValue라는 클래스의 인스턴스와 변경 전, 변경 후의 값을 인수로 전달된다.

“메소드가 하나뿐"이라는 것에 대해 기억하는 사람이 있을 것이다. 이렇게, Java8는 메소드가 하나인 인터페이스는 람다 식으로 대체 할 수 있다. addListener에 의한 이벤트 리스너 설정을 람다 식으로 하면 이렇게 된다.

selectionModel.selectedItemProperty().addListener (
    (ObservableValue observable, Object oldVal, Object newVal) -> {
        // 수행할 처리
    }
);

이쪽이 더 알아보기 쉽다. 이렇게 ChangeListener를 설정하여, 값이 변경되었을 때 (SelectedItem의 값이 바뀌었을 때 = 선택 상태가 변경되었을 때)의 처리를 할 수 있다.

아래와 같이 간단한 사용 예제를 만들어 보자.

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
 
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ListView list1;
    @FXML Button btn1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        list1.setItems(FXCollections.observableArrayList());
        list1.getItems().add("One");
        list1.getItems().add("Two");
        list1.getItems().add("Three");
         
        list1.getSelectionModel().selectedItemProperty().addListener(
            (ObservableValue observable, Object oldVal, Object newVal) -> {
                label1.setText(oldVal + " -> " + newVal);
            }
        );
         
        btn1.setOnAction((AtionEvent)->{
            Object obj = list1.getSelectionModel().getSelectedItem();
            label1.setText("you selected: \"" + obj.toString() + "\".");
        });
    }
 
}

목록의 항목을 선택하면 “이전 값 -> 새로운 값"라는 형식으로 값의 변화가 label1에 표시된다. MouseClick를 사용하는 것보다 이쪽이 스마트하지 않은가? MouseClick는 키 조작 등으로 선택 항목을 변경 한 경우에는 이벤트가 발생하지 않지만, ChangeListener를 이용하면 어떤 형태로든 선택 상태가 바뀌면 바로 이벤트가 발생하고 그에 따른 처리 수행을 할 수가 있다.




최종 수정 : 2017-09-19