JavaFX Enter key issue on ComboBox - javafx

I am encountering an issue with the "ENTER" key event propagation in ComboBox.
I have a form(VBox) which submits or perform validation when it receives "ENTER" key released event. The user will enter the details by tabbing between fields and hitting UP/DOWN keys for selection in combo box (Just to minimize the mouse interaction). So far everything works fine.
When the user tabs to ComboBox and press "DOWN" key, the ComboBox popup is shown. And user can select the options using UP/DOWN keys. After choosing an option, user will press the "ENTER" key to close the popup. Here I want the "ENTER" event to only close the popup but not to submit the form. If the popup not opened, then "ENTER" key on ComboBox should submit the form.
I tried to fix this by consuming the events on ComboBox. But that didn't work. So two questions::
Can anyone let me know "how to prevent the 'ENTER' key event propagation to VBox, only when the ComboBox is showing"?
And also can anyone let me know how come the event is propagated to "VBox" handler, in spite of consuming event on ComboBox filter/handler?
Below is the example show casing the issue:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ComboBoxEnterKeyIssue extends Application {
private DateFormat df = new SimpleDateFormat("hh:mm:ss");
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField1 = new TextField();
textField1.setMaxWidth(150);
ComboBox<String> comboBox = new ComboBox<>();
comboBox.setPrefWidth(150);
comboBox.getItems().addAll("Item 1", "Item 2", "Item 3", "Item 4", "Item 5");
comboBox.addEventFilter(KeyEvent.KEY_RELEASED, ke -> {
if (ke.getCode() == KeyCode.DOWN && !comboBox.isShowing()) {
comboBox.show();
ke.consume();
} else if (ke.getCode() == KeyCode.ENTER) {
ke.consume(); // Has no effect !!
}
});
comboBox.addEventHandler(KeyEvent.KEY_RELEASED, ke -> {
if (ke.getCode() == KeyCode.ENTER) {
ke.consume(); // Has no effect !!
}
});
TextField textField2 = new TextField();
textField2.setMaxWidth(150);
ListView<String> output = new ListView<>();
VBox.setVgrow(output, Priority.ALWAYS);
VBox pane = new VBox(textField1, comboBox, textField2, output);
pane.setPadding(new Insets(10));
pane.setSpacing(10);
pane.setOnKeyReleased(ke -> {
if (ke.getCode() == KeyCode.ENTER) {
output.getItems().add(df.format(new Date()) + " :: Enter key released on pane...");
// Form submission/validation is performed here !!
}
});
Scene scene = new Scene(pane, 500, 400);
primaryStage.setScene(scene);
primaryStage.setTitle("ComboBox enter key issue");
primaryStage.show();
}
}

