JavaFx:未触发 TextField KeyEvents
JavaFx: TextField KeyEvents are not triggered
我正在实施一个自定义组件,用户可以在其中输入 TextField
并弹出 TableView
,然后用户可以在 table 中查找项目。
我对 标准 KeyEvent
的 TextField
如 Ctrl+A 或首页。在带有 TableView
的窗格弹出后,那些按键事件不再起作用。
我检查了 TextField
是否失去了焦点,但它没有,如果我设置 EventFilter
来查看发生了什么,它表明这些事件已被触发,但我看不到对 UI。甚至弹出窗口的 setHideOnEscape
也不起作用。
这里有一个简单的代码来验证它:
fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="textField.Controller">
<TextField fx:id="textField" prefWidth="400"/>
</AnchorPane>
控制器:
public class Controller implements Initializable {
@FXML
private TextField textField;
@Override
public void initialize(URL location, ResourceBundle resources) {
Popup popUp = new Popup();
TableView<Object> table = new TableView<>();
table.prefWidthProperty().bind(textField.widthProperty());
popUp.getContent().add(table);
popUp.setHideOnEscape(true);
popUp.setAutoHide(true);
// To see if the KeyEvent is triggered
textField.addEventFilter(KeyEvent.ANY, System.out::println);
textField.setOnKeyTyped(event -> {
if(!popUp.isShowing()){
popUp.show(
textField.getScene().getWindow(),
textField.getScene().getWindow().getX()
+ textField.localToScene(0, 0).getX()
+ textField.getScene().getX(),
textField.getScene().getWindow().getY()
+ textField.localToScene(0, 0).getY()
+ textField.getScene().getY()
+ textField.getHeight() - 1);
}
});
}
}
主要的:
public class Main extends Application {
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("View.fxml"));
AnchorPane pane = loader.load();
primaryStage.setScene(new Scene(pane, 800, 600));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
这是控制台输出
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_PRESSED, consumed = false, character = , text = a, code = A]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_TYPED, consumed = false, character = a, text = , code = UNDEFINED]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_RELEASED, consumed = false, character = , text = a, code = A]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_PRESSED, consumed = false, character = , text = , code = CONTROL, controlDown, shortcutDown]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_TYPED, consumed = false, character = , text = , code = UNDEFINED, controlDown, shortcutDown]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_RELEASED, consumed = false, character = , text = a, code = A, controlDown, shortcutDown]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_RELEASED, consumed = false, character = , text = , code = CONTROL]
知道为什么这些事件被消耗了,即使它说它们没有被消耗吗?
textField 不响应 some/all "standard function keys" 的原因是它们的 KEY_PRESSED 类型从未到达它 - 它们被重定向到 table弹出窗口和 most/all 被 table.
使用
一个天真的第一个近似值是将 table 的 focusTraversable 属性 设置为 false:这有效地阻止了所有密钥被传递给它。现实世界的要求可能没有那么简单,因为其中一些应该达到 table 而另一些应该冒泡到 textField.
这可以通过自定义 EventDispatcher(在 table 上)来实现,它检查所有 keyEvent 并决定将哪个 deliver/not 发送给 table 的原始调度程序。一个代码片段,其中拦截器是用于决策的谓词(为了方便,最后有完整的工作示例):
private BasicEventDispatcher original;
private Predicate<Event> interceptor;
@Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (!interceptor.test(event)) {
event = original.dispatchCapturingEvent(event);
if (event.isConsumed()) {
return null;
}
}
event = tail.dispatchEvent(event);
if (event != null && !interceptor.test(event)) {
event = original.dispatchBubblingEvent(event);
if (event.isConsumed()) {
return null;
}
}
return event;
}
它的用法:if f.i。我们希望 LEFT 和 RIGT 冒泡到那个文本字段,而所有其他的应该由 table
正常处理
List<KeyCode> toIntercept = List.of(KeyCode.LEFT, KeyCode.RIGHT);
Predicate<Event> interceptor = e -> {
if (e instanceof KeyEvent) {
return toIntercept.contains(((KeyEvent) e).getCode());
}
return false;
};
table.setEventDispatcher(new InterceptingEventDispatcher(
(BasicEventDispatcher) table.getEventDispatcher(), interceptor));
可玩的完整示例:
public class ViewPopupApplication extends Application {
public static class InterceptingEventDispatcher implements EventDispatcher {
private BasicEventDispatcher original;
private Predicate<Event> interceptor;
public InterceptingEventDispatcher(BasicEventDispatcher original, Predicate<Event> interceptor) {
this.original = original;
this.interceptor = interceptor;
}
@Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (!interceptor.test(event)) {
event = original.dispatchCapturingEvent(event);
if (event.isConsumed()) {
return null;
}
}
event = tail.dispatchEvent(event);
if (event != null && !interceptor.test(event)) {
event = original.dispatchBubblingEvent(event);
if (event.isConsumed()) {
return null;
}
}
return event;
}
}
private Parent createContent() {
TableView<Locale> table = new TableView<>(FXCollections.observableArrayList(Locale.getAvailableLocales()));
// just to see that right/left are intercepted while up/down are handled
table.getSelectionModel().setCellSelectionEnabled(true);
TableColumn<Locale, String> country = new TableColumn<>("Country");
country.setCellValueFactory(new PropertyValueFactory<>("displayCountry"));
TableColumn<Locale, String> language = new TableColumn<>("Language");
language.setCellValueFactory(new PropertyValueFactory<>("displayLanguage"));
table.getColumns().addAll(country, language);
// disables default focus traversal
// table.setFocusTraversable(false);
// decide which keys to intercept
List<KeyCode> toIntercept = List.of(KeyCode.LEFT, KeyCode.RIGHT);
Predicate<Event> interceptor = e -> {
if (e instanceof KeyEvent) {
return toIntercept.contains(((KeyEvent) e).getCode());
}
return false;
};
table.setEventDispatcher(new InterceptingEventDispatcher(
(BasicEventDispatcher) table.getEventDispatcher(), interceptor));
TextField textField = new TextField("something to show");
textField.setPrefColumnCount(20);
textField.setText("something to see");
table.prefWidthProperty().bind(textField.widthProperty());
Popup popUp = new Popup();
popUp.getContent().add(table);
textField.setOnKeyTyped(event -> {
if(!popUp.isShowing()){
popUp.show(
textField.getScene().getWindow(),
textField.getScene().getWindow().getX()
+ textField.localToScene(0, 0).getX()
+ textField.getScene().getX(),
textField.getScene().getWindow().getY()
+ textField.localToScene(0, 0).getY()
+ textField.getScene().getY()
+ textField.getHeight() - 1);
}
});
BorderPane content = new BorderPane(textField);
return content;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
我正在实施一个自定义组件,用户可以在其中输入 TextField
并弹出 TableView
,然后用户可以在 table 中查找项目。
我对 标准 KeyEvent
的 TextField
如 Ctrl+A 或首页。在带有 TableView
的窗格弹出后,那些按键事件不再起作用。
我检查了 TextField
是否失去了焦点,但它没有,如果我设置 EventFilter
来查看发生了什么,它表明这些事件已被触发,但我看不到对 UI。甚至弹出窗口的 setHideOnEscape
也不起作用。
这里有一个简单的代码来验证它:
fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="textField.Controller">
<TextField fx:id="textField" prefWidth="400"/>
</AnchorPane>
控制器:
public class Controller implements Initializable {
@FXML
private TextField textField;
@Override
public void initialize(URL location, ResourceBundle resources) {
Popup popUp = new Popup();
TableView<Object> table = new TableView<>();
table.prefWidthProperty().bind(textField.widthProperty());
popUp.getContent().add(table);
popUp.setHideOnEscape(true);
popUp.setAutoHide(true);
// To see if the KeyEvent is triggered
textField.addEventFilter(KeyEvent.ANY, System.out::println);
textField.setOnKeyTyped(event -> {
if(!popUp.isShowing()){
popUp.show(
textField.getScene().getWindow(),
textField.getScene().getWindow().getX()
+ textField.localToScene(0, 0).getX()
+ textField.getScene().getX(),
textField.getScene().getWindow().getY()
+ textField.localToScene(0, 0).getY()
+ textField.getScene().getY()
+ textField.getHeight() - 1);
}
});
}
}
主要的:
public class Main extends Application {
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("View.fxml"));
AnchorPane pane = loader.load();
primaryStage.setScene(new Scene(pane, 800, 600));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
这是控制台输出
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_PRESSED, consumed = false, character = , text = a, code = A]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_TYPED, consumed = false, character = a, text = , code = UNDEFINED]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_RELEASED, consumed = false, character = , text = a, code = A]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_PRESSED, consumed = false, character = , text = , code = CONTROL, controlDown, shortcutDown]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_TYPED, consumed = false, character = , text = , code = UNDEFINED, controlDown, shortcutDown]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_RELEASED, consumed = false, character = , text = a, code = A, controlDown, shortcutDown]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_RELEASED, consumed = false, character = , text = , code = CONTROL]
知道为什么这些事件被消耗了,即使它说它们没有被消耗吗?
textField 不响应 some/all "standard function keys" 的原因是它们的 KEY_PRESSED 类型从未到达它 - 它们被重定向到 table弹出窗口和 most/all 被 table.
使用一个天真的第一个近似值是将 table 的 focusTraversable 属性 设置为 false:这有效地阻止了所有密钥被传递给它。现实世界的要求可能没有那么简单,因为其中一些应该达到 table 而另一些应该冒泡到 textField.
这可以通过自定义 EventDispatcher(在 table 上)来实现,它检查所有 keyEvent 并决定将哪个 deliver/not 发送给 table 的原始调度程序。一个代码片段,其中拦截器是用于决策的谓词(为了方便,最后有完整的工作示例):
private BasicEventDispatcher original;
private Predicate<Event> interceptor;
@Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (!interceptor.test(event)) {
event = original.dispatchCapturingEvent(event);
if (event.isConsumed()) {
return null;
}
}
event = tail.dispatchEvent(event);
if (event != null && !interceptor.test(event)) {
event = original.dispatchBubblingEvent(event);
if (event.isConsumed()) {
return null;
}
}
return event;
}
它的用法:if f.i。我们希望 LEFT 和 RIGT 冒泡到那个文本字段,而所有其他的应该由 table
正常处理List<KeyCode> toIntercept = List.of(KeyCode.LEFT, KeyCode.RIGHT);
Predicate<Event> interceptor = e -> {
if (e instanceof KeyEvent) {
return toIntercept.contains(((KeyEvent) e).getCode());
}
return false;
};
table.setEventDispatcher(new InterceptingEventDispatcher(
(BasicEventDispatcher) table.getEventDispatcher(), interceptor));
可玩的完整示例:
public class ViewPopupApplication extends Application {
public static class InterceptingEventDispatcher implements EventDispatcher {
private BasicEventDispatcher original;
private Predicate<Event> interceptor;
public InterceptingEventDispatcher(BasicEventDispatcher original, Predicate<Event> interceptor) {
this.original = original;
this.interceptor = interceptor;
}
@Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (!interceptor.test(event)) {
event = original.dispatchCapturingEvent(event);
if (event.isConsumed()) {
return null;
}
}
event = tail.dispatchEvent(event);
if (event != null && !interceptor.test(event)) {
event = original.dispatchBubblingEvent(event);
if (event.isConsumed()) {
return null;
}
}
return event;
}
}
private Parent createContent() {
TableView<Locale> table = new TableView<>(FXCollections.observableArrayList(Locale.getAvailableLocales()));
// just to see that right/left are intercepted while up/down are handled
table.getSelectionModel().setCellSelectionEnabled(true);
TableColumn<Locale, String> country = new TableColumn<>("Country");
country.setCellValueFactory(new PropertyValueFactory<>("displayCountry"));
TableColumn<Locale, String> language = new TableColumn<>("Language");
language.setCellValueFactory(new PropertyValueFactory<>("displayLanguage"));
table.getColumns().addAll(country, language);
// disables default focus traversal
// table.setFocusTraversable(false);
// decide which keys to intercept
List<KeyCode> toIntercept = List.of(KeyCode.LEFT, KeyCode.RIGHT);
Predicate<Event> interceptor = e -> {
if (e instanceof KeyEvent) {
return toIntercept.contains(((KeyEvent) e).getCode());
}
return false;
};
table.setEventDispatcher(new InterceptingEventDispatcher(
(BasicEventDispatcher) table.getEventDispatcher(), interceptor));
TextField textField = new TextField("something to show");
textField.setPrefColumnCount(20);
textField.setText("something to see");
table.prefWidthProperty().bind(textField.widthProperty());
Popup popUp = new Popup();
popUp.getContent().add(table);
textField.setOnKeyTyped(event -> {
if(!popUp.isShowing()){
popUp.show(
textField.getScene().getWindow(),
textField.getScene().getWindow().getX()
+ textField.localToScene(0, 0).getX()
+ textField.getScene().getX(),
textField.getScene().getWindow().getY()
+ textField.localToScene(0, 0).getY()
+ textField.getScene().getY()
+ textField.getHeight() - 1);
}
});
BorderPane content = new BorderPane(textField);
return content;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}