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

时间:2015-03-19 15:46:41

标签: javafx contextmenu eventhandler

我正在学习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);
编辑:我希望有一些简单的解决方案(一个班轮),但如果没有,那么有很多复杂的方法可以做到。

5 个答案:

答案 0 :(得分: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 :(得分: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 ;
}

答案 2 :(得分: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();
   ...
};

答案 3 :(得分:0)

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

show(Node node, ...)

  

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

Node getOwnerNode()

  

该弹出窗口的所有者节点。

所以MenuItem操作中的一般方法是

  • 掌握该项目的parentPopup(即contextMenu),如果存在嵌套菜单,则可能不得不使用梯形图
  • 抓住它的ownerNode
  • 访问所需的任何属性

最后的示例仅在copyText中执行此操作,并验证它是否按预期工作……如果我们不是使用控件的contextMenuProperty来。控件无法正常工作的原因是ContextMenu违反了方法合同(可能是introduced by a bug fix围绕textInputControls中的自动隐藏行为):在将show(Window w, ..)设置为任何内容的contextMenu后,它始终使用setShowRelativeToWindow(true)控件(实现细节:Control.contextMenuProperty设置一个标志show(Node owner, ... ),该标志触发错误行为)

现在我们该怎么做才能拥有ownerNode?有几种选择,但都不是很好的选择:

  • 与其他答案一样,以某种方式跟踪ownerNode:通过使用工厂方法,通过在用户属性中存储或任何其他临时手段
  • 扩展ContextMenu,覆盖show(Node owner, ...),并将给定的所有者保留在自定义属性中
  • 扩展ContextMenu,覆盖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()); } ,然后将超级ownerNode反射性地设置为给定
  • 变脏,然后在将菜单设置为任何控件后,将有问题的showRelativeToWindow标志重新设置为false

前两个引入了额外的耦合,后一个(除了肮脏的反射通道之外)可能会重新引入自动隐藏的问题(“固定”行为本身就是肮脏的..违反了“保持开放-如果拥有者”单击的“保证”

最后,举例说明:

INFO: Sensor CFamily [cpp]
ERROR: The only way to get an accurate analysis of C/C++/Objective-C files is by using the SonarSource build-wrapper
and setting the property "sonar.cfamily.build-wrapper-output", but it was not specified.
If you don't want to analyze C/C++/Objective-C files, then prevent them from being analyzed by setting the following properties:
sonar.c.file.suffixes=-
sonar.cpp.file.suffixes=-
sonar.objc.file.suffixes=-

答案 4 :(得分:0)

我知道距被问到已经有一段时间了,但是当我想用JavaFX上下文菜单解决类似的问题时,我遇到了这个问题,Oleksandr Potomkin的回答给了我一个解决方法的想法。 / p>

我想要实现的是一个正常运行的ContextMenu(用于多个字段),当我单击MenuItem时,将允许我访问打开上下文菜单(或被Accelerator调用)的控件。

我在设置Accelerator时也遇到了问题-如果我专注于表单,则可以使用,但是如果我专注于所需的控件,则无法使用。应该是相反的方式

我所做的是创建了一个类,该类将初始化ContextMenu(在其构造函数中)并共享一种方法将该上下文菜单链接至所需控件:

public class FieldContextMenu {
    ContextMenu menu;
    MenuItem menuCopy;

    public FieldContextMenu() {
        menu = new ContextMenu();

        menuCopy = new MenuItem("Copy");
        menuCopy.setAccelerator(KeyCombination.keyCombination("Ctrl+C"));
        menuCopy.setOnAction(event -> System.out.println(((TextField) menu.getUserData()).getText()));

        menu.getItems().addAll(menuCopy);
    }

    public void link(Control ctrl) {
        ctrl.setContextMenu(menu);
        // onKeyPressed so KeyCombination work while focused on this control
        ctrl.setOnKeyPressed(event -> {
            if(event.isControlDown() && event.getCode() == KeyCode.C) {
                menu.setUserData(ctrl);
                menuCopy.fire();
            }
        });
        // setting this control in menus UserData when ContextMenu is activated in this control
        ctrl.setOnContextMenuRequested(e -> menu.setUserData(ctrl));
    }
}

这是我在FXML Controller中使用它的方式:

public class ExampleController {
    @FXML private AnchorPane rootPane;
    @FXML private TextField textField1;
    @FXML private TextField textField2;

    @FXML protected void initialize() {
        // consume roots keyPressed event so the accelerator wouldn't "run" when outside of the control
        rootPane.setOnKeyPressed(event -> {
            if(event.isControlDown()) event.consume();
        });

        FieldContextMenu contextMenu = new FieldContextMenu();
        contextMenu.link(textField1);
        contextMenu.link(textField2);
    }
}

我的操作方式是仅一次初始化ContextMenu =减少内存使用量(如果我想的没错的话)。