So I came with this nasty solution to differentiate the enter event.
The fix is as below:
Created a custom combo skin to indentify the listView and set a flag when an "enter" key is pressed on listview.
In the Custom combo event dispatcher,check the flag if the enter is pressed while the popup is opened and then consume the event in dispatcher.
The question is still open for any better ideas.
import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.application.Application;
import javafx.event.EventDispatcher;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.control.Skin;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ComboBoxEnterKeyIssue extends Application {
private DateFormat df = new SimpleDateFormat("hh:mm:ss");
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField1 = new TextField();
textField1.setMaxWidth(150);
MyComboBox<String> comboBox = new MyComboBox<>();
comboBox.setPrefWidth(150);
comboBox.getItems().addAll("Item 1", "Item 2", "Item 3", "Item 4", "Item 5");
comboBox.addEventFilter(KeyEvent.KEY_RELEASED, ke -> {
if (ke.getCode() == KeyCode.DOWN && !comboBox.isShowing()) {
comboBox.show();
ke.consume();
} else if (ke.getCode() == KeyCode.ENTER) {
ke.consume(); // Has no effect !!
}
});
comboBox.addEventHandler(KeyEvent.KEY_RELEASED, ke -> {
if (ke.getCode() == KeyCode.ENTER) {
ke.consume(); // Has no effect !!
}
});
TextField textField2 = new TextField();
textField2.setMaxWidth(150);
ListView<String> output = new ListView<>();
VBox.setVgrow(output, Priority.ALWAYS);
VBox pane = new VBox(textField1, comboBox, textField2, output);
pane.setPadding(new Insets(10));
pane.setSpacing(10);
pane.setOnKeyReleased(ke -> {
if (ke.getCode() == KeyCode.ENTER) {
output.getItems().add(df.format(new Date()) + " :: Enter key released on pane...");
// Form submission/validation is performed here !!
}
});
Scene scene = new Scene(pane, 500, 400);
primaryStage.setScene(scene);
primaryStage.setTitle("ComboBox enter key issue");
primaryStage.show();
}
class MyComboBox<T> extends ComboBox<T> {
boolean hiddenByEnter = false;
public MyComboBox() {
init();
}
public void setHiddenByEnter(boolean hiddenByEnter) {
this.hiddenByEnter = hiddenByEnter;
}
private void init() {
final EventDispatcher initial = getEventDispatcher();
setEventDispatcher((event, tail) -> {
// Consuming the event only if the popup is hidden by 'enter' key event.
if (event instanceof KeyEvent && ((KeyEvent) event).getCode() == KeyCode.ENTER && hiddenByEnter) {
hiddenByEnter = false;
return null;
}
return initial.dispatchEvent(event, tail);
});
}
#Override
protected Skin<?> createDefaultSkin() {
return new MyComboBoxSkin<>(this);
}
}
class MyComboBoxSkin<T> extends ComboBoxListViewSkin<T> {
private ListView<T> listView;
public MyComboBoxSkin(MyComboBox<T> comboBox) {
super(comboBox);
/* Identifying the "ENTER" event on listView in popup, to differentiate the events. */
getPopup().showingProperty().addListener((obs, old, showing) -> {
if (showing) {
comboBox.setHiddenByEnter(false);
if (listView == null) {
listView = (ListView) getPopup().getScene().getRoot().lookup(".list-view");
listView.addEventFilter(KeyEvent.KEY_PRESSED, ke -> {
if (ke.getCode() == KeyCode.ENTER) {
comboBox.setHiddenByEnter(true);
}
});
}
}
});
}
}
}

Related

How do I, at the scene level, find the id of label and button nodes clicked in my scene?

