当前位置: 首页 > 知识库问答 >
问题:

JavaFX CustomMenuItem与TextField的奇怪

干茂才
2023-03-14

也许有人能解释以下行为--希望能找到可行的解决办法...多谢了。

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

有什么想法吗?

共有1个答案

胡厉刚
2023-03-14

(至少部分)原因是ContextMenuContent(充当menuItems列表的皮肤)的内部被混淆了:

  • 它在ENTER时注册一个keyHandler,(最终)激发内部跟踪的当前焦点项
  • 单击自定义内容时,内部跟踪无法正常工作

一种方法是强制更新内部状态(注意:需要访问隐藏的API!)。在TextField的MouseEnter处理程序中。

    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());
    });
  • 对于customMenuItems,它会注册一个mouseEnterted处理程序,该处理程序请求将焦点集中在自身上
  • 对于其他类型的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);
    }
}

转发到父级会触发不正确的menuItem(由于内部簿记中的错误,参见上文),因此需要寻找另一种方法来防止它:

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

实际上,将该标记添加到属性中可以防止激发错误的项--没有访问内部类的权限(尽管仍然高度依赖于实现):

textField.getProperties().put(
   "TextInputControlBehavior.disableForwardToParent", true);
textField.addEventHandler(ActionEvent.ACTION, event -> {
 类似资料:
  • 问题内容: 和有什么区别?该文档说应该用于较小的字符串,而应用于较大的字符串。好的,但是“小”和“大”之间的界线在哪里?这到底是怎么回事? 问题答案: (或类似的)之间的区别是-通常以最大长度来指定,并且在性能或存储方面可能更有效-和(或类似)类型-这些通常仅受硬编码实现限制(而不是数据库架构)。 PostgreSQL 9特别指出“这三种类型之间没有性能差异”,但是AFAIK在MySQL等方面存在

  • 我有一个ui显示的数据表:repeat。因为我希望用户能够在每行的基础上更改数据,所以每行都包含在一个h:form中。最后,每个h:form都有一个带有f:ajax标记的按钮。我的行为越来越不一致。 上述方法可行,但带宽显然不便宜。 如果我将render=“@all”更改为render=“@form”,Firebug显示发送的部分响应正常,但我的浏览器(Firefox)神秘地没有显示它。所以我猜它

  • 利用UITextField实现stepper。用户点击“ ”按钮,stepper的数字加一,点击“-”按钮,stepper的数字减一。 [Code4App.com]

  • APAutocomplete-TextField 是一个具有自动完成功能的文本框,它的工作原理跟 Safari(iOS)或者是 Chrome(iOS) 搜索/地址栏一样。 --- 安装方式 ---使用 CocoaPods 来安装: pod 'APAutocompleteTextField'

  • 我有一个文本字段供用户输入,一旦它获得内容,我想执行一个服务调用。 谢谢!

  • SwiftUI 文本字段可以与可选绑定一起使用吗?当前此代码: 产生以下错误: 无法转换“Binding”类型的值 有什么办法解决这个问题吗?在数据模型中使用选项是一种非常常见的模式——事实上,这是核心数据中的默认模式,所以SwiftUI不支持它们似乎很奇怪