JavaFX 2 TextArea: как остановить использование [Enter] и [Tab]

Я хочу использовать JavaFX TextArea, как если бы он был в точности как многострочный TextField. Другими словами, когда я нажимаю [Tab], я хочу перейти к следующему элементу управления в форме, а когда я нажимаю [Enter], я хочу, чтобы Key.Event перешел к элементу управления defaultButton (а не использовался TextArea).

Поведение по умолчанию для TextArea состоит в том, что [Tab] вставляется в TextArea, а [Enter] вставляет символ новой строки.

Я знаю, что мне нужно использовать EventFilters, чтобы получить поведение, которое я хочу, но яЯ все понимаю неправильно. Я нене хочу, чтобы TextArea использовала эти события ... я просто хочу, чтобы они позволили имидти прямо по ".

Ответы на вопрос(1)

Решение Вопроса

Когда пользователь нажимает клавишу табуляции, фокус перемещается к следующему элементу управления вниз. Когда пользователь нажимает клавишу ввода, запускается кнопка по умолчанию.

Для достижения этого поведения:

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

Код использует функции, реализованные в Java 8, поэтомуJava 8 требуется выполнить его.

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.value.*;
import javafx.collections.ObservableList;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.control.*;
import static javafx.scene.input.KeyCode.ENTER;
import static javafx.scene.input.KeyCode.TAB;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.*;

public class TextAreaTabAndEnterHandler extends Application {
  final Label status = new Label();

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

  @Override public void start(final Stage stage) {
    final TextArea textArea1 = new TabAndEnterIgnoringTextArea();
    final TextArea textArea2 = new TabAndEnterIgnoringTextArea();

    final Button defaultButton = new Button("OK");
    defaultButton.setDefaultButton(true);
    defaultButton.setOnAction(new EventHandler<actionevent>() {
      @Override public void handle(ActionEvent event) {
        status.setText("Default Button Pressed");
      }
    });

    textArea1.textProperty().addListener(new ClearStatusListener());
    textArea2.textProperty().addListener(new ClearStatusListener());

    VBox layout = new VBox(10);
    layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10px;");
    layout.getChildren().setAll(
      textArea1, 
      textArea2, 
      defaultButton, 
      status
    );

    stage.setScene(
      new Scene(layout)
    );
    stage.show();
  }

  class ClearStatusListener implements ChangeListener<string> {
    @Override public void changed(ObservableValue<!--? extends String--> observable, String oldValue, String newValue) {
      status.setText("");
    }
  }

  class TabAndEnterIgnoringTextArea extends TextArea {
    final TextArea myTextArea = this;

    TabAndEnterIgnoringTextArea() {
      addEventFilter(KeyEvent.KEY_PRESSED, new TabAndEnterHandler());
    }

    class TabAndEnterHandler implements EventHandler<keyevent> {
      private KeyEvent recodedEvent;

      @Override public void handle(KeyEvent event) {
        if (recodedEvent != null) {
          recodedEvent = null;
          return;
        }

        Parent parent = myTextArea.getParent();
        if (parent != null) {
          switch (event.getCode()) {
            case ENTER:
              if (event.isControlDown()) {
                recodedEvent = recodeWithoutControlDown(event);
                myTextArea.fireEvent(recodedEvent);
              } else {
                Event parentEvent = event.copyFor(parent, parent);
                myTextArea.getParent().fireEvent(parentEvent);
              }  
              event.consume();
              break;

            case TAB:
              if (event.isControlDown()) {
                recodedEvent = recodeWithoutControlDown(event);
                myTextArea.fireEvent(recodedEvent);
              } else {
                ObservableList<node> children = parent.getChildrenUnmodifiable();
                int idx = children.indexOf(myTextArea);
                if (idx >= 0) {
                  for (int i = idx + 1; i < children.size(); i++) {
                    if (children.get(i).isFocusTraversable()) {
                      children.get(i).requestFocus();
                      break;
                    }
                  }
                  for (int i = 0; i < idx; i++) {
                    if (children.get(i).isFocusTraversable()) {
                      children.get(i).requestFocus();
                      break;
                    }
                  }
                }
              }  
              event.consume();
              break;
          }
        }  
      }

      private KeyEvent recodeWithoutControlDown(KeyEvent event) {
        return new KeyEvent(
          event.getEventType(), 
          event.getCharacter(), 
          event.getText(), 
          event.getCode(), 
          event.isShiftDown(), 
          false, 
          event.isAltDown(), 
          event.isMetaDown()
        );
      }
    }
  }
}
</node></keyevent></string></actionevent>

Альтернативным решением было бы реализовать собственный настроенный скин для TextArea, который включает новое поведение обработки ключей. Я считаю, что такой процесс будет более сложным, чем решение, представленное здесь.

Обновить

Одна вещь, которую я не сделалМне действительно нравилось, что мое первоначальное решение этой проблемы заключалось в том, что после использования клавиши Tab или Enter не было никакой возможности запустить их обработку по умолчанию. Поэтому я обновил решение таким образом, что, если пользователь нажимает клавишу управления при нажатии клавиши Tab или Enter, будет выполняться операция по умолчанию Tab или Enter. Эта обновленная логика позволяет пользователю вставлять новую строку или пространство табуляции в текстовую область, нажимая клавиши CTRL + Enter или CTRL + Tab.

 scottb02 июн. 2013 г., 07:24
Очень хорошо. По моему мнению, в TextArea API должно быть свойство обработки по умолчанию. Я полагаю, что способ TextAreas обрабатывать Enter и Tab как поведение по умолчанию нежелателен во многих ситуациях. Ваше решение, хотя и блестящее, в настоящее время широко не доступно и требует большого количества клиентского кода.
 jewelsea02 июн. 2013 г., 07:34
@scottb, вы можете записать запрос функции платформы JavaFX для этого поведения вОтслеживание проблем JavaFX.
 codepleb07 сент. 2016 г., 13:15
Было запрошено 2 случая, такой шаблон необходим для такой базовой функции. Я надеюсь, что они будут реализовывать решение по умолчанию для этого. Хорошая работа, хотя.
 scottb21 мая 2013 г., 03:35
Блестяще элегантное решение. Спасибо, что нашли время, чтобы опубликовать это.
 jewelsea07 сент. 2016 г., 23:51
@TrudleR Я думаю, что было какое-то обсуждение вокруг поведения клавиатуры, такое, "Обработка нажатий клавиш Enter на кнопках в JavaFX ", наСписок рассылки для разработчиков OpenJFX назад в июне, однако я неследуйте этому очень внимательно. Если у вас есть какие-либо вопросы, предложения или предложения, вы можете попробовать проверить связь со списком рассылки, хотя он, возможно, уже обсуждался и решался там.

Ваш ответ на вопрос