My setup
I have added an EventFilter to an Hbox node which also contains two VBoxes.
In one of the VBoxes I have a Button, a Label, and a TextArea class instance.
The EventHandler is:
public static EventHandler<MouseEvent> pageEventFilter = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
EventTarget target = e.getTarget();
String name = target.getClass().getName();
System.out.println("NAME: " + name);
}
};
In the event filter, I want to get the id string for buttons or labels that the user clicks on.
I cannot determine the button or label clicked
When I click on a part of the button or label, I get the following class names reported:
click on button text ->
com.sun.javafx.scene.control.LabeledText
click outside button text ->
javafx.scene.control.Button or javafx.scene.control.Label
Trying to cast the event target of LabeledText to a Button or a Label gives me a ClassCastException.
If I try to create a variable:
LabeledText lbt = target
I get a compilation error that the LabeledText class does not exist.
Similarly for TextArea
When I click on the TextArea, I get javafx.scene.control.skin.TextAreaSkin$ContentView as the name of the target class. I am unable to click anywhere to get TextArea reported as the event target class. When I click the text in the TextArea, I get javafx.scene.text.Text, I cannot get the id of the TextArea node from either of these.
My Question
How do I find the parent of the target node, for the types of nodes I am interested in (buttons, labels and perhaps some other types), for which I can then get the node ID string using:
String nodeId = targetParentNode.getId();
In the screenshot, the user has clicked on a descendent node of a TextArea which is of type ContentView.
On intercepting the click event in a filter, the application has walked up the scene graph branch via repeated getParent() calls until it has found a higher level node of one of the specified types. In this case it has found a TextArea. It has then queried that text area for its "id" value and reported it in the appropriate label.
IdReporterApp.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class IdReporterApp extends Application {
private static final String CSS =
"""
data:text/css,
.label {
-fx-padding: 3px;
-fx-background-color: lightblue;
}
VBox {
-fx-spacing: 10px;
-fx-padding: 10px;
-fx-background-color: lemonchiffon;
}
HBox {
-fx-padding: 10px;
-fx-background-color: palegreen;
}
""";
#Override
public void start(Stage stage) {
Scene scene = new Scene(new IdController().getUI());
scene.getStylesheets().add(CSS);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
IdController.java
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.util.List;
class IdController {
private final Label clickedTargetTypeLabel = new Label();
private final Label reportedTargetTypeLabel = new Label();
private final Label reportedTargetIdLabel = new Label();
private final EventTargetFinder eventTargetFinder = new EventTargetFinder(
List.of(
Label.class, Button.class, TextArea.class
)
);
public Parent getUI() {
final VBox layout = new VBox(
new VBox(
id("box1", new Label("Box 1")),
id("button1", new Button("Button"))
),
new VBox(
id("box2", new Label("Box 2")),
id("textArea1", new TextArea("Text Area"))
),
new HBox(
id("clickTargetLabel", new Label("Clicked target Type")),
id( "clickTargetValueLabel", clickedTargetTypeLabel)
),
new HBox(
id("reportedTargetLabel", new Label("Reported target Type")),
id( "reportedTargetTypeLabel", reportedTargetTypeLabel)
),
new HBox(
id("reportedTargetIdLabel", new Label("Reported target ID")),
id("reportedTargetIdValueLabel", reportedTargetIdLabel)
)
);
layout.addEventFilter(
MouseEvent.MOUSE_CLICKED,
this::updateTargetLabels
);
return layout;
}
private Node id(String id, Node node) {
node.setId(id);
return node;
}
private void updateTargetLabels(MouseEvent e) {
EventTargetFinder.TargetSearchResult searchResult =
eventTargetFinder.findTargetsForMouseEvent(e);
clickedTargetTypeLabel.setText(
searchResult
.clickedTarget()
.getClass()
.getSimpleName()
);
reportedTargetTypeLabel.setText(
searchResult.reportedTarget() == null
? "null"
: searchResult
.reportedTarget()
.getClass()
.getSimpleName()
);
reportedTargetIdLabel.setText(
searchResult.reportedTarget() == null
? "none"
: searchResult.reportedTarget().getId() == null
? "null"
: searchResult.reportedTarget().getId()
);
}
}
EventTargetFinder.java
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import java.util.Collections;
import java.util.List;
record EventTargetFinder(List<Class<? extends Node>> filterTargetTypes) {
EventTargetFinder(List<Class<? extends Node>> filterTargetTypes) {
this.filterTargetTypes = Collections.unmodifiableList(filterTargetTypes);
}
record TargetSearchResult(
Node clickedTarget,
Node reportedTarget
) {}
public TargetSearchResult findTargetsForMouseEvent(MouseEvent event) {
Node target = (Node) event.getTarget();
Node clickedTarget = target;
while (target != null && !isTargetFiltered(target)) {
target = target.getParent();
}
Node reportedTarget = target;
return new TargetSearchResult(clickedTarget, reportedTarget);
}
private boolean isTargetFiltered(Node target) {
return filterTargetTypes.stream()
.anyMatch(
t -> t.isInstance(target)
);
}
}

Disable ComboBox dropdown on hitting F4

Currently JavaFX provides a feature to dropdown the comboBox on hitting F4. We want to disable that feature and process other functions for F4. On first instance I thought this is pretty straight forward. My idea is that I will add a key event filter and consume it when F4 is pressed.
But unfortunately that didnt worked !! Upon investigation, I noticed that there is a part of code in ComboBoxPopupControl to handle the key event, which is set as KeyEvent.ANY filter. The strange part is that they consume the event after showing/hiding.
The part of code is as below:
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)
}
}
This makes me totally helpless, as now I can no more control this part of event chain by merely consuming the filters/handlers. None of the below filters helped me to stop showing the dropdown.
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
}
});
The only way I can stop it is to consume the event on its parent and not allowing to delegate to ComboBox. But this is definetly an overhead, with already tens of ComboBox(es) across the application and many more to come.
My question is:
Why did they implemented a feature which is tightly integrated not allowing the user to disable it?
Is there any alternate that I can implement on ComboBox level to stop showing/hiding the dropdown when F4 is pressed.
I tried the below approach to make it work. But I am not sure how much i can rely on Timeline based solution :(
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();
}
}
As mentioned by #kleopatra in the question comments, consuming an event does not stop its propagation within the same "level" of the same phase. In other words, all the event filters registered with the ComboBox (for the EventType and its supertypes) will still be notified even if one of them consumes the event. Then there's also the problem of changing the default behavior of controls which may be unexpected, and unappreciated, by your end users.
If you still want to change the control's behavior, and you don't find consuming the event on an ancestor satisfactory, you can intercept the event in a custom EventDispatcher instead of an event filter:
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();
}
}

