JavaFXのComboBoxでoptgroupみたいな見た目を実現してみた


完成した画面

ComboBoxを開いたときはいい感じにグループっぽく見えて、選択後は左寄せ

実際のコード

OptGroupComboBox.fxml

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

<?import javafx.collections.FXCollections?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.layout.AnchorPane?>
<?import optGroupComboBox.Java.*?>
<?import optGroupComboBox.Java.Enum.*?>
<AnchorPane style = "-fx-background-color: darkgray" fx:controller="optGroupComboBox.Java.Controller" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
    <children>
        <ComboBox fx:id="optGroupComboBox" layoutX="249.0" layoutY="48.0" prefHeight="25.0" prefWidth="80.0" onAction="#trimLabelPosition" >
            <cellFactory>
                <ComboBoxCellFactory />
            </cellFactory>
            <buttonCell>
                <ComboBoxCell />
            </buttonCell>
            <items>
                <FXCollections fx:factory="observableArrayList">
                    <ComboBoxOptions fx:value="GROUP1"/>
                    <ComboBoxOptions fx:value="A" />
                    <ComboBoxOptions fx:value="B" />
                    <ComboBoxOptions fx:value="GROUP2" />
                    <ComboBoxOptions fx:value="C" />
                    <ComboBoxOptions fx:value="D" />
                </FXCollections>
            </items>
        </ComboBox>
    </children>
</AnchorPane>

今回やりたい事にはあまりかかわりがないのでデザインは適当に


Mainクラス

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        AnchorPane anchorPane = FXMLLoader.load(getClass().getResource("OptGroupComboBox.fxml"));
        Scene root = new Scene(anchorPane);
        root.getStylesheets().add("sample/css/optGroup.css");
        primaryStage.setScene(root);
        primaryStage.show();
    }


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


よくあるstageの定義とfxml、CSSの読み込みをしてるだけ


Controllerクラス

package optGroupComboBox.Java;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.effect.Effect;
import javafx.util.Callback;
import optGroupComboBox.Java.Enum.ComboBoxOptions;

import java.net.URL;
import java.util.ResourceBundle;

public class Controller implements Initializable {


    @FXML
    private ComboBox<ComboBoxOptions> optGroupComboBox;

    @FXML
    private void trimLabelPosition(ActionEvent event) {
        optGroupComboBox.setId("selected");
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        optGroupComboBox.setCellFactory(new Callback<ListView<ComboBoxOptions>, ListCell<ComboBoxOptions>>() {
            @Override
            public ListCell<ComboBoxOptions> call(ListView<ComboBoxOptions> param) {
                return new ListCell<ComboBoxOptions>() {
                    @Override
                    protected void updateItem(ComboBoxOptions item, boolean empty) {
                        super.updateItem(item, empty);
                        if (empty) {
                            setText("");
                            setGraphic(null);
                        } else {
                            setText(item.getLabel());
                            if (item.isOption().equals("NOT_OPTION")) {
                                setId("notOption");
                                setDisable(true);
                            } else {
                                setId("option");
                            }

                        }
                    }
                };
            }
        });
    }
}



initializeの中でComboBoxに対してカスタムcell?を持ったListCellをsetしている。

Actionevent内では選択肢を押された後に左寄せに直すcssを適用。

ComboBoxOptions

package optGroupComboBox.Java;

public enum ComboBoxOptions {
    GROUP1("Group1","NOT_OPTION"),
    A("A","OPTION"),
    B("B","OPTION"),
    GROUP2("Group2","NOT_OPTION"),
    C("C","OPTION"),
    D("D","OPTION");


    private String label;
    private String isOption;

    ComboBoxOptions(String label, String isOption){
        this.label = label;
        this.isOption = isOption;
    }

    public String getLabel(){
        return this.label;
    }

    public String isOption(){
        return this.isOption;
    }
}

EnumでComboBoxの選択肢を定義。
ここで選択肢としないものはNOT_OPTIONとしておく

optGroup.css

#notOption{
    -fx-font-weight: bold;
}

#option{
    -fx-padding:0 0 0 15;
}

#selected{
    -fx-padding: 0 0 0 -5;
}

ここも単純にJavaFX用のCSSで太字化とpaddingの設定をちょろっと書いただけ

参考にしたサイト

http://stackoverflow.com/questions/20283940/javafx-combobox-with-custom-object-displays-object-address-though-custom-cell-fa


一応見た目はいい感じになったのだけれど、OPTIONと定義している選択肢を選んだあとに矢印キーなどで選択し直すとNOT_OPTIONと定義している選択肢が選択されてしまう、、、しかもEnumの列挙型名がそのまま表示される、、、

動きとしてはやっぱりGroup2をスキップしてC,Dが選べたりGroup1には移動せずにAで止まってほしいけどもどう制御すればいいかわからずお手上げ


参考サイトを見ればわかる通り私はそれほど深いところは理解していないのでどなたか解決法をご教示頂けるとすごくうれしいです。

一応Gitにも上げているのでpullRequestもらえたらとってもうれしいです。https://github.com/kurituedia/JavaFXTips