Проблема заключается в запуске Button
из обработчика onKeyReleased
. К тому моменту, как вы отпустите клавишу ENTER , FileChooser
будет скрыт, а Stage
восстановит фокус, что означает, что событие отпускания ключа передается вашему Stage
/ Button
. Очевидно, это вызовет цикл.
Одно из возможных решений - запустить Button
из обработчика onKeyPressed
. Однако это приведет к несколько иному поведению по сравнению с другими приложениями, которое ваши пользователи могут не ожидать / ценить.
Другое возможное решение состоит в том, чтобы отследить, был ли FileChooser
открыт перед стрельбой Button
, как то, что Мэтт делает в его ответе .
Похоже, вы пытаетесь разрешить пользователям использовать клавишу ENTER для запуска Button
; это должно быть поведением по умолчанию на платформах, таких как Windows.
Не для меня. Пробел является единственным ключом, который запускает кнопку. Я думаю, что причина в том, что ввод используется для запуска кнопки по умолчанию, которая устанавливается с помощью btn.setDefaultButton(true);
Для меня нажатие ENTER , когда у Button
есть фокус, запускает событие action при использовании JavaFX 11.0.2, но не JavaFX 8u202, как в Windows 10. Похоже, поведение 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, то вы можете использовать отражение, чтобы изменить поведение всех кнопочных элементов управления в вашем приложении , Вот пример служебного метода:
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
. Вот пример использования его:
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();
}
}
Напоминание: это исправление зависит от реализации.