向JavaFX上下文菜单添加标题

向JavaFX上下文菜单添加标题,javafx,Javafx,是否可以向JavaFXTableViewContextMenu添加标题 现在,我添加了一个MenuItem,我已将其标记为禁用(通过menuTitle.setDisable(true)),但它使用一些样式设置(不透明度)进行渲染,我还没有弄清楚如何覆盖这些设置 代码示例- import java.util.function.Function; import java.util.stream.IntStream; import javafx.application.Application; im

是否可以向JavaFX
TableView
ContextMenu
添加标题

现在,我添加了一个
MenuItem
,我已将其标记为禁用(通过
menuTitle.setDisable(true)
),但它使用一些样式设置(不透明度)进行渲染,我还没有弄清楚如何覆盖这些设置

代码示例-

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);
    }
}
import java.util.function.function;
导入java.util.stream.IntStream;
导入javafx.application.application;
导入javafx.beans.property.BooleanProperty;
导入javafx.beans.property.IntegerProperty;
导入javafx.beans.property.SimpleBoleAnProperty;
导入javafx.beans.property.SimpleIntegerProperty;
导入javafx.beans.property.SimpleStringProperty;
导入javafx.beans.property.StringProperty;
导入javafx.beans.value.observeValue;
导入javafx.scene.scene;
导入javafx.scene.control.CheckMenuItem;
导入javafx.scene.control.ContextMenu;
导入javafx.scene.control.MenuItem;
导入javafx.scene.control.SeparatorMenuItem;
导入javafx.scene.control.TableColumn;
导入javafx.scene.control.TableRow;
导入javafx.scene.control.TableView;
导入javafx.scene.layout.BorderPane;
导入javafx.stage.stage;
公共类TableWithContextMenu扩展了应用程序{
@凌驾
公共无效开始(阶段primaryStage){
TableView table=新TableView();
table.getColumns().add(列(“项”,项::nameProperty));
table.getColumns().add(column(“Value”,Item::valueProperty));
BooleanProperty globalSelection=新的SimpleBoleAnProperty();
表3.setRowFactory(t->{
TableRow行=新TableRow();
ContextMenu ContextMenu=新建ContextMenu();
MenuItem menuTitle=新菜单项(“菜单标题”);
menuTitle.setDisable(true);
MenuItem2=新MenuItem(“做其他事情”);
item2.setOnAction(e->System.out.println(“使用“+row.getItem().getName()”)执行其他操作);
CheckMenuItem3=新的CheckMenuItem(“全局选择”);
item3.selectedProperty().BindBidirective(全局选择);
contextMenu.getItems().addAll(menutile,item2,新分隔符numitem(),item3);
row.emptyProperty().addListener((obs,waspempty,isEmpty)->{
如果(我是空的){
row.setContextMenu(空);
}否则{
row.setContextMenu(contextMenu);
}
});
返回行;
});
IntStream.rangeClosed(1,25).mapToObj(i->newitem(“Item”+i,i)).forEach(table.getItems()::add);
设置场景(新场景(新边框窗格(表),800600));
primaryStage.show();
}
私有表列(字符串标题、函数属性){
TableColumn col=新的TableColumn(标题);
col.setCellValueFactory(cellData->property.apply(cellData.getValue());
返回列;
}
公共静态类项{
私有最终IntegerProperty值=新的SimpleIntegerProperty();
私有最终StringProperty名称=新SimpleStringProperty();
公共项(字符串名称、int值){
集合名(名称);
设置值(值);
}
公共最终整数属性值属性(){
返回此.value;
}
公共最终整数getValue(){
返回此.valueProperty().get();
}
公共最终无效设置值(最终整数值){
this.valueProperty().set(值);
}
公共最终字符串属性nameProperty(){
返回此.name;
}
公共最终字符串getName(){
返回此.nameProperty().get();
}
公共最终void集合名(最终字符串名){
this.nameProperty().set(name);
}
}
公共静态void main(字符串[]args){
发射(args);
}
}
编辑
下面发布的解决方案适用于此示例。但是,当处理多个FXML时,除非清除样式类,然后应用自定义样式,否则它不会工作。我已经发布了我的工作代码来演示这个问题。

您不能在文件中添加标题

ContextMenu从中扩展,不允许窗口装饰或标题栏

从文档中:

弹出窗口是没有窗口装饰或标题栏的辅助窗口。它不会作为顶级窗口显示在操作系统中。它通常用于通知、下拉框、菜单等工具提示


禁用的
MenuItem
的不透明度在CSS样式类
菜单项:disabled
中指定。要完全消除不透明度,还需要覆盖已禁用的
MenuItem
命名的
菜单项中的
标签的样式类:disabled.Label
。只需在自己的css文件中指定样式即可覆盖该样式:

例如:

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

.menu-item:disabled .label {
    -fx-opacity: 1.0;
}
不过,您可能不希望其他不应作为标题的禁用菜单项具有相同的样式。在这种情况下,只需将样式类添加到title
MenuItem

ContextMenu contextMenu = new ContextMenu();
MenuItem titleItem = new MenuItem("Title");
titleItem.setDisable(true);
titleItem.getStyleClass().add("context-menu-title");
然后css将是:

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

.context-menu-title:disabled .label {
    -fx-opacity: 1.0;
}
编辑:您可能不希望标题与其他菜单项具有相同的
:hover
样式。这可以通过为标题
菜单项设置
:disabled:hover
来解决。例如:

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

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

这在我上面粘贴的独立示例中起作用。然而,我无法让它在一个更复杂的程序中工作,因为我有多个FXML文件(每个文件都有自己的css)。从调试器中,我确认行应用了两个样式类——“菜单项”和“上下文菜单标题”。我实现这一点的唯一方法是通过
row.getStyleClass().clear()
删除类,然后只添加“上下文菜单标题”。然而,这样做的一个副作用是我失去了另一个s