ページ

2013年5月30日木曜日

JavaFX - TableView : Simple CRUD

TableViewを利用して、基本的なデータの表示・編集・追加・削除を行なってみたいと思います。

TableViewへのデータの表示は、itemsPropertyに表示したいモデルのObservableListを設定し、データの追加・削除についてはそのObservableListにモデルを追加・削除することによって行います。

では、実際に以下のようなものを作成してみたいと思います。

Name, Gender, Email, Ageを持つUserを表示しています。[+]ボタンで新規にUserを追加し、[-]ボタンで選択されているUserを削除します。

まずは、表示するUserのモデルを以下のように定義します。

public interface UserModel {
    StringProperty nameProperty();
    ObjectProperty<GenderModel> genderProperty();
    StringProperty emailProperty();
    IntegerProperty ageProperty();
}

GenderModelは以下のようになります。

public enum GenderModel {
    MALE,
    FEMALE;
}

次に、このUserModelを表示するViewに対するモデルを以下のように定義します。

public interface UserListModel {
    ObjectProperty<ObservableList<UserModel>> usersProperty();
    ObjectProperty<TableView.TableViewSelectionModel<UserModel>> userSelectionModelProperty();

    void loadUsers();
    void addNewUser();
    void removeSelectedUser();
}

UserModelの読み込み・追加・削除を行うメソッドを定義しています。また、TableViewのselectionModelプロパティからTableViewで選択されている状態を取得したいので、それをバインドして取得できるようにuserSelectionModelプロパティを定義しています。

UserModelを表示するViewを以下のように定義します。

<fx:root xmlns:fx="http://javafx.com/fxml" type="javafx.scene.layout.HBox">
    <TableView fx:id="userTableView" editable="true">
        <columns>
            <TableColumn fx:id="nameTableColumn" text="Name" prefWidth="100"/>
            <TableColumn fx:id="genderTableColumn" text="Gender" prefWidth="80"/>
            <TableColumn fx:id="emailTableColumn" text="Email" prefWidth="160"/>
            <TableColumn fx:id="ageTableColumn" text="Age" prefWidth="60"/>
        </columns>
    </TableView>

    <VBox id="actionButtonRegion">
        <Button fx:id="addUserButton" text="+" prefWidth="35"/>
        <Button fx:id="removeUserButton" text="-" prefWidth="35"/>
    </VBox>
</fx:root>

UserModelを表示するTableViewと、UserModelの追加・削除のためのボタンを定義しています。

このViewのコントローラを以下のようにします。

public class UserListView extends HBox {
    public ObjectProperty<UserListModel> modelProperty() { return model; }
    private final ObjectProperty<UserListModel> model = new SimpleObjectProperty<UserListModel>(this, "model") {
        private UserListModel currentModel;

        @Override
        protected void invalidated() {
            UserListView.this.unbind(currentModel);
            currentModel = get();
            UserListView.this.bind(currentModel);
        }
    };
    public final UserListModel getModel() { return model.get(); }
    public final void setModel(UserListModel model) { this.model.set(model); }

    @FXML
    private TableView<UserModel> userTableView;
    @FXML
    private TableColumn<UserModel, String> nameTableColumn;
    @FXML
    private TableColumn<UserModel, GenderModel> genderTableColumn;
    @FXML
    private TableColumn<UserModel, String> emailTableColumn;
    @FXML
    private TableColumn<UserModel, Integer> ageTableColumn;
    @FXML
    private Button addUserButton;
    @FXML
    private Button removeUserButton;

    public UserListView() {
        getStyleClass().add("user-list-view");
        initializeComponent();
    }

    protected void initializeComponent() {
        FXController.of(this).fromDefaultLocation().load();
    }

    protected void bind(UserListModel model) {
        if (model == null) { return; }

        userTableView.itemsProperty().bindBidirectional(model.usersProperty());
        model.userSelectionModelProperty().bind(userTableView.selectionModelProperty());
    }

    protected void unbind(UserListModel model) {
        if (model == null) { return; }

        userTableView.itemsProperty().unbindBidirectional(model.usersProperty());
        model.userSelectionModelProperty().unbind();
    }

    protected void addUser() {
        if (getModel() == null) { return; }

        getModel().addNewUser();
        userTableView.requestFocus();
    }

    protected void removeUser() {
        if (getModel() == null) { return; }

        getModel().removeSelectedUser();
        userTableView.requestFocus();
    }

