Можно ли использовать событие из окна JavaFX FileChooser? - PullRequest
0 голосов
/ 21 января 2019

У меня есть кнопка JavaFX, которая срабатывает, когда пользователь нажимает ввод.Это приводит к открытию FileChooser.Некоторые люди (например, я) могут нажать Enter внутри FileChooser, чтобы сохранить файл.Однако это приводит к тому, что кнопка сохранения снова срабатывает сама и снова открывает FileChooser, чтобы сохранить новый файл.Нажатие кнопки (в FileChooser) с помощью мыши не имеет этой проблемы.

Я думал, что использование события от кнопки могло бы решить эту проблему, но оно потребляет только кнопку в событии GUI, а нечем кнопка FileChooser.Я пытался найти способы изменить EventHandler в FileChooser, чтобы он использовал нажатие клавиши ввода, но безуспешно.
Я также пытался снять фокус с кнопки и переместить ее к родителю (панели), чтобы он могне нажимать снова.Тем не менее, есть кнопки, которые полезны при многократном нажатии без необходимости снова фокусироваться на них.

Пример моего кода выглядит следующим образом (очевидно, это будет часть большего класса, расширяющего 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 */});).

Как я могу предотвратить возможность запуска кнопки при нажатии пользователем клавиши enterв 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();
    }
}

Ответы [ 2 ]

0 голосов
/ 23 января 2019

Проблема заключается в запуске 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();
  }

}

Напоминание: это исправление зависит от реализации.

0 голосов
/ 21 января 2019

Я добавил логическое значение для файла 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); }
}
...