How to remove selection on input in editable ComboBox - javafx

Yes, there are earlier threads and guides on the issue. And they tell me that either setValue(null) or getSelectionModel().clearSelection() should be the answer. But doing any of these gives me a java.lang.IndexOutOfBoundsException.
What I want to do is to clear the selection everytime something is being written into the combo box. This is because it causes problems and looks weird when you write something in the combo box and something else remains selected in the combo box popup.
Here's an SSCCE:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;
public class SSCCE extends Application {
#Override
public void start(Stage stage) {
HBox root = new HBox();
ComboBox<Integer> cb = new ComboBox<Integer>();
cb.setEditable(true);
cb.getItems().addAll(1, 2, 6, 7, 9);
cb.setConverter(new IntegerStringConverter());
cb.getEditor().textProperty()
.addListener((obs, oldValue, newValue) -> {
// Using any of these will give me a IndexOutOfBoundsException
// Using any of these will give me a IndexOutOfBoundsException
//cb.setValue(null);
//cb.getSelectionModel().clearSelection();
});
root.getChildren().addAll(cb);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}

You are running into this JavaFX ComboBox change value causes IndexOutOfBoundsException issue, which is causing the IndexOutOfBoundsException. These are kind of a pain.
There is a bit of a logical issue with your attempts anyway: clearing the selected value will cause the editor to update its text, so even if this worked it would make it impossible for the user to type. So you want to check that the changed value isn't the one typed in. This seems to fix both issues:
cb.getEditor().textProperty()
.addListener((obs, oldValue, newValue) -> {
if (cb.getValue() != null && ! cb.getValue().toString().equals(newValue)) {
cb.getSelectionModel().clearSelection();
}
});
You may need to change the toString() call, depending on the exact converter you are using. In this case, it will work.

Related

JavaFX: ComboBox with custom cell factory - buggy rendering

I have a ComboBox with a custom cell factory, but the rendering is buggy and I don't know what I'm doing wrong.
The ComboBox's data is based on an enum, let's call it MyEnum. So my ComboBox is defined as:
#FXML
private ComboBox<MyEnum> comboBox;
I am setting it up like this:
comboBox.getItems().addAll(MyEnum.values());
comboBox.setButtonCell(new CustomRenderer());
comboBox.setCellFactory(cell -> new CustomRenderer());
comboBox.getSelectionModel().select(0);
My CustomRenderer looks like this:
public class CustomRenderer extends ListCell<MyEnum> {
#Override
protected void updateItem(MyEnum enumValue, boolean empty) {
super.updateItem(enumValue, empty);
if (enumValue== null || empty) {
setGraphic(null);
setText(null);
} else {
setGraphic(enumValue.getGraphic());
}
}
}
The getGraphic() method of MyEnum returns a HBox which can contain any number of elements, but in my specific case it's just an ImageView:
public enum MyEnum{
ONE(new ImageView(new Image(MyApp.class.getResourceAsStream("image1.png"),
15,
15,
true,
true))),
TWO(new ImageView(new Image(MyApp.class.getResourceAsStream("image2.png"),
15,
15,
true,
true)));
private final HBox graphic;
MyEnum(Node... graphicContents) {
graphic = new HBox(graphicContents);
graphic.setSpacing(5);
}
public HBox getGraphic() {
return graphic;
}
}
Now, when I start the app, the first bug is that the ComboBox doesn't show anything selected, even though I have the comboBox.getSelectionModel().select(0) in the initialization:
When I click on it, the dropdown is correct and shows my two entries with their images:
When I select one of the entries, everything still seems fine:
But when I open the dropdown again, then it looks like this:
So suddenly the selected image is gone from the dropdown.
After I select the other entry where the icon is still displayed, and reopen the dropdown, then both images are gone. The images are still shown in the ButtonCell though, just not in the dropdown.
I first thought maybe it has something to do specifically with ImageViews, but when I replaced them with other nodes, like Labels, it was still the same 2 bugs:
Nothing shown as selected on app start
Everything that I click in the dropdown box is then gone from the dropdown
If a runnable sample is needed, let me know. But maybe someone can already spot my mistake from the given code.
Thx
Your issue is that a node cannot appear in the scene graph more than once.
From the node documentation:
A node may occur at most once anywhere in the scene graph. Specifically, a node must appear no more than once in all of the following: as the root node of a Scene, the children ObservableList of a Parent, or as the clip of a Node.
If a program adds a child node to a Parent (including Group, Region, etc) and that node is already a child of a different Parent or the root of a Scene, the node is automatically (and silently) removed from its former parent.
You are trying to reuse the same node in both the button for the list selection and in the selection list itself, which is not allowed.
Additionally, as noted in comments by kleopatra: "it's wrong to use nodes as data". One reason for that (among others), is that if you want to have more than one view of the same data visible at the same time, you won't be able to because the node related to the data can only be attached to the scene at a single place at any given time.
I'd especially recommend not placing nodes in enums. In my opinion, that it is really not what enums are for.
What you need to do is separate the model from the view. The cell factory is creating a view of the changing value of the enum which is the model. This view only needs to be created once for the cell and it needs to be updated (by providing the view with the appropriate image) whenever the cell value changes.
Example Code
You can see in the example that, because we have created two separate views of the same Monster (the dragon), the example is able to render both views at the same time. This is because the views are completely different nodes, that provide a view to the same underlying data (an enum value and its associated image).
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class ChooseYourDoomApp extends Application {
public static final String CSS = "data:text/css," + // language=CSS
"""
.root {
-fx-background-color: lightblue;
-fx-base: palegreen;
}
""";
#Override
public void start(Stage stage) throws Exception {
ComboBox<Monster> choiceOfDoom = new ComboBox<>(
FXCollections.observableArrayList(
Monster.values()
)
);
choiceOfDoom.setButtonCell(new MonsterCell());
choiceOfDoom.setCellFactory(listView -> new MonsterCell());
choiceOfDoom.getSelectionModel().select(Monster.Dragon);
StackPane layout = new StackPane(choiceOfDoom);
layout.setPadding(new Insets(20));
Scene scene = new Scene(layout);
scene.getStylesheets().add(CSS);
stage.setScene(scene);
stage.show();
}
public class MonsterCell extends ListCell<Monster> {
private ImageView imageView = new ImageView();
#Override
protected void updateItem(Monster item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
imageView.setImage(null);
setGraphic(null);
} else {
imageView.setImage(monsterImages.get(item));
setGraphic(imageView);
}
}
}
public enum Monster {
Medusa,
Dragon,
Treant,
Unicorn
}
private Map<Monster, Image> monsterImages = createMonsterImages();
private Map<Monster, Image> createMonsterImages() {
Map<Monster, Image> monsterImages = new HashMap<>();
for (Monster monster : Monster.values()) {
monsterImages.put(
monster,
new Image(
Objects.requireNonNull(
ChooseYourDoomApp.class.getResource(
monster + "-icon.png"
)
).toExternalForm()
)
);
}
return monsterImages;
}
public static void main(String[] args) {
launch(args);
}
}
Icons are placed in the resource directory under the same hierarchy as the package containing the main application code.
https://iconarchive.com/show/role-playing-icons-by-chanut.html
Dragon-icon.png
Medusa-icon.png
Treant-icon.png
Unicorn-icon.png

