按 F4 时禁用 ComboBox 下拉菜单
Disable ComboBox dropdown on hitting F4
目前,JavaFX 提供了一种功能,可以在按 F4 时下拉组合框。我们想禁用该功能并处理 F4 的其他功能。一开始我认为这很简单。我的想法是,我将添加一个按键事件过滤器,并在按下 F4 时使用它。
但不幸的是那没有用!!经查,发现ComboBoxPopupControl中有一段代码处理按键事件,设置为KeyEvent.ANY过滤器。奇怪的是他们消费 showing/hiding 之后的事件。
部分代码如下:
private void handleKeyEvent(KeyEvent ke, boolean doConsume) {
// When the user hits the enter or F4 keys, we respond before
// ever giving the event to the TextField.
if (ke.getCode() == KeyCode.ENTER) {
setTextFromTextFieldIntoComboBoxValue();
if (doConsume && comboBoxBase.getOnAction() != null) {
ke.consume();
} else {
forwardToParent(ke);
}
} else if (ke.getCode() == KeyCode.F4) {
if (ke.getEventType() == KeyEvent.KEY_RELEASED) {
if (comboBoxBase.isShowing()) comboBoxBase.hide();
else comboBoxBase.show();
}
ke.consume(); // we always do a consume here (otherwise unit tests fail)
}
}
这让我完全无能为力,因为现在我无法仅通过消耗 filters/handlers 来控制事件链的这一部分。 None 以下过滤器帮助我停止显示下拉菜单。
comboBox.addEventFilter(KeyEvent.ANY, e -> {
if (e.getCode() == KeyCode.F4) {
e.consume(); // Didn't stopped showing the drop down
}
});
comboBox.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.F4) {
e.consume(); // Didn't stopped showing the drop down
}
});
comboBox.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() == KeyCode.F4) {
e.consume(); // Didn't stopped showing the drop down
}
});
我可以阻止它的唯一方法是在其父级上使用事件并且不允许委托给 ComboBox。但这绝对是一种开销,整个应用程序中已经有数十个组合框,而且还会有更多。
我的问题是:
为什么他们实现了一个紧密集成的功能,不允许用户禁用它?
有没有我可以在 ComboBox 级别实施的替代方案,以在按下 F4 时停止 showing/hiding 下拉菜单。
我尝试了以下方法使其工作。但我不确定我能在多大程度上依赖基于时间轴的解决方案:(
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ComboBoxF4_Demo extends Application {
Timeline f4PressedTimeline = new Timeline(new KeyFrame(Duration.millis(100), e1 -> {
}));
@Override
public void start(Stage stage) throws Exception {
HBox root = new HBox();
root.setSpacing(15);
root.setPadding(new Insets(25));
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 600, 600);
stage.setScene(scene);
final ComboBox<String> comboBox = new ComboBox<String>() {
@Override
public void show() {
if (f4PressedTimeline.getStatus() != Animation.Status.RUNNING) {
super.show();
}
}
};
comboBox.setItems(FXCollections.observableArrayList("One", "Two", "Three"));
comboBox.addEventFilter(KeyEvent.ANY, e -> {
if (e.getCode() == KeyCode.F4) {
if (e.getEventType() == KeyEvent.KEY_RELEASED) {
f4PressedTimeline.playFromStart();
}
}
});
// NONE OF THE BELOW FILTERS WORKED :(
/*comboBox.addEventFilter(KeyEvent.ANY, e -> {
if (e.getCode() == KeyCode.F4) {
e.consume(); // Didn't stopped showing the drop down
}
});
comboBox.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.F4) {
e.consume(); // Didn't stopped showing the drop down
}
});
comboBox.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() == KeyCode.F4) {
e.consume(); // Didn't stopped showing the drop down
}
});
*/
root.getChildren().addAll(comboBox);
stage.show();
}
}
正如@kleopatra 在问题评论中提到的,消耗一个事件不会停止其在同一阶段的相同 "level" 内的传播。换句话说,所有注册到 ComboBox
的事件过滤器(对于 EventType
及其超类型)仍然会收到通知,即使其中一个使用了该事件。然后还有更改控件的默认行为的问题,这可能是您的最终用户意想不到的,并且不被欣赏。
如果您仍想更改控件的行为,并且发现在祖先上使用事件不令人满意,您可以在自定义 EventDispatcher
而不是事件过滤器中拦截事件:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage primaryStage) {
var comboBox = new ComboBox<String>();
for (int i = 0; i < 20; i++) {
comboBox.getItems().add("Item #" + i);
}
comboBox.getSelectionModel().select(0);
var oldDispatcher = comboBox.getEventDispatcher();
comboBox.setEventDispatcher((event, tail) -> {
if (event.getEventType() == KeyEvent.KEY_RELEASED
&& ((KeyEvent) event).getCode() == KeyCode.F4) {
return null; // returning null indicates the event was consumed
}
return oldDispatcher.dispatchEvent(event, tail);
});
primaryStage.setScene(new Scene(new StackPane(comboBox), 500, 300));
primaryStage.show();
}
}
目前,JavaFX 提供了一种功能,可以在按 F4 时下拉组合框。我们想禁用该功能并处理 F4 的其他功能。一开始我认为这很简单。我的想法是,我将添加一个按键事件过滤器,并在按下 F4 时使用它。
但不幸的是那没有用!!经查,发现ComboBoxPopupControl中有一段代码处理按键事件,设置为KeyEvent.ANY过滤器。奇怪的是他们消费 showing/hiding 之后的事件。
部分代码如下:
private void handleKeyEvent(KeyEvent ke, boolean doConsume) {
// When the user hits the enter or F4 keys, we respond before
// ever giving the event to the TextField.
if (ke.getCode() == KeyCode.ENTER) {
setTextFromTextFieldIntoComboBoxValue();
if (doConsume && comboBoxBase.getOnAction() != null) {
ke.consume();
} else {
forwardToParent(ke);
}
} else if (ke.getCode() == KeyCode.F4) {
if (ke.getEventType() == KeyEvent.KEY_RELEASED) {
if (comboBoxBase.isShowing()) comboBoxBase.hide();
else comboBoxBase.show();
}
ke.consume(); // we always do a consume here (otherwise unit tests fail)
}
}
这让我完全无能为力,因为现在我无法仅通过消耗 filters/handlers 来控制事件链的这一部分。 None 以下过滤器帮助我停止显示下拉菜单。
comboBox.addEventFilter(KeyEvent.ANY, e -> {
if (e.getCode() == KeyCode.F4) {
e.consume(); // Didn't stopped showing the drop down
}
});
comboBox.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.F4) {
e.consume(); // Didn't stopped showing the drop down
}
});
comboBox.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() == KeyCode.F4) {
e.consume(); // Didn't stopped showing the drop down
}
});
我可以阻止它的唯一方法是在其父级上使用事件并且不允许委托给 ComboBox。但这绝对是一种开销,整个应用程序中已经有数十个组合框,而且还会有更多。
我的问题是: 为什么他们实现了一个紧密集成的功能,不允许用户禁用它?
有没有我可以在 ComboBox 级别实施的替代方案,以在按下 F4 时停止 showing/hiding 下拉菜单。
我尝试了以下方法使其工作。但我不确定我能在多大程度上依赖基于时间轴的解决方案:(
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ComboBoxF4_Demo extends Application {
Timeline f4PressedTimeline = new Timeline(new KeyFrame(Duration.millis(100), e1 -> {
}));
@Override
public void start(Stage stage) throws Exception {
HBox root = new HBox();
root.setSpacing(15);
root.setPadding(new Insets(25));
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 600, 600);
stage.setScene(scene);
final ComboBox<String> comboBox = new ComboBox<String>() {
@Override
public void show() {
if (f4PressedTimeline.getStatus() != Animation.Status.RUNNING) {
super.show();
}
}
};
comboBox.setItems(FXCollections.observableArrayList("One", "Two", "Three"));
comboBox.addEventFilter(KeyEvent.ANY, e -> {
if (e.getCode() == KeyCode.F4) {
if (e.getEventType() == KeyEvent.KEY_RELEASED) {
f4PressedTimeline.playFromStart();
}
}
});
// NONE OF THE BELOW FILTERS WORKED :(
/*comboBox.addEventFilter(KeyEvent.ANY, e -> {
if (e.getCode() == KeyCode.F4) {
e.consume(); // Didn't stopped showing the drop down
}
});
comboBox.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.F4) {
e.consume(); // Didn't stopped showing the drop down
}
});
comboBox.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() == KeyCode.F4) {
e.consume(); // Didn't stopped showing the drop down
}
});
*/
root.getChildren().addAll(comboBox);
stage.show();
}
}
正如@kleopatra 在问题评论中提到的,消耗一个事件不会停止其在同一阶段的相同 "level" 内的传播。换句话说,所有注册到 ComboBox
的事件过滤器(对于 EventType
及其超类型)仍然会收到通知,即使其中一个使用了该事件。然后还有更改控件的默认行为的问题,这可能是您的最终用户意想不到的,并且不被欣赏。
如果您仍想更改控件的行为,并且发现在祖先上使用事件不令人满意,您可以在自定义 EventDispatcher
而不是事件过滤器中拦截事件:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage primaryStage) {
var comboBox = new ComboBox<String>();
for (int i = 0; i < 20; i++) {
comboBox.getItems().add("Item #" + i);
}
comboBox.getSelectionModel().select(0);
var oldDispatcher = comboBox.getEventDispatcher();
comboBox.setEventDispatcher((event, tail) -> {
if (event.getEventType() == KeyEvent.KEY_RELEASED
&& ((KeyEvent) event).getCode() == KeyCode.F4) {
return null; // returning null indicates the event was consumed
}
return oldDispatcher.dispatchEvent(event, tail);
});
primaryStage.setScene(new Scene(new StackPane(comboBox), 500, 300));
primaryStage.show();
}
}