How to Fix 'JavaFX Dialogbox taking user input' problem

I am creating a JavaFx dialog box and I have written to a large extent the code. My problem is how to display the error message if a user enters the invalid input. I know I have to use a while loop somewhere but not sure where because of the structure of JavaFx dialog box. Second problem is if the user enters the right input, say 1 for yes, I would want to call a function to carry out a task.
The code I have written brings up the pop up box and prints the consequence of the user input to the console.
public static void AnotherMatch() {
//creates a popUp window
Stage popUp = new Stage();
// makes sure no changes are made in the Main window while this window is open
popUp.initModality(Modality.APPLICATION_MODAL);
popUp.setTitle("New Game");
popUp.setMinWidth(400);
popUp.setHeight(200);
TextPanel textPanel2 = new TextPanel();
TextField nameInput = new TextField();
Button button = new Button("Enter");
//label explains how the game works
Label displayLabel = new Label();
displayLabel.setText("Do you want to play another match: Yes: 1 -- No: 2");
button.setOnAction(e -> isChoice(nameInput, nameInput.getText()));
//vbox stores label and is set in centre
VBox windowDisplay = new VBox();
windowDisplay.setStyle("-fx-background-color:Wheat"); //background colour is set
windowDisplay.getChildren().addAll(displayLabel,nameInput, button);
windowDisplay.setAlignment(Pos.CENTER);
Scene scene = new Scene(windowDisplay);
popUp.setScene(scene);
popUp.showAndWait(); }
Code for isChoice function
private static boolean isChoice(TextField nameInput, String message) {
// TODO Auto-generated method stub
try {
int choice = Integer.parseInt(nameInput.getText());
if(choice == 1) {
System.out.println("I want to play game again");
return true;
}
else if (choice == 2){
System.out.println("I want to stop playing");
return false;
}
else {
System.out.println("Invalid entry");
return false;
}
}
catch(NumberFormatException e){
System.out.println(message + " Invalid .Enter 1 for yes and 2 for no");
return false;
}
}
The user should be asked to enter yes or no. If the user invalid input, an error message should be displayed to the user and the answer asked again until they answer yes or no.
One way you can do is using Bindings to disable the Button unless the TextField contains Yes or No(ignore case).
Demo App using Bindings.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication357 extends Application
{
#Override
public void start(Stage primaryStage)
{
TextField textField = new TextField();
Button btn = new Button();
btn.disableProperty().bind(Bindings.notEqualIgnoreCase("yes", textField.textProperty()).and(Bindings.notEqualIgnoreCase("no", textField.textProperty())));
btn.setText("Say 'Hello World'");
btn.setOnAction((ActionEvent event) -> {
System.out.println("Hello World!");
});
StackPane root = new StackPane(new VBox(textField, btn));
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}

JavaFX ComboBox - Make Control + A select all in text box while dropdown is visible

I'm making a combobox that is filtered by typed text, and shows the drop down whenever text is typed.
I found this example which works very well. I have modified it slightly so the dropdown appears when text is entered.
However, when I type a few letters and then press ctrl + A to select all text in the TextField, it does not select all of the text if the dropdown is visible. Something else is consuming that hotkey.
Here is the MCVE code:
import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MCVE extends Application {
public void start(Stage stage) {
HBox root = new HBox();
ComboBox<String> cb = new ComboBox<String>();
cb.setEditable(true);
ObservableList<String> items = FXCollections.observableArrayList("One", "Two", "Three", "Four", "Five", "Six",
"Seven", "Eight", "Nine", "Ten");
FilteredList<String> filteredItems = new FilteredList<String>(items, p -> true);
cb.getEditor().textProperty().addListener((obs, oldValue, newValue) -> {
final TextField editor = cb.getEditor();
final String selected = cb.getSelectionModel().getSelectedItem();
Platform.runLater(() -> {
if ( !editor.getText().isEmpty() ) {
cb.show();
} else {
cb.hide();
}
if (selected == null || !selected.equals(editor.getText())) {
filteredItems.setPredicate(item -> {
if (item.toUpperCase().startsWith(newValue.toUpperCase())) {
return true;
} else {
return false;
}
});
}
});
});
cb.setItems(filteredItems);
root.getChildren().add(cb);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Here are a few solutions I've tried. None of them work. It seems that the issue is that JavaFX has reserved the hotkey Ctrl + A and will not let me grab onto it.
(this one works if the key is D, but not A
((ComboBoxListViewSkin)cb.getSkin()).getDisplayNode().addEventFilter( KeyEvent.KEY_PRESSED, keyEvent -> {
if ( keyEvent.isControlDown() && keyEvent.getCode() == KeyCode.A ) {
cb.getEditor().selectAll();
}
});
(this one also works if the key is D, but not A
cb.setOnKeyPressed( ( KeyEvent e ) -> {
if ( e.isControlDown() && e.getCode() == KeyCode.D ) {
cb.getEditor().selectAll();
}
});
The behaviour is the same for a plain combo (that is without any filtering) and looks like a bug: ctrl-A is eaten by the ListView in the dropDown. To work around, you can install the eventFilter on the list, f.i. in a onShown handler - at this time the skin is installed:
cb.setOnShown(e -> {
ComboBoxListViewSkin<?> skin = (ComboBoxListViewSkin<?>) cb.getSkin();
ListView<?> list = (ListView<?>) skin.getPopupContent();
list.addEventFilter( KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.isControlDown() && keyEvent.getCode() == KeyCode.A ) {
cb.getEditor().selectAll();
}
});
cb.setOnShown(null);
});
This is working in all versions (8 and 9+). For 9+ the bug is worse in that all navigation inside the editor is disabled (aka: list eating left/right as well).

Refresh label in JAVAFX

So i have this code in which i'm trying to do a scene for my game. I'm really a beginner in a Java and especially JAVAFX world and doing this as a school project (Once again..) and trying to figure out a way to refresh my label.
I've found one URL from stackoverflow, which was a similar issue but didn't work for my problem (or was i too stupid to make it work..) anyways, link is here
This is the part where the problem occurs - i have a text box, from which you have to enter player names. Every time a user inputs player name the label shows how many names have been entered, according to the nimedlist.size() which holds the names inside.
Label mängijate_arv = new Label("Mängijaid on sisestatud: "+nimedlist.size());
// if we press enter, program will read the name
nimiTekst.setOnKeyPressed(new EventHandler<KeyEvent>() {
public void handle(final KeyEvent keyEvent) {
if (keyEvent.getCode() == KeyCode.ENTER) {
if (nimiTekst.getText() != null) {
nimedlist.add(nimiTekst.getText());
nimiTekst.setText(null);
}
}
}
});
startBox.getChildren().addAll(sisestus_mängijad, nimiTekst, mängijate_arv,
startButton2);
This is the whole code:
package application;
import java.util.ArrayList;
import javafx.application.Application;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
public class Baila2 extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(final Stage peaLava) {
final Group root = new Group();
final BorderPane piir = new BorderPane();
piir.setPrefSize(960, 540);
final Text tekst = new Text();
tekst.setText("JOOMISMÄNG");
tekst.setFont(Font.font("Verdana", 40));
VBox nupudAlam = new VBox();
Button startButton = new Button("Start");
nupudAlam.setSpacing(20);
Button reeglidButton = new Button("Reeglid");
nupudAlam.setAlignment(Pos.CENTER);
startButton.setId("btn3");
startButton.setMaxWidth(160);
reeglidButton.setMaxWidth(160);
reeglidButton.setId("btn3");
nupudAlam.getChildren().addAll(startButton, reeglidButton);
piir.setTop(tekst);
piir.setAlignment(tekst, Pos.CENTER);
piir.setCenter(nupudAlam);
root.getChildren().add(piir);
// START NUPP TÖÖ
startButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(final ActionEvent event) {
final ArrayList nimedlist = new ArrayList();
piir.setVisible(false);
final BorderPane startPiir = new BorderPane();
final VBox startBox = new VBox();
Button startButton2 = new Button("ALUSTA!");
startButton2.setId("btn2");
startButton2.setMaxWidth(160);
startPiir.setPrefSize(960, 540);
final Text startTekst = new Text();
startTekst.setText("JOOMISMÄNG");
startTekst.setFont(Font.font("Verdana", 40));
startPiir.setTop(startTekst);
startPiir.setAlignment(startTekst, Pos.CENTER);
final TextField nimiTekst = new TextField();
nimiTekst.setText(null);
nimiTekst.setMaxWidth(250);
Label sisestus_mängijad = new Label(
"Sisesta 3-9 mängija nimed:");
sisestus_mängijad.setFont(Font.font("Verdana", 30));
sisestus_mängijad.setTextFill(Color.ORANGE);
Label mängijate_arv = new Label("Mängijaid on sisestatud: "+nimedlist.size());
// kui vajutatakse ENTER,siis loeme nime
nimiTekst.setOnKeyPressed(new EventHandler<KeyEvent>() {
public void handle(final KeyEvent keyEvent) {
if (keyEvent.getCode() == KeyCode.ENTER) {
if (nimiTekst.getText() != null) {
nimedlist.add(nimiTekst.getText());
nimiTekst.setText(null);
}
}
}
});
startBox.getChildren().addAll(sisestus_mängijad, nimiTekst, mängijate_arv,
startButton2);
startBox.setSpacing(20);
startBox.setAlignment(Pos.CENTER);
startPiir.setCenter(startBox);
root.getChildren().add(startPiir);
}
});
// aknasündmuse lisamine
peaLava.setOnHiding(new EventHandler<WindowEvent>() {
public void handle(WindowEvent event) {
// luuakse teine lava
final Stage kusimus = new Stage();
// küsimuse ja kahe nupu loomine
Label label = new Label("Kas tõesti tahad kinni panna?");
Button okButton = new Button("Jah");
Button cancelButton = new Button("Ei");
// sündmuse lisamine nupule Jah
okButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
kusimus.hide();
}
});
// sündmuse lisamine nupule Ei
cancelButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
peaLava.show();
kusimus.hide();
}
});
// nuppude grupeerimine
FlowPane pane = new FlowPane(10, 10);
pane.setAlignment(Pos.CENTER);
pane.getChildren().addAll(okButton, cancelButton);
// küsimuse ja nuppude gruppi paigutamine
VBox vBox = new VBox(10);
vBox.setAlignment(Pos.CENTER);
vBox.getChildren().addAll(label, pane);
// stseeni loomine ja näitamine
Scene stseen2 = new Scene(vBox);
kusimus.setScene(stseen2);
kusimus.show();
}
}); // siin lõpeb aknasündmuse kirjeldus
// stseeni loomine ja näitamine
Scene stseen1 = new Scene(root, 960, 540, Color.GREEN);
peaLava.setTitle("BAILA 2.0");
// peaLava.setResizable(false);
stseen1.getStylesheets().add(
getClass().getClassLoader().getResource("test.css")
.toExternalForm());
peaLava.setScene(stseen1);
peaLava.show();
}
}
Sorry about Estonian language, it's compulsory in our school to write in our native language..
You can just do
nimiTekst.setOnKeyPressed(new EventHandler<KeyEvent>() {
public void handle(final KeyEvent keyEvent) {
if (keyEvent.getCode() == KeyCode.ENTER) {
if (nimiTekst.getText() != null) {
nimedlist.add(nimiTekst.getText());
nimiTekst.setText(null);
mängijate_arv.setText("Mängijaid on sisestatud: "+nimedlist.size());
}
}
}
});
If you are not using Java 8 (you appear not to be, since you are implementing all the handlers the old, long way...), you will have to declare mängijate_arv as final:
final Label mängijate_arv = new Label("Mängijaid on sisestatud: "+nimedlist.size());
If you want to be extra cool with this, you can use bindings instead. You will have to make nimidlist an observable list:
final ObservableList<String> nimedlist = FXCollections.observableArrayList();
and then:
mängijate_arv.bind(Bindings.format("Mängijaid on sisestatud: %d", Bindings.size(nimedList)));
and don't put the mängijate_arv.setText(...) call in the handler. This solution is nicer in many ways, as if you remove items from the list (or add other items elsewhere in your code), then the label will still remain properly updated without any additional code.
One other thing: it's a bit better to use an action handler on the text field, instead of a low-level key event handler:
nimiTekst.setOnAction(new EventHandler<ActionEvent>() {
public void handle(final ActionEvent keyEvent) {
if (nimiTekst.getText() != null) {
nimedlist.add(nimiTekst.getText());
nimiTekst.setText(null);
mängijate_arv.setText("Mängijaid on sisestatud: "+nimedlist.size());
}
}
});
(Sorry if I mangled your variable names. My Estonian is a bit weak ;). Your school's policy is a good one, for what it's worth.)

Resources