【问题标题】:Is it possible to consume the event from a JavaFX FileChooser window?是否可以使用 JavaFX FileChooser 窗口中的事件?
【发布时间】:2019-06-14 08:38:26
【问题描述】:

我有一个 JavaFX 按钮,当用户按下回车键时触发。这会导致 FileChooser 打开。有些人(比如我自己)可能会在 FileChooser 中按 Enter 键来保存文件。但是,这会导致保存按钮再次触发自身并再次打开 FileChooser 以保存新文件。用鼠标单击按钮(在 FileChooser 中)没有这个问题。

我认为使用按钮中的事件可以解决这个问题,但它只使用 GUI 事件上的按钮,而不是 FileChooser 按钮。我尝试寻找修改 FileChooser 的 EventHandler 以使用回车键的方法,但没有成功。
我还尝试将焦点从按钮上移开并将其移至父级(窗格),因此无法再次单击它。但是,有些按钮可以多次单击而无需重新获得焦点。

我的代码示例如下所示(显然这将是扩展应用程序的更大类的一部分):

EventHandler<KeyEvent> enter = event -> {
    if (event.getCode() == KeyCode.ENTER && event.getSource() instanceof Button) {
        Button src = (Button) event.getSource();
        src.fire();
    }
    event.consume();
};

Button b1 = new Button("Save");

b1.setOnKeyReleased(enter);

/* Called by .fire method */
b1.setOnAction(event -> {
    /* Create the save dialog box */
    FileChooser saveDialog = new FileChooser();
    saveDialog.setTitle("Save");

    /* Get file */
    File f = saveDialog.showSaveDialog(stage);
    /*
     * ... do stuff with file ...
     */
});

注意:这个例子不是我的确切代码。相反,按键释放事件是用于多个按钮的变量,而不仅仅是保存按钮(即b2.setOnKeyReleased(enter); b2.setOnAction(event -&gt; {/* Do something */});)。

当用户在 FileChooser 中按下回车键时,如何防止按钮触发?如果他们没有鼠标,我不希望用户陷入循环。我知道按 Alt+S 也会保存它,但我不能指望所有用户都知道这一点。

编辑:根据现在似乎已被删除的评论中的要求,这是代码的可运行版本:

import java.io.File;

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

public class ButtonTest extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        /* EventHandler to be used with multiple buttons */
        EventHandler<KeyEvent> enter = event -> {
            if (event.getCode() == KeyCode.ENTER && event.getSource() instanceof Button) {
                Button src = (Button) event.getSource();
                src.fire();
            }
            event.consume();
        };

        /* Create a new button */
        Button b1 = new Button("Save");
        Button b2 = new Button("Print");
        /* Add event handlers */
        b1.setOnKeyReleased(enter);
        b2.setOnKeyReleased(enter);

        /* Called by .fire method of save button */
        b1.setOnAction(event -> {
            /* Create the save dialog box */
            FileChooser saveDialog = new FileChooser();
            saveDialog.setTitle("Save");

            /* Get file */
            File f = saveDialog.showSaveDialog(stage);
            /* ... do stuff with file ... */
        });
        /* Called by .fire method of print button */
        b2.setOnAction(event -> System.out.println("Pressed"));

        Scene scene = new Scene(new HBox(b1, b2));
        stage.setScene(scene);
        stage.show();
    }

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

【问题讨论】:

  • 我很困惑为什么你会在onKeyReleased 处理程序中调用src.fire()onAction 处理程序已由 enter 键调用(即 enter 键触发按钮)。
  • 不适合我。空格键是触发按钮的唯一键。我认为这样做的原因是因为 enter 用于触发使用 btn.setDefaultButton(true); 设置的默认按钮
  • 我无法重现该问题。如果Button 具有焦点,则按下回车会触发动作事件并显示FileChooser。如果我删除了onKeyReleased 处理程序,则FileChooser 在通过按回车键关闭它时不会重新出现。 编辑: 我可以使用 Java 8u202 重现该问题,但无法在 OpenJFX 11.0.2 上重现该问题。我正在寻找有关任何更改的错误报告,但我只能找到 1.3 版固定 2.0 的错误报告。
  • 您能告诉我您使用的是什么操作系统和 JavaFX 版本吗?因为如果您使用的是 JavaFX 8,我有一个潜在的答案。
  • Windows 7 和 JavaFX 8。感谢您付出如此多的努力来提供帮助。

