JavaFX ContextMenu如何获取被点击的对象?

问题描述 投票:3回答:4

我正在学习javafx.scene.control.ContextMenu,现在我遇到了一个问题:

如何从EventHandler获取单击的对象? event.source()和event.target()都返回MenuItem。

让我用一个例子来解释一下:我应该在函数句柄内写什么?

    TextField text = new TextField();
    Label label1 = new Label("hello");
    Label label2 = new Label("world");
    Label label3 = new Label("java");

    ContextMenu menu = new ContextMenu();
    MenuItem item = new MenuItem("copy to text field");
    menu.getItems().add(item);
    item.setOnAction(new EventHandler(){
        public void handle(Event event) {
            //I want to copy the text of the Label I clicked to TextField
            event.consume();
        }
    });

    label1.setContextMenu(menu);
    label2.setContextMenu(menu);
    label3.setContextMenu(menu);

编辑:我希望有一些简单的解决方案(一个班轮),但如果没有,那么有很多复杂的方法来做到这一点。

javafx contextmenu eventhandler
4个回答
2
投票

您可以创建自己的ContextMenu实例并向其添加操作父级以供进一步参考:

public class Main extends Application {

    TextField text = new TextField();

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

    @Override
    public void start(Stage primaryStage) {


        Label label1 = new Label("hello");
        Label label2 = new Label("world");
        Label label3 = new Label("java");

        label1.setContextMenu(new MyContextMenu(label1));
        label2.setContextMenu(new MyContextMenu(label2));
        label3.setContextMenu(new MyContextMenu(label3));

        HBox root = new HBox();

        root.getChildren().addAll(text, label1, label2, label3);

        Scene scene = new Scene(root, 300, 100);

        primaryStage.setScene(scene);
        primaryStage.show();

    }

    private class MyContextMenu extends ContextMenu {

        public MyContextMenu(Label label) {

            MenuItem item = new MenuItem("copy to text field");
            item.setOnAction(event -> {

                // I want to copy the text of the Label I clicked to TextField
                text.setText(label.getText());

                event.consume();
            });

            getItems().add(item);

        }

    }
}

1
投票

只需为每个标签创建一个不同的ContextMenu实例:

TextField text = new TextField();
Label label1 = new Label("hello");
Label label2 = new Label("world");
Label label3 = new Label("java");

label1.setContextMenu(createContextMenu(label1, text));       
label2.setContextMenu(createContextMenu(label2, text));            
label3.setContextMenu(createContextMenu(label3, text));

// ...

private ContextMenu createContextMenu(Label label, TextField text) {
    ContextMenu menu = new ContextMenu();
    MenuItem item = new MenuItem("copy to text field");
    menu.getItems().add(item);
    item.setOnAction(new EventHandler(){
        public void handle(Event event) {
            text.setText(label.getText());
        }
    });
    return menu ;
}

1
投票

我认为最简单的方法是将Node保存为上下文菜单的UserData。

EventHandler<? super ContextMenuEvent> eventHandle = e->menu.setUseData(e.getSource());
label1.setOnContextMenuRequested(eventHandle );
label2.setOnContextMenuRequested(eventHandle );
label3.setOnContextMenuRequested(eventHandle );

并在行动:

EventHandler<ActionEvent> menuItemEvent = e->{
    Node node = (Node) ((MenuItem)e.getSource()).getParentPopup().getUserData();
   ...
};

0
投票

总结基本要求:掌握打开contextMenu的节点。根据PopupWindow(ContextMenu的祖父母)的api文档,这应该很容易实现

show(Node node, ...)

...弹出窗口与指定的所有者节点相关联...

Node getOwnerNode()

作为此弹出窗口所有者的节点。

因此,MenuItem动作的一般方法是

  • 获取项目的parentPopup(即contextMenu),如果有嵌套菜单,可能需要处理阶梯
  • 抓住它的ownerNode
  • 访问任何需要的财产

