是否可以从JavaFX FileChooser窗口使用事件?

时间:2019-01-20 23:00:55

标签: events button javafx event-handling filechooser

我有一个JavaFX Button,当用户按下Enter时触发。这将导致FileChooser打开。有些人(例如我本人)可能会在FileChooser内部打入Enter来保存文件。但是,这导致保存按钮再次触发自身,并再次打开FileChooser来保存新文件。用鼠标单击按钮(在FileChooser中)不会出现此问题。

我认为使用按钮上的事件可以解决此问题,但是它仅使用GUI事件上的按钮,而不使用FileChooser按钮。我尝试寻找修改FileChooser的EventHandler来消耗Enter键的方法,但没有成功。
我还尝试过将焦点从按钮上移开,并将其移到父级(窗格)上,这样就无法再次单击它。但是,有些按钮可以多次被单击,而不必重新集中精力于它们。

我的代码示例如下(显然,这将是扩展Application的更大类的一部分):

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 -> {/* Do something */});

当用户在FileChooser中按下Enter键时,如何防止按钮触发?我不希望用户没有鼠标就陷入困境。我知道按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();
    }
}

2 个答案:

答案 0 :(得分:2)

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

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

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

您似乎想做的是允许用户使用 ENTER 键触发Button;在Windows之类的平台上,此应该是默认行为。

  

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

设置的默认按钮

对我来说,Button处于焦点状态时按 ENTER 会同时在Windows 10和Windows 10上使用JavaFX 11.0.2而不是JavaFX 8u202时触发操作事件。 {1}}自JavaFX 8以来发生了变化。以下是Button的不同实现,显示了已注册的键绑定。

JavaFX 8u202

com.sun.javafx.scene.control.behavior.ButtonBehavior

JavaFX 11.0.2

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

如您所见,这两个寄存器都注册了 SPACE 以在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 */ } 具有焦点时触发它们。但是,JavaFX 11.0.2实现也为此注册了 ENTER ,但仅适用于非Mac OS平台。我找不到有关此行为变化的任何文档。

如果您希望JavaFX 8中具有相同的行为,并且不介意侵入JavaFX的内部结构,则可以使用反射来更改所有按钮中的控件的行为应用。这是一个实用程序方法示例:

Button

在创建任何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() {} } 之前,应在应用程序启动初期调用此实用程序方法。这是一个使用它的示例:

Button

提醒:此修补程序取决于实现。

答案 1 :(得分:1)

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

'|'.join