标签: events button javafx event-handling filechooser


【解决方案1】:

问题是从onKeyReleased 处理程序触发Button。当您释放 ENTER 键时,FileChooser 已被隐藏,Stage 已重新获得焦点,这意味着您的Stage/Button 将释放键事件。显然这会导致一个循环。

一种可能的解决方案是从onKeyPressed 处理程序内部触发Button。与其他应用程序相比,这会产生略微不同的行为,但您的用户可能不会期望/欣赏。

另一个可能的解决方案是在触发Button 之前跟踪FileChooser 是否已打开,就像Matt 在his answer 中所做的那样。

您似乎试图做的是允许用户使用 ENTER 键来触发Button;这应该是 Windows 等平台上的默认行为。

不适合我。空格是触发按钮的唯一键。我认为这样做的原因是因为 enter 用于触发使用btn.setDefaultButton(true);设置的默认按钮

对我来说,当Button 具有焦点时按 ENTER 会在 Windows 10 上使用 JavaFX 11.0.2 而不是 JavaFX 8u202 时触发动作事件。它似乎是 Button 的行为自 JavaFX 8 以来发生了变化。下面是 com.sun.javafx.scene.control.behavior.ButtonBehavior 的不同实现,显示了已注册的键绑定。

JavaFX 8u202

protected static final List<KeyBinding> BUTTON_BINDINGS = new ArrayList<KeyBinding>();
static {
        BUTTON_BINDINGS.add(new KeyBinding(SPACE, KEY_PRESSED, PRESS_ACTION));
        BUTTON_BINDINGS.add(new KeyBinding(SPACE, KEY_RELEASED, RELEASE_ACTION));
}

JavaFX 11.0.2

public ButtonBehavior(C control) {
    super(control);

    /* SOME CODE OMITTED FOR BREVITY */

    // then button-specific mappings for key and mouse input
    addDefaultMapping(buttonInputMap,
        new KeyMapping(SPACE, KeyEvent.KEY_PRESSED, this::keyPressed),
        new KeyMapping(SPACE, KeyEvent.KEY_RELEASED, this::keyReleased),
        new MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed),
        new MouseMapping(MouseEvent.MOUSE_RELEASED, this::mouseReleased),
        new MouseMapping(MouseEvent.MOUSE_ENTERED, this::mouseEntered),
        new MouseMapping(MouseEvent.MOUSE_EXITED, this::mouseExited),

        // on non-Mac OS platforms, we support pressing the ENTER key to activate the button
        new KeyMapping(new KeyBinding(ENTER, KeyEvent.KEY_PRESSED), this::keyPressed, event -> PlatformUtil.isMac()),
        new KeyMapping(new KeyBinding(ENTER, KeyEvent.KEY_RELEASED), this::keyReleased, event -> PlatformUtil.isMac())
    );

    /* SOME CODE OMITTED FOR BREVITY */

}

如您所见,两者都注册 SPACE 以在 Button 获得焦点时触发它。但是,JavaFX 11.0.2 实现也注册了 ENTER,但仅适用于非 Mac OS 平台。我找不到任何关于这种行为变化的文档。

如果您想在 JavaFX 8 中实现相同的行为,并且不介意侵入 JavaFX 的内部,那么您可以使用反射来改变您的 all 按钮式控件的行为应用。这是一个实用方法示例:

import com.sun.javafx.PlatformUtil;
import com.sun.javafx.scene.control.behavior.ButtonBehavior;
import com.sun.javafx.scene.control.behavior.KeyBinding;
import java.lang.reflect.Field;
import java.util.List;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