    @FXML
    protected void initialize() {
        nameTableColumn.setCellValueFactory(new PropertyValueFactory<UserModel, String>("name"));
        nameTableColumn.setCellFactory(TextFieldTableCell.<UserModel>forTableColumn());

        genderTableColumn.setCellValueFactory(new PropertyValueFactory<UserModel, GenderModel>("gender"));
        genderTableColumn.setCellFactory(ChoiceBoxTableCell.<UserModel, GenderModel>forTableColumn(GenderModel.values()));

        emailTableColumn.setCellValueFactory(new PropertyValueFactory<UserModel, String>("email"));
        emailTableColumn.setCellFactory(TextFieldTableCell.<UserModel>forTableColumn());

        ageTableColumn.setCellValueFactory(new PropertyValueFactory<UserModel, Integer>("age"));
        ageTableColumn.setCellFactory(TextFieldTableCell.<UserModel, Integer>forTableColumn(new IntegerStringConverter()));

        addUserButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                addUser();
            }
        });

        removeUserButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                removeUser();
            }
        });
    }
}

UserListModelを設定したときに、UserListModelで定義されているプロパティとTableViewのプロパティをバインドしています。[+]ボタンが押下されたときに、UserListModelのaddNewUserメソッドを呼び出してUserModelを追加し、[-]ボタンが押下されたときに、UserListModelのremoveSelectedUserメソッドを呼び出して選択されているUserModelを削除するようにしています。

以上で準備ができましたので、このUserListViewを表示していきます。

まずは、モデルを以下のように定義します。

public interface TableViewSimpleCRUDDemoModel {
    ObjectProperty<UserListModel> userListModelProperty();

    void initialize();
}

次に、Viewを以下のように定義します。

<Scene xmlns:fx="http://javafx.com/fxml"
       fx:id="scene"
       width="640" height="480">
    <stylesheets>
        <URL value="@TableViewSimpleCRUDDemoSceneStyle.css"/>
    </stylesheets>

    <StackPane>
        <UserListView fx:id="userListView"/>
    </StackPane>
</Scene>

このViewのコントローラを以下のようにします。

public class TableViewSimpleCRUDDemoSceneController {
    public ObjectProperty<TableViewSimpleCRUDDemoModel> modelProperty() { return model; }
    private final ObjectProperty<TableViewSimpleCRUDDemoModel> model = new SimpleObjectProperty<TableViewSimpleCRUDDemoModel>(this, "model") {
        private TableViewSimpleCRUDDemoModel currentModel;
        @Override
        protected void invalidated() {
            TableViewSimpleCRUDDemoSceneController.this.unbind(currentModel);
            currentModel = get();
            TableViewSimpleCRUDDemoSceneController.this.bind(currentModel);
        }
    };
    public final TableViewSimpleCRUDDemoModel getModel() { return model.get(); }
    public final void setModel(TableViewSimpleCRUDDemoModel model) { this.model.set(model); }

    @FXML
    private Scene scene;
    @FXML
    private UserListView userListView;

    public void performOn(Stage stage) {
        stage.setScene(scene);
        stage.setTitle("Simple CRUD for Table View Demo");
        stage.sizeToScene();
        stage.centerOnScreen();
        stage.show();
    }

    public TableViewSimpleCRUDDemoSceneController with(TableViewSimpleCRUDDemoModel model) {
        setModel(model);
        if (model != null) { model.initialize(); }

        return this;
    }

    protected void bind(TableViewSimpleCRUDDemoModel model) {
        if (model == null) { return; }

        userListView.modelProperty().bindBidirectional(model.userListModelProperty());
    }

    protected void unbind(TableViewSimpleCRUDDemoModel model) {
        if (model == null) { return; }

        userListView.modelProperty().unbindBidirectional(model.userListModelProperty());
    }
}

あとは、各モデルのデフォルトの実装を行っておきます。

まずは、UserModelのデフォルトの実装を以下のようにします。

public class DefaultUserModel implements UserModel {
    @Override
    public StringProperty nameProperty() { return name; }
    private final StringProperty name = new SimpleStringProperty(this, "name");
    public final String getName() { return name.get(); }
    public final void setName(String name) { this.name.set(name); }

    @Override
    public ObjectProperty<GenderModel> genderProperty() { return gender; }
    private final ObjectProperty<GenderModel> gender = new SimpleObjectProperty<>(this, "gender");
    public final GenderModel getGender() { return gender.get(); }
    public final void setGender(GenderModel gender) { this.gender.set(gender); }

    @Override
    public StringProperty emailProperty() { return email; }
    private final StringProperty email = new SimpleStringProperty(this, "email");
    public final String getEmail() { return email.get(); }
    public final void setEmail(String email) { this.email.set(email); }

