JavaFX CustomMenuItem与TextField的奇怪行为

JavaFX CustomMenuItem与TextField的奇怪行为,javafx,javafx-8,Javafx,Javafx 8,也许有人可以解释以下行为——希望还有可能的解决办法。。。谢谢 当使用包含文本字段的CustomMenuItem时,在文本字段内按enter键将触发上面MenuItem的操作。。。除非textfield设置了actionlistener(未添加)。。。我需要使用addEventHandler,而不是setOnAction…:-/ import javafx.application.Application; import javafx.scene.Scene; import javafx.scene

也许有人可以解释以下行为——希望还有可能的解决办法。。。谢谢

当使用包含文本字段的CustomMenuItem时,在文本字段内按enter键将触发上面MenuItem的操作。。。除非textfield设置了actionlistener(未添加)。。。我需要使用addEventHandler,而不是setOnAction…:-/

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

public class CustomMenuTest extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {

        MenuButton menu = new MenuButton("Fancy Menu...");

        MenuItem hello = new MenuItem("Hello");
        hello.setOnAction(event -> System.out.println("Hello | " + event.getSource()));

        MenuItem world = new MenuItem("World!");
        world.setOnAction(event -> System.out.println("World! | " + event.getSource()));

        /*
        Set the cursor into the TextField, maybe type something, and hit enter.
        --> Expected: "ADD: <Text you typed> ..."
        --> Actual: "ADD: <Text you typed> ..." AND "World! ..." - so the button above gets triggered as well.
        If I activate listener (II) or (III), everything works fine - even the empty action in (III) does is job, but this is ugly...
        (And I can't use (II), because I need (I).
         */
        TextField textField = new TextField();
        /*   I */ textField.addEventHandler(ActionEvent.ACTION,
                                  event -> System.out.println("ADD: " + textField.getText() + " | " + event.getSource()));
        /*  II */ // textField.setOnAction(event -> System.out.println("SET: " + textField.getText() + " | " + event.getSource()));
        /* III */ // textField.setOnAction(__ -> {/* do nothing */});

        CustomMenuItem custom = new CustomMenuItem(textField, false);

        menu.getItems().addAll(hello, world, custom);

        primaryStage.setScene(new Scene(menu));
        primaryStage.show();
    }
}
导入javafx.application.application;
导入javafx.scene.scene;
导入javafx.scene.control.CustomMenuItem;
导入javafx.scene.control.MenuButton;
导入javafx.scene.control.MenuItem;
导入javafx.scene.control.TextField;
导入javafx.stage.stage;
公共类CustomMenuTest扩展了应用程序{
@凌驾
public void start(Stage primaryStage)引发异常{
菜单按钮菜单=新菜单按钮(“花式菜单…”);
MenuItem hello=新MenuItem(“hello”);
hello.setOnAction(event->System.out.println(“hello |”+event.getSource());
MenuItem world=新MenuItem(“世界!”);
world.setOnAction(event->System.out.println(“world!|“+event.getSource());
/*
将光标设置到文本字段中,可能键入一些内容,然后按enter键。
-->预期:“添加:…”
-->实际:“添加:…”和“世界!”——因此上面的按钮也会被触发。
如果我激活了监听器(II)或(III),一切都正常-即使是(III)中的空动作也可以,但这很难看。。。
(我不能使用(II),因为我需要(I)。
*/
TextField TextField=新的TextField();
/*I*/textField.addEventHandler(ActionEvent.ACTION,
event->System.out.println(“添加:”+textField.getText()+“|”+event.getSource());
/*II*///textField.setOnAction(event->System.out.println(“SET:”+textField.getText()+“|”+event.getSource());
/*III*///textField.setOnAction(u->{/*donothing*//});
CustomMenuItem custom=新的CustomMenuItem(textField,false);
menu.getItems().addAll(hello,world,custom);
设置场景(新场景(菜单));
primaryStage.show();
}
}
我正在使用Java8

有什么想法吗?

原因(至少部分)是ContextMenuContent(作为菜单项列表的皮肤)的内部结构变得混乱:

  • 它在ENTER上注册一个keydhandler,该keydhandler(最终)触发内部跟踪的当前焦点项目
  • 单击自定义内容时,内部跟踪无法正常工作
一种破解方法是在文本字段的鼠标插入处理程序中强制更新内部状态(注意:需要访问隐藏的api!)f.i

一些代码:

    TextField textField = new TextField();
    CustomMenuItem custom = new CustomMenuItem(textField, false);
    // menuItemContainer registers a mouseClicked handler that fires
    // need to consume before it reaches the container
    textField.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> e.consume());
    // hack to update internal state of ContextMenuItem
    textField.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> {
        ContextMenuContent cmContent = null;
        Node node = textField;
        while (node != null) {
            node = node.getParent();
            if (node instanceof ContextMenuContent) {
                cmContent = (ContextMenuContent) node;
                break;
            }
        }
        if (cmContent != null) {
            Parent menuItemContainer = textField.getParent();
            Parent menuBox = menuItemContainer.getParent();
            int index = menuBox.getChildrenUnmodifiable().indexOf(menuItemContainer);
            // hack internal state
            cmContent.requestFocusOnIndex(index);
        }
    });
    /* I */
    textField.addEventHandler(ActionEvent.ACTION, event -> {
        System.out.println("ADD: " + textField.getText() + " | "
                + event.getSource()
        );
        // consume to prevent item to fire twice
        event.consume();

    });

    custom.setOnAction(e -> {
        // someone needs to hide the popup
        // could be done in textField actionHandler as well
        if (custom.getParentPopup() != null) {
            custom.getParentPopup().hide();
        }
        System.out.println("action custom menu " + e.getSource());
    });
据报道,这是一个错误

进一步挖掘:真正的罪魁祸首似乎是MenuItemContainer(即单个物品的容器)

  • 对于customMenuItems,它注册一个mouseEntered处理程序,该处理程序请求关注自身
  • 对于其他类型的menuItems,它在其focusedProperty上注册一个侦听器,该属性更新ContextMenuContent的currentFocusedIndex
一个干净的解决方案可能是为所有项目(分隔符除外)注册焦点侦听器


进一步挖掘,发现了另一个选项/bug;)setOnAction与addHandler(ActionEvent…)行为不同的原因是TextFieldBehavior中的一些可疑代码:

