Search code examples
javafxjavafx-2javafx-8

JavaFX ChoiceBox add separator with type safety


I'm looking to add a separator into a choice box and still retain the type safety.

On all of the examples I've seen, they just do the following:

ChoiceBox<Object> cb =  new ChoiceBox<>();
cb.getItems().addAll("one", "two", new Separator(), "fadfadfasd", "afdafdsfas");

Has anyone come up with a solution to be able to add separators and still retain type safety?

I would expect that if I wanted to add separators, I should be able do something along the following:

ChoiceBox<T> cb = new ChoiceBox<T>();
cb.getSeparators().add(1, new Separator()); // 1 is the index of where the separator should be

I shouldn't have to sacrifice type safety just to add separators.


Solution

  • As already noted, are Separators only supported if added to the items (dirty, dirty). To support them along the lines expected in the question, we need to:

    • add the notion of list of separator to choiceBox
    • make its skin aware of that list

    While the former is not a big deal, the latter requires a complete re-write (mostly c&p) of its skin, as everything is tightly hidden in privacy. If the re-write has happened anyway, then it's just a couple of lines more :-)

    Just for fun, I'm experimenting with ChoiceBoxX that solves some nasty bugs in its selection handling, so couldn't resist to try.

    First, add support to the ChoiceBoxx itself:

    /**
     * Adds a separator index to the list. The separator is inserted 
     * after the item with the same index. Client code
     * must keep this list in sync with the data.
     * 
     * @param separator
     */
    public final void addSeparator(int separator) {
        if (separatorsList.getValue() == null) {
            separatorsList.setValue(FXCollections.observableArrayList());
        }
        separatorsList.getValue().add(separator);
    };
    

    Then some changes in ChoiceBoxXSkin

    • must listen to the separatorsList
    • must expect index-of-menuItem != index-of-choiceItem
    • menuItem must keep its index-of-choiceItem

    At its simplest, the listener re-builds the popup, the menuItem stores the dataIndex in its properties and all code that needs to access a popup by its dataIndex is delegated to a method that loops through the menuItems until it finds one that fits:

    protected RadioMenuItem getMenuItemFor(int dataIndex) {
        if (dataIndex < 0) return null;
        int loopIndex = dataIndex;
        while (loopIndex < popup.getItems().size()) {
            MenuItem item = popup.getItems().get(loopIndex);
    
            ObservableMap<Object, Object> properties = item.getProperties();
            Object object = properties.get("data-index");
            if ((object instanceof Integer) && dataIndex == (Integer) object) {
                return item instanceof RadioMenuItem ? (RadioMenuItem)item : null;
            }
            loopIndex++;
        }
        return null;
    }