最后的例子只是在copyText中验证,它正在按预期工作...如果我们没有使用控件的contextMenuProperty。控件中不起作用的原因是ContextMenu的方法合同违规(可能是围绕textInputControls中的自动隐藏行为的introduced by a bug fix):它在将contextMenu设置为任何控件之后总是使用show(Window w, ..)(实现细节:Control.contextMenuProperty)设置一个触发错误行为的标志setShowRelativeToWindow(true)

现在我们可以做些什么来获取ownerNode?有几个选项,其中没有一个是好的:

  • 在其他答案中完成,以某种方式跟踪ownerNode:通过使用工厂方法,通过存储在用户属性或任何其他ad-hoc手段
  • 扩展ContextMenu,覆盖show(Node owner, ... )并将给定的所有者保留在自定义属性中
  • 扩展ContextMenu,覆盖show(Node owner, ...) go dirty并反射性地将super ownerNode设置为给定的
  • 将菜单设置为任何控件后,将脏的并反射性地将违规的showRelativeToWindow标志重置为false

前两个引入额外的耦合,后者(除了脏反射访问)可能会重新引入自动隐藏的问题(“固定”行为本身就是脏的......违反了“keep-open-if-owner-clicked”保证)

最后,一个例子:

public class ContextMenuOwnerSO extends Application {

    private Parent createContent() {

        TextField text = new TextField();
        // the general approach to grab a property from the Node
        // that the ContextMenu was opened on
        EventHandler<ActionEvent> copyText = e -> {
            MenuItem source = (MenuItem) e.getTarget();
            ContextMenu popup = source.getParentPopup();
            String ownerText = "<not available>";
            if (popup != null) {
                Node ownerNode = popup.getOwnerNode();
                if (ownerNode instanceof Labeled) {
                    ownerText = ((Label) ownerNode).getText();
                } else if (ownerNode instanceof Text) {
                    ownerText = ((Text) ownerNode).getText();
                }
            }
            text.setText(ownerText);
        };

        MenuItem printOwner = new MenuItem("copy to text field");
        printOwner.setOnAction(copyText);

        // verify with manual managing of contextMenu
        Text textNode = new Text("I DON'T HAVE a contextMenu property");
        Label textNode2 = new Label("I'm NOT USING the contextMenu property");
        ContextMenu nodeMenu = new ContextMenu();
        nodeMenu.getItems().addAll(printOwner);
        EventHandler<ContextMenuEvent> openRequest = e -> {
            nodeMenu.show((Node) e.getSource(), Side.BOTTOM, 0, 0);
            e.consume();
        };

        textNode.setOnContextMenuRequested(openRequest);
        textNode2.setOnContextMenuRequested(openRequest);

        Label label1 = new Label("I'm USING the contextMenu property");

        ContextMenu menu = new ContextMenu() {

            // force menu to have an owner node: this being the case, it is not hidden 
            // on mouse events inside its owner
            //@Override
            //public void show(Node anchor, double screenX, double screenY) {
            //    ReadOnlyObjectWrapper<Node> owner = 
            //            (ReadOnlyObjectWrapper<Node>) 
            //            FXUtils.invokeGetFieldValue(PopupWindow.class, this, "ownerNode");
            //    owner.set(anchor);
            //    super.show(anchor, screenX, screenY);
            //}

        };
        MenuItem item = new MenuItem("copy to text field");
        menu.getItems().add(item);
        item.setOnAction(copyText);

        label1.setContextMenu(menu);
        // same effect as forcing the owner node 
        // has to be done after the last setting of contextMenuProperty 
        // setting to true was introduced as fix for
        // https://bugs.openjdk.java.net/browse/JDK-8114638
        //FXUtils.invokeGetMethodValue(ContextMenu.class, menu, "setShowRelativeToWindow", Boolean.TYPE, false);

        VBox content = new VBox(10, textNode, textNode2, text, label1);
        return content;

    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent(), 400, 200));
        stage.setTitle(FXUtils.version());
        stage.show();
    }

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

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(ContextMenuOwnerSO.class.getName());

}
© www.soinside.com 2019 - 2024. All rights reserved.