javafx label message showing with timer does not work

So im trying to show a message in javafx on a label and then make it disapear after 1 second. Im able to show the message as desired but i cannot make it dissapear. Actually my problem is that i never appear. So if i use only this:
lbReserva.setText("RESERVA REALITZADA");
Works as expected but obviously it just stay like that. So then i tried this:
try {
lbReserva.setText("RESERVA REALITZADA");
TimeUnit.SECONDS.sleep(1);
lbReserva.setText("");
} catch (InterruptedException e) {
System.err.format("IOException: %s%n", e);
}
But then the label it just never appears. I've tried placing the first set text outside right before the try block. I've tried placing the second set text just after the catch. In any case i got the same result, the label never appears, or probably appears and straight away dissapear. Any clues what im doing wrong? thank you so much in advance.
pd: i have tried using Thread.sleep instead of TimeUnit but i got the same result.
Use PauseTransition.
import javafx.animation.PauseTransition;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TestingGround extends Application
{
#Override
public void start(Stage stage) throws Exception
{
Label label = new Label("Hello World!");
PauseTransition wait = new PauseTransition(Duration.seconds(1));
wait.setOnFinished((e) -> {
label.setVisible(false);
});
wait.play();
VBox root = new VBox(label);
stage.setScene(new Scene(root, 700, 500));
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}

JavaFX TreeView buggy behaviour

I created a TreeView with checkbox tree items. I wanted to add listener to each checkbox, so I created a sample problem. But I got some pretty weird behavior. When I click on a checkbox, it actually triggered the handler for both itself and the one above it.
Here's the code I wrote:
package sample;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.layout.AnchorPane;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable{
#FXML
public AnchorPane container;
public static class CustomTreeCell extends CheckBoxTreeCell<String> {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
setText(item);
((CheckBox) getGraphic()).selectedProperty().addListener(((observable, oldValue, newValue) -> {
System.out.println("Clicked on: " + item);
}));
}
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {
TreeView<String> treeView = new TreeView<>();
treeView.setCellFactory((TreeView<String> param) -> new CustomTreeCell());
container.getChildren().add(treeView);
CheckBoxTreeItem root = new CheckBoxTreeItem<>("Root");
treeView.setRoot(root);
CheckBoxTreeItem leaf_A = new CheckBoxTreeItem<>("Leaf A");
CheckBoxTreeItem leaf_B = new CheckBoxTreeItem<>("Leaf B");
CheckBoxTreeItem leaf_C = new CheckBoxTreeItem<>("Leaf C");
root.getChildren().addAll(leaf_A, leaf_B, leaf_C);
}
}
After running the application, when I click on Leaf B, it prints out this:
Clicked on: Leaf A
Clicked on: Leaf B
I have been trying to figure out why this is happening for days but still couldn't find out why.
Any hints are appreciated!
Thanks!
updateItem is called any time the cell is reused to show a different item. This can happen quite often, and the API (deliberately) does not specify exactly when (i.e. it's an implementation detail). Typically, though, it happens frequently if, for example, the user scrolls in the table.
Your updateItem() method adds new listeners to the check box every time, and never removes those listeners. So what you might observe is more and more listeners getting fired the more you have scrolled in the table. (Try adding enough items to your table to allow scrolling, scroll around a lot, and then check/uncheck a check box.)
The intended use is to set the selectedStateCallBack to map to a property in your model class. Then if you want to react to changes, you can do so by observing the property in the model. This is explained in CheckBoxTableCell changelistener not working.

remove default focus from TextField JavaFX

I have designed some TextField in a form by SceneBuilder, when I run the code, one of the TextFields has been clicked by default, I want when I run the code, none of the TextFields get selected by default and user select a TextFiled.
UPDATE: As you see in this image I want to make the first field like other two field when code runs(no curser in field)
How can I do this?
In my case the accepted answer is not working. But this worked:
i requested focus for parent wrapped in runLater.
#FXML
public void initialize() {
//unfocus pathField
Platform.runLater( () -> root.requestFocus() );
}
Direct call of requestFocuss does not work.
Had the same problem with a non - editable TextField, which got selected and highlighted every time.
Solved it by setting myTextField.setFocusTraversable(false);
Note that in this case the focus goes to the next UI element an you must set every single element you don't want focused. You lose also the ability to select the element via the Tab key.
The ApiDoc states that setFocusTraversable() is false by default, but that seems not to work, unless explicitly called.
As there is no public method to achieve this, there is no direct way. Though, you can use a trick to do it. You can have a BooleanProperty just to check when the control is focused for the first time. Listen to focusProperty() of the control and when it is focused for the first time, delegate the focus to its container. For the rest of the focus, it will work as it should.
Example:
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
final BooleanProperty firstTime = new SimpleBooleanProperty(true); // Variable to store the focus on stage load
VBox vBox = new VBox(10);
vBox.setPadding(new Insets(20));
TextField t1 = new TextField();
TextField t2 = new TextField();
TextField t3 = new TextField();
t1.setPromptText("FirstName");
t2.setPromptText("LastName");
t3.setPromptText("Email");
vBox.getChildren().addAll(new HBox(t1, t2), t3);
primaryStage.setScene(new Scene(vBox, 300, 300));
primaryStage.show();
t1.focusedProperty().addListener((observable, oldValue, newValue) -> {
if(newValue && firstTime.get()){
vBox.requestFocus(); // Delegate the focus to container
firstTime.setValue(false); // Variable value changed for future references
}
});
}
public static void main(String[] args) {
launch(args);
}
}
On initial screen load :
You can simply set the focus traversable to false in the initialize method. Here is an example:
#Override
public void initialize(URL location, ResourceBundle resources) {
yourTextField.setFocusTraversable(false);
}
You can simply use Bindings
TextField textField = new TextField();
textField.styleProperty().bind(
Bindings
.when(textField.focusedProperty())
.then("-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);")
.otherwise("-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);"));
This will keep your prompt text visible even when the TextField is focused, as long it's empty.

