JavaFX CustomMenuItem与TextField的奇怪行为 [英] JavaFX CustomMenuItem strange behaviour with TextField

查看:181
本文介绍了JavaFX CustomMenuItem与TextField的奇怪行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

也许有人可以解释以下行为 - 并且希望可能的解决方法...谢谢。

maybe someone can explain the following behaviour - and hopefully, possible workarounds... thanks.

当使用包含TextField的CustomMenuItem时,MenuItem的动作<通过在文本字段中按Enter键来触发em> above ...除非textfield有一个actionlistener设置(未添加)...我需要使用addEventHandler,而不是setOnAction ...: - /

When using a CustomMenuItem containing a TextField, the action of the MenuItem above gets triggered by pressing enter inside the textfield... unless the textfield has an actionlistener set (not added)... I need to use addEventHandler, not 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();
    }
}

我正在使用Java 8.

I'm using Java 8.

任何想法?

推荐答案

(至少部分)原因是内部的ContextMenuContent(作为menuItem列表的皮肤类型)变得混乱:

The (at least part of) reason is that internals of ContextMenuContent (which acts as kind-of skin for the list of menuItems) get confused:


  • 它在ENTER上注册了一个keyHandler(在触发内部跟踪的当前关注项目

  • 内部跟踪在点击自定义内容时无法正常工作

黑客攻击是强制更新内部状态(注意:需要访问隐藏的api!),fi在textField的mouseEntered处理程序中。

A hack around is to force the update of internal state (beware: requires access to hidden api!), f.i. in a mouseEntered handler of the textField.

一些代码:

    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());
    });

bug

进一步挖掘:真正的罪魁祸首似乎是MenuItemContainer(这是容器)单个项目)

On further digging: the actual culprit seems to be MenuItemContainer (that is the container for a single item)


  • 对于customMenuItems,它会注册一个请求关注自身的mouseEntered处理程序

  • for其他类型的menuItems在其focusedProperty上注册一个监听器,它更新ContextMenuContent的currentFocusedIndex

一个干净的修复可能是注册焦点监听器对于所有项目(分隔符除外)

A clean fix might be to register the focus listener for all items (except separators)

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

Digging a bit further, turned up another option/bug ;) The reason for the different behaviour of setOnAction vs addHandler(ActionEvent...) is some fishy code in TextFieldBehaviour:

@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);
    }
}

我认为实施意图只有在有特殊的onAction处理程序或没有普通的处理程序消耗它。检查事件上的消耗总是返回false,即使处理程序已经消耗了它 - 因为事件在分派期间被复制...即处理程序更改了副本上消耗的内容,而不是原始内容。

I think the implementation intention was to forward only if either there's that special onAction handler or no normal handler had consumed it. Checking for consumed on the event always returns false, even if a handler had it consumed - because the event is copied during dispatch ... that is the handler changes the consumed on a copy, not the original.

转发到父级会触发错误的menuItem(由于内部簿记中的错误,见上文),所以寻找另一种方法来阻止它:

Forwarding to parent fires the incorrect menuItem (due to the bug in internal book-keeping, see above), so looking for another way to prevent it:

protected void forwardToParent(KeyEvent event) {
    // fix for JDK-8145515
    if (getNode().getProperties().containsKey(DISABLE_FORWARD_TO_PARENT)) {
        return;
    }

    if (getNode().getParent() != null) {
        getNode().getParent().fireEvent(event);
    }
}

实际上,将该标记添加到属性会阻止触发错误的项目 - 无法访问内部类(虽然仍然高度依赖于实现..):

and indeed, adding that marker to the properties prevents firing the wrong item - without access to internal classes (though still highly implementation dependent ..):

textField.getProperties().put(
   "TextInputControlBehavior.disableForwardToParent", true);
textField.addEventHandler(ActionEvent.ACTION, event -> {

这篇关于JavaFX CustomMenuItem与TextField的奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