    @Override
    public IntegerProperty ageProperty() { return ageProperty; }
    private final IntegerProperty ageProperty = new SimpleIntegerProperty(this, "ageProperty");
    public final int getAge() { return ageProperty.get(); }
    public final void setAge(int ageProperty) { this.ageProperty.set(ageProperty); }

    protected DefaultUserModel(String name, GenderModel gender, String email, int age) {
        setName(name);
        setGender(gender);
        setEmail(email);
        setAge(age);
    }

    public static DefaultUserModel asMale(String name, String email, int age) {
        return new DefaultUserModel(name, GenderModel.MALE, email, age);
    }

    public static DefaultUserModel asFemale(String name, String email, int age) {
        return new DefaultUserModel(name, GenderModel.FEMALE, email, age);
    }

    public static DefaultUserModel newUser() {
        return new DefaultUserModel("Unknown", GenderModel.MALE, "", 20);
    }
}

次に、UserListModelのデフォルトの実装を以下のようにします。

public class DefaultUserListModel implements UserListModel {
    @Override
    public ObjectProperty<ObservableList<UserModel>> usersProperty() { return users; }
    private final ObjectProperty<ObservableList<UserModel>> users = new SimpleObjectProperty<>(this, "users");
    public final ObservableList<UserModel> getUsers() { return users.get(); }
    public final void setUsers(ObservableList<UserModel> users) { this.users.set(users); }

    @Override
    public ObjectProperty<TableView.TableViewSelectionModel<UserModel>> userSelectionModelProperty() { return userSelectionModel; }
    private final ObjectProperty<TableView.TableViewSelectionModel<UserModel>> userSelectionModel = new SimpleObjectProperty<>(this, "userSelectionModel");
    public final TableView.TableViewSelectionModel<UserModel> getUserSelectionModel() { return userSelectionModel.get(); }
    public final void setUserSelectionModel(TableView.TableViewSelectionModel<UserModel> userSelectionModel) { this.userSelectionModel.set(userSelectionModel); }

    @Override
    public void loadUsers() {
        setUsers(
            FXCollections.<UserModel>observableArrayList(
                DefaultUserModel.asMale("User 1", "user1@email.com", 25),
                DefaultUserModel.asFemale("User 2", "user2@email.com", 28),
                DefaultUserModel.asFemale("User 3", "user3@email.com", 24),
                DefaultUserModel.asMale("User 4", "user4@email.com", 21),
                DefaultUserModel.asFemale("User 5", "user5@email.com", 26)
            )
        );
    }

    @Override
    public void addNewUser() {
        if (getUsers() == null) { setUsers(FXCollections.<UserModel>observableArrayList()); }

        getUsers().add(DefaultUserModel.newUser());
        if (getUserSelectionModel() != null) { getUserSelectionModel().selectLast(); }
    }

    @Override
    public void removeSelectedUser() {
        if (getUsers() == null) { return; }
        if (getUserSelectionModel() == null) { return; }

        getUsers().remove(getUserSelectionModel().getSelectedItem());
    }
}

loadUsersでは、サンプルデータとして5件のUserModelを設定しています。

最後に、TableViewSimpleCRUDDemoModelのデフォルトの実装を以下のようにします。

public class DefaultTableViewSimpleCRUDDemoModel implements TableViewSimpleCRUDDemoModel {
    @Override
    public ObjectProperty<UserListModel> userListModelProperty() { return userListModel; }
    private final ObjectProperty<UserListModel> userListModel = new SimpleObjectProperty<>(this, "userListModel");
    public final UserListModel getUserListModel() { return userListModel.get(); }
    public final void setUserListModel(UserListModel userListModel) { this.userListModel.set(userListModel); }

    @Override
    public void initialize() {
        setUserListModel(new DefaultUserListModel());
        getUserListModel().loadUsers();
    }
}

以上ですべての実装が完了しましたので、Applicationクラスを以下のようにして実行します。

public class TableViewSimpleCRUDDemo extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        FXController.of(new TableViewSimpleCRUDDemoSceneController())
            .fromDefaultLocation()
            .load()
            .with(new DefaultTableViewSimpleCRUDDemoModel())
            .performOn(primaryStage);
    }

    public static void main(String... args) {
        launch(args);
    }
}

TableViewを利用して、基本的なデータの表示・編集・追加・削除を行なってみました。データの操作はモデル側で行い、Viewへの表示については、モデルのプロパティをViewのプロパティにバインドすることで行うことになります。