public final class ButtonUtils {

  public static void installEnterFiresButtonFix() throws ReflectiveOperationException {
    if (PlatformUtil.isMac()) {
      return;
    }

    Field bindingsField = ButtonBehavior.class.getDeclaredField("BUTTON_BINDINGS");
    Field pressedActionField = ButtonBehavior.class.getDeclaredField("PRESS_ACTION");
    Field releasedActionField = ButtonBehavior.class.getDeclaredField("RELEASE_ACTION");

    bindingsField.setAccessible(true);
    pressedActionField.setAccessible(true);
    releasedActionField.setAccessible(true);

    @SuppressWarnings("unchecked")
    List<KeyBinding> bindings = (List<KeyBinding>) bindingsField.get(null);
    String pressedAction = (String) pressedActionField.get(null);
    String releasedAction = (String) releasedActionField.get(null);

    bindings.add(new KeyBinding(KeyCode.ENTER, KeyEvent.KEY_PRESSED, pressedAction));
    bindings.add(new KeyBinding(KeyCode.ENTER, KeyEvent.KEY_RELEASED, releasedAction));
  }

  private ButtonUtils() {}

}

您可以在应用程序启动的早期调用此实用程序方法,在创建任何Buttons 之前。这是一个使用它的示例:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

public class Main extends Application {

  @Override
  public void start(Stage primaryStage) {
    try {
      ButtonUtils.installEnterFiresButtonFix();
    } catch (ReflectiveOperationException ex) {
      ex.printStackTrace();
    }
    Button button = new Button("Save");
    button.setOnAction(event -> {
      event.consume();
      System.out.println(new FileChooser().showSaveDialog(primaryStage));
    });
    Scene scene = new Scene(new StackPane(button), 300, 150);
    primaryStage.setScene(scene);
    primaryStage.setTitle("Workshop");
    primaryStage.show();
  }

}

提醒:此修复依赖于实现。

【讨论】:

    【解决方案2】:

    我为正在打开的 fileChooser 添加了一个布尔值,它似乎对我有用,但我必须拆分事件,否则它只会每隔一个触发打印按钮

    public class Main extends Application {
    
        private boolean fileChooserOpen = false;
    
        @Override
        public void start(Stage stage) throws Exception{
            /* EventHandler to be used with multiple buttons */
            EventHandler<KeyEvent> enterWithFileChooser = event -> {
                if (!fileChooserOpen && event.getCode() == KeyCode.ENTER && event.getSource() instanceof Button) {
                    Button src = (Button) event.getSource();
                    src.fire();
                    fileChooserOpen = true;
                }else {
                    fileChooserOpen = false;
                }
                event.consume();
            };
    
            EventHandler<KeyEvent> enter = event -> {
                if (event.getCode() == KeyCode.ENTER && event.getSource() instanceof Button) {
                    Button src = (Button) event.getSource();
                    src.fire();
                }
                event.consume();
            };
    
            /* Create a new button */
            Button b1 = new Button("Save");
            Button b2 = new Button("Print");
            /* Add event handlers */
            b1.setOnKeyReleased(enterWithFileChooser);
            b2.setOnKeyReleased(enter);
    
            /* Called by .fire method of save button */
            b1.setOnAction(event -> {
                /* Create the save dialog box */
                FileChooser saveDialog = new FileChooser();
                saveDialog.setTitle("Save");
    
                /* Get file */
                File f = saveDialog.showSaveDialog(stage);
                /* ... do stuff with file ... */
            });
            /* Called by .fire method of print button */
            b2.setOnAction(event -> System.out.println("Pressed"));
    
            Scene scene = new Scene(new HBox(b1, b2));
            stage.setScene(scene);
            stage.show();
        }
    
        public static void main(String[] args) { launch(args); }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-27
      • 1970-01-01
      • 1970-01-01
      • 2014-05-25
      • 2019-02-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多