Articles

Creating Custom Menu Separator in JavaFX

We can create custom menu easily in JavaFX using CustomMenuItem class. I was thinking that using that class, I can creat menu separator that has label. What I want to use with that was to group menu items. Each group should have label name using the menu separator. So, I created a component namely LabelSeparator that will fill CustomMenuItem.

public class LabelSeparator extends StackPane {

    private Label lblText;

    public LabelSeparator(String label) {
        this(label, true);
    }

    public LabelSeparator(String label, boolean topPading) {
        HBox line = HBoxBuilder.create()
                .styleClass("line")
                .minHeight(2)
                .prefHeight(2)
                .prefWidth(USE_PREF_SIZE)
                .maxHeight(USE_PREF_SIZE)
                .build();
        if (topPading) {
            setPadding(new Insets(10, 0, 0, 0));
        }
        lblText = LabelBuilder.create().text(label).build();
        this.getChildren().addAll(line, lblText);
        this.getStyleClass().add("label-separator");

    }

    public void setText(String label) {
        textProperty().set(label);
    }

    public String getText() {
        return textProperty().get();
    }

    public StringProperty textProperty() {
        return lblText.textProperty();
    }
}

But when I used that class to fill CustomMenuItem and added it to ContextMenu, the menu item didn’t fill horizontally as I expected. It also still received blue-background when hovered. I checked the source code of SeparatorMenuItem, but I found no clues. I checked deeper by searching what classes use SeparatorMenuItem. I found that there are many line of

if (item instanceof SeparatorMenuItem) {
...
}

I came to a conclusion that SeparatorMenuItem is treated differently.

So I changed my approach. I created a class extending SeparatorMenuItem so it will be treated as one.

public class LabelSeparatorMenuItem extends SeparatorMenuItem {

    public LabelSeparatorMenuItem(String label) {
        this(label, true);
    }

    public LabelSeparatorMenuItem(String label, boolean topPading) {
        super();
        LabelSeparator content = new LabelSeparator(label, topPading);
        content.setPrefHeight(LabelSeparator.USE_COMPUTED_SIZE);
        content.setMinHeight(LabelSeparator.USE_PREF_SIZE);
        setContent(content);

    }

}

Nice, It worked as expected. The separator filled horizontally and it ignored mouse events.

LabelSeparatorMenuItem

The CSS for LabelSeparator is below

.label-separator .line {
    -fx-border-color: derive(-fx-background, -25%) -fx-background derive(-fx-background, 40%) -fx-background;
}

.label-separator .label {
    -fx-background-color: 
        #090a0c,
        linear-gradient(#38424b 0%, #1f2429 20%, #191d22 100%),
        linear-gradient(#20262b, #191d22),
        radial-gradient(center 50% 0%, radius 100%, rgba(114,131,148,0.9), rgba(255,255,255,0));
    -fx-background-radius: 5,4,3,5;
    -fx-background-insets: 0,1,2,0;
    -fx-text-fill: white;
    -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 5, 0.0 , 0 , 1 );
    -fx-font-family: "Arial";
    -fx-text-fill: linear-gradient(white, #d0d0d0);
    -fx-font-size: 12px;
    -fx-padding: 2 10;
}