@Override protected void fire(KeyEvent event) {
    TextField textField = getNode();
    EventHandler<ActionEvent> onAction = textField.getOnAction();
    ActionEvent actionEvent = new ActionEvent(textField, null);

    textField.commitValue();
    textField.fireEvent(actionEvent);
    // ---> this condition smells 
    if (onAction == null && !actionEvent.isConsumed()) {
        forwardToParent(event);
    }
}
事实上,将该标记添加到属性中可以防止触发错误的项,而无需访问内部类(尽管仍然高度依赖于实现)

(至少部分)原因是ContextMenuContent(作为菜单项列表的皮肤)的内部结构变得混乱:

  • 它在ENTER上注册一个keydhandler,该keydhandler(最终)触发内部跟踪的当前焦点项目
  • 单击自定义内容时,内部跟踪无法正常工作
一种破解方法是在文本字段的鼠标插入处理程序中强制更新内部状态(注意:需要访问隐藏的api!)f.i

一些代码:

    TextField textField = new TextField();
    CustomMenuItem custom = new CustomMenuItem(textField, false);
    // menuItemContainer registers a mouseClicked handler that fires
    // need to consume before it reaches the container
    textField.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> e.consume());
    // hack to update internal state of ContextMenuItem
    textField.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> {
        ContextMenuContent cmContent = null;
        Node node = textField;
        while (node != null) {
            node = node.getParent();
            if (node instanceof ContextMenuContent) {
                cmContent = (ContextMenuContent) node;
                break;
            }
        }
        if (cmContent != null) {
            Parent menuItemContainer = textField.getParent();
            Parent menuBox = menuItemContainer.getParent();
            int index = menuBox.getChildrenUnmodifiable().indexOf(menuItemContainer);
            // hack internal state
            cmContent.requestFocusOnIndex(index);
        }
    });
    /* I */
    textField.addEventHandler(ActionEvent.ACTION, event -> {
        System.out.println("ADD: " + textField.getText() + " | "
                + event.getSource()
        );
        // consume to prevent item to fire twice
        event.consume();

    });

    custom.setOnAction(e -> {
        // someone needs to hide the popup
        // could be done in textField actionHandler as well
        if (custom.getParentPopup() != null) {
            custom.getParentPopup().hide();
        }
        System.out.println("action custom menu " + e.getSource());
    });
据报道,这是一个错误

进一步挖掘:真正的罪魁祸首似乎是MenuItemContainer(即单个物品的容器)

  • 对于customMenuItems,它注册一个mouseEntered处理程序,该处理程序请求关注自身
  • 对于其他类型的menuItems,它在其focusedProperty上注册一个侦听器,该属性更新ContextMenuContent的currentFocusedIndex
一个干净的解决方案可能是为所有项目(分隔符除外)注册焦点侦听器


进一步挖掘,发现了另一个选项/bug;)setOnAction与addHandler(ActionEvent…)行为不同的原因是TextFieldBehavior中的一些可疑代码:

@Override protected void fire(KeyEvent event) {
    TextField textField = getNode();
    EventHandler<ActionEvent> onAction = textField.getOnAction();
    ActionEvent actionEvent = new ActionEvent(textField, null);

    textField.commitValue();
    textField.fireEvent(actionEvent);
    // ---> this condition smells 
    if (onAction == null && !actionEvent.isConsumed()) {
        forwardToParent(event);
    }
}
事实上,将该标记添加到属性中可以防止触发错误的项,而无需访问内部类(尽管仍然高度依赖于实现)


适用于我(JavaFX8U161;windows)。您使用的确切版本/操作系统是什么?您试图产生的结果是什么?我试图通过更改上面的注释来澄清预期的行为。我希望文本字段会触发,但不会触发上面的按钮。我使用的是java 8u111,但在更新到8u171后,问题是相同的…在fx10中也确认了(消费时也是如此)。。。嗯,你为什么需要第一版?顺便说一句,另外两个不会关闭菜单,所以它看起来像是某个地方丢失了一块。对我有用(JavaFX8U161;windows)。您使用的确切版本/操作系统是什么?您试图产生的结果是什么?我试图通过更改上面的注释来澄清预期的行为。我希望文本字段会触发,但不会触发上面的按钮。我使用的是java 8u111,但在更新到8u171后,问题是相同的…在fx10中也确认了(消费时也是如此)。。。嗯,你为什么需要第一版?顺便说一句,另外两个不会关闭菜单,所以看起来好像少了一块