How to add a shortcut event in javafx with combination of Ctrl + P +X

table.setOnKeyPressed(new EventHandler<KeyEvent>() {
// final KeyCombination kb = new KeyCodeCombination(KeyCode.P, KeyCombination.CONTROL_DOWN);
// final KeyCombination k = new KeyCodeCombina
public void handle(KeyEvent key) {
if (key.getCode() == KeyCode.P && key.isControlDown()) {
//My Code
}
}
});
I want to invoke the event with the shortcut keycombination of Ctrl+P+X
It is actually a little hard to understand what Ctrl+P+X means. I am going to assume it means that you press ctrl, then you press p, then you press x (potentially releasing the p before you press the x). I'll also assume that the order matters, e.g. press ctrl, then press x then press p would not count. Anyway a bit of speculation on my part, perhaps not exactly what you want, but hopefully you will get the gist of the provided solution and be able to adapt it to your situation.
The solution monitors both key presses and releases so that it can keep track of the state of key presses to determine if the key combination triggers.
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.time.LocalTime;
public class KeyCombo extends Application {
KeyCombination ctrlP = KeyCodeCombination.keyCombination("Ctrl+P");
KeyCombination ctrlX = KeyCodeCombination.keyCombination("Ctrl+X");
#Override
public void start(Stage stage) throws Exception {
Label lastPressedLabel = new Label();
TextField textField = new TextField();
BooleanProperty pDown = new SimpleBooleanProperty(false);
textField.setOnKeyPressed(event -> {
if (ctrlP.match(event)) {
pDown.set(true);
}
if (pDown.get() && ctrlX.match(event)) {
pDown.set(false);
lastPressedLabel.setText(
LocalTime.now().toString()
);
}
});
textField.setOnKeyReleased(event -> {
if (!event.isControlDown()) {
pDown.set(false);
}
});
VBox layout = new VBox(10,
new Label("Press Ctrl+P+X"),
textField,
lastPressedLabel
);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you can, I'd advise trying to use a simpler control scheme, e.g. just Ctrl+P or Ctrl+X (which is directly supported by the key code combination event matching), rather than using a composite control scheme of Ctrl+P+X.

Resources