Darth Ninja
Darth Ninja

Reputation: 1039

Adding a title to a JavaFX ContextMenu

Is it possible to add a title to a JavaFX TableView ContextMenu?

For now, I have added a MenuItem which I have marked disabled (via menuTitle.setDisable(true)), but that is rendered with some style settings (opacity) that I havent figured out how to override.

Code sample -

import java.util.function.Function;
import java.util.stream.IntStream;

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class TableWithContextMenu extends Application {

    @Override
    public void start(Stage primaryStage) {
        TableView<Item> table = new TableView<>();
        table.getColumns().add(column("Item", Item::nameProperty));
        table.getColumns().add(column("Value", Item::valueProperty));

        BooleanProperty globalSelection = new SimpleBooleanProperty();

        table.setRowFactory(t -> {
            TableRow<Item> row = new TableRow<>();
            ContextMenu contextMenu = new ContextMenu();
            MenuItem menuTitle = new MenuItem("Menu Title");
            menuTitle.setDisable(true);

            MenuItem item2 = new MenuItem("Do something else");
            item2.setOnAction(e -> System.out.println("Do something else with " + row.getItem().getName()));

            CheckMenuItem item3 = new CheckMenuItem("Global selection");
            item3.selectedProperty().bindBidirectional(globalSelection);

            contextMenu.getItems().addAll(menuTitle, item2, new SeparatorMenuItem(), item3);

            row.emptyProperty().addListener((obs, wasEmpty, isEmpty) -> {
                if (isEmpty) {
                    row.setContextMenu(null);
                } else {
                    row.setContextMenu(contextMenu);
                }
            });
            return row ;
        });

        IntStream.rangeClosed(1, 25).mapToObj(i -> new Item("Item "+i, i)).forEach(table.getItems()::add);

        primaryStage.setScene(new Scene(new BorderPane(table), 800, 600));
        primaryStage.show();
    }

    private <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property) {
        TableColumn<S,T> col = new TableColumn<>(title);
        col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
        return col ;
    }

    public static class Item {
        private final IntegerProperty value = new SimpleIntegerProperty();
        private final StringProperty name = new SimpleStringProperty();

        public Item(String name, int value) {
            setName(name);
            setValue(value);
        }

        public final IntegerProperty valueProperty() {
            return this.value;
        }

        public final int getValue() {
            return this.valueProperty().get();
        }

        public final void setValue(final int value) {
            this.valueProperty().set(value);
        }

        public final StringProperty nameProperty() {
            return this.name;
        }

        public final String getName() {
            return this.nameProperty().get();
        }

        public final void setName(final String name) {
            this.nameProperty().set(name);
        }


    }

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

EDIT The solution posted below, works for this example. However, when dealing with multiple FXML, it doesnt work unless I clear out the style classes and then apply the custom style. I have posted my working code here that demonstrates the issue.

Upvotes: 3

Views: 1970

Answers (2)

Jonatan Stenbacka
Jonatan Stenbacka

Reputation: 1864

The opacity of a disabled MenuItemis specified in the CSS style class menu-item:disabled. To completely get rid of the opacity you also need to override the style class for the Label inside that disabled MenuItem named menu-item:disabled .label. Just override that style by specifying it yourself in your own css file:

Example:

.menu-item:disabled {
    -fx-opacity: 1.0;
}

.menu-item:disabled .label {
    -fx-opacity: 1.0;
}

You probably don't want other disabled menu items that isn't supposed to be a title to have the same style though. In this case you could just add a style class to the title MenuItem:

ContextMenu contextMenu = new ContextMenu();
MenuItem titleItem = new MenuItem("Title");
titleItem.setDisable(true);
titleItem.getStyleClass().add("context-menu-title");

And the css would then be:

.context-menu-title:disabled {
    -fx-opacity: 1.0;
}

.context-menu-title:disabled .label {
    -fx-opacity: 1.0;
}

Edit: You probably don't want the title to have the same :hover style as the rest of the menu items either. This could be fixed by setting :disabled:hover for the title MenuItem. E.g:

.context-menu-title:disabled:hover {
    -fx-background-color: white;
}

.context-menu-title:disabled:hover .label {
    -fx-background-color: white;
    -fx-text-fill: black;
}

Upvotes: 5

ItachiUchiha
ItachiUchiha

Reputation: 36722

You cannot add a title to a ContextMenu.

ContextMenu are extended from PopupWindow which do not allow window decoration or title bar.

From the docs :

A PopupWindow is a secondary window which has no window decorations or title bar. It doesn't show up in the OS as a top-level window. It is typically used for tool tip like notification, drop down boxes, menus, and so forth.

Upvotes: 0

Related Questions