ComboBox not close the popup after select value from list [duplicate] - javafx

I am encountering some strange issues after upgrading to latest JavaFX version (19).
If I set the last option as the value in ComboBox, then on first opening, the dropdown will not hide if I select any option. After that, the dropdown works as usual. The strange part is this only happens if I set the last option as value. When I set any other option apart from last option, it is working well.
In the above example, you can notice that:
If "Four" is set: the dropdown does not hide even when I select different options (only for first time).
If "Three" is set: the dropdown works as usual.
When I tried to investigate the root cause, I am almost concluded that it is because of the new feature "focusWithIn". Because that is where it is causing the issue to happen.
But on further investigation, I also noticed that the focus got messed up. In the above gif, you can also notice that the "focused" style is not applied on ComboBox if I set last option as value (again only for first time till I moved focus to another node and when I am back, it works as usual). Whereas the focused style works well if I set a different value.
When I tried to put some logs on focused property, below is the output when I focus on ComboBox (with last option as value):
ComboBox focused : true
ComboBox focused : false
The focus is immediately turned off!!
The only thing that confuses me is "Why only with last option??". I know issues/bugs will occur in general. But this particual relation with last option, I couldn't get it :)
Anyway, I tried different ways to make the dropdown hiding and focus to work, but none worked. Do any of you have any suggestions(workaround) to let this fix.
Below is the working demo:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ComboBoxDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().addAll("One", "Two", "Three", "Four");
comboBox.getSelectionModel().select("Four"); // Change value to any other options, it works
comboBox.focusedProperty().addListener((obs, old, focused) -> {
System.out.println("ComboBox focused : " + focused+", showing: "+comboBox.isShowing());
// ATTEMPT #2 : Requesting the focus by conditional checking (DIDN'T WORKED)
if(comboBox.isShowing() && !focused){
comboBox.requestFocus();
}
});
// ATTEMPT #1 : Requesting the focus after the dropdown is shown (DIDN'T WORKED)
comboBox.setOnShown(e -> {
System.out.println("ComboBox shown...");
comboBox.requestFocus();
});
VBox root = new VBox(new CheckBox("Just for focus grabbing"), comboBox);
root.setAlignment(Pos.CENTER);
root.setSpacing(20);
Scene scene = new Scene(root, 300, 200);
primaryStage.setTitle("ComboBox FX " + System.getProperty("javafx.runtime.version"));
primaryStage.setScene(scene);
primaryStage.show();
}
}

Definitely a bug in JavaFX 19.
I tested a bunch of things, and it seems that in order to set an initial selection for a ComboBox at the time of generation, it requires two ticks:
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().addAll("One", "Two", "Three", "Four");
initializeComboBox(comboBox, 3);
private void initializeComboBox(ComboBox comboBox, int intialIndex) {
Platform.runLater(()->{
Platform.runLater(()->{
comboBox.getSelectionModel().select(intialIndex);
});
});
}
I cannot answer why it is required for the last item only. That will be a job for the developers. However, this will work as a workaround in the meantime for this particular use case in this version.

Related

Issue with ComboBox focus in FX 19

I am encountering some strange issues after upgrading to latest JavaFX version (19).
If I set the last option as the value in ComboBox, then on first opening, the dropdown will not hide if I select any option. After that, the dropdown works as usual. The strange part is this only happens if I set the last option as value. When I set any other option apart from last option, it is working well.
In the above example, you can notice that:
If "Four" is set: the dropdown does not hide even when I select different options (only for first time).
If "Three" is set: the dropdown works as usual.
When I tried to investigate the root cause, I am almost concluded that it is because of the new feature "focusWithIn". Because that is where it is causing the issue to happen.
But on further investigation, I also noticed that the focus got messed up. In the above gif, you can also notice that the "focused" style is not applied on ComboBox if I set last option as value (again only for first time till I moved focus to another node and when I am back, it works as usual). Whereas the focused style works well if I set a different value.
When I tried to put some logs on focused property, below is the output when I focus on ComboBox (with last option as value):
ComboBox focused : true
ComboBox focused : false
The focus is immediately turned off!!
The only thing that confuses me is "Why only with last option??". I know issues/bugs will occur in general. But this particual relation with last option, I couldn't get it :)
Anyway, I tried different ways to make the dropdown hiding and focus to work, but none worked. Do any of you have any suggestions(workaround) to let this fix.
Below is the working demo:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ComboBoxDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().addAll("One", "Two", "Three", "Four");
comboBox.getSelectionModel().select("Four"); // Change value to any other options, it works
comboBox.focusedProperty().addListener((obs, old, focused) -> {
System.out.println("ComboBox focused : " + focused+", showing: "+comboBox.isShowing());
// ATTEMPT #2 : Requesting the focus by conditional checking (DIDN'T WORKED)
if(comboBox.isShowing() && !focused){
comboBox.requestFocus();
}
});
// ATTEMPT #1 : Requesting the focus after the dropdown is shown (DIDN'T WORKED)
comboBox.setOnShown(e -> {
System.out.println("ComboBox shown...");
comboBox.requestFocus();
});
VBox root = new VBox(new CheckBox("Just for focus grabbing"), comboBox);
root.setAlignment(Pos.CENTER);
root.setSpacing(20);
Scene scene = new Scene(root, 300, 200);
primaryStage.setTitle("ComboBox FX " + System.getProperty("javafx.runtime.version"));
primaryStage.setScene(scene);
primaryStage.show();
}
}
Definitely a bug in JavaFX 19.
I tested a bunch of things, and it seems that in order to set an initial selection for a ComboBox at the time of generation, it requires two ticks:
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().addAll("One", "Two", "Three", "Four");
initializeComboBox(comboBox, 3);
private void initializeComboBox(ComboBox comboBox, int intialIndex) {
Platform.runLater(()->{
Platform.runLater(()->{
comboBox.getSelectionModel().select(intialIndex);
});
});
}
I cannot answer why it is required for the last item only. That will be a job for the developers. However, this will work as a workaround in the meantime for this particular use case in this version.

JavaFX - How to override Textfield focus event behavior? [duplicate]

The default behaviour is that the prompt text in the field is erased as the field is being focused. That is when the marker is in the field.
Is it possible to configure the textfield so the prompt text is only erased when the user have started typing?
Otherwise I need to add a label beside/over each textfield for description of the value in it.
I know it's a bit old, but I needed it myself and this is still very relevant.
I will complete jewelsea's answer and give a simpler solution.
Background
Apparently this was the default behavior of Java(FX) (prompt text in a TextField was cleared only when the user starts typing).
But then, following a request (or a bug report) in the JIRA system,
Java changed this behavior (and made the default to clear the text when the TextField gets focus).
You can review this bug report here.
Solution
To get back to the old default (and nicer behavior IMO), all you need to do is to add the following line(s) of code.
In case your application interface is written using proper Java code.
Java Code:
textField.setStyle("-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);");
Where textField is your TextField component.
And in case your application interface is written using FXML and CSS, add the following to your CSS file.
JavaFX FXML (CSS):
.text-input, .text-input:focused {
-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);
}
--------------------------------------------------------------------------------
Cleared-on-Focus Behavior
Currently it's the default behavior (the text-field's prompt text is cleared when the text-field gets focus),
so you don't need to do anything to get this behavior,
but in case Java will decide to go back to the cleared-on-typing behavior,
and you want to get the cleared-on-focus behavior, this is how to:
In case of proper Java code - it's a bit tricky since you can't define behavior of pseudo-classes directly.
Java Code (using bindings):
textField.styleProperty().bind(
Bindings
.when(textField.focusedProperty())
.then("-fx-prompt-text-fill: transparent;")
.otherwise("-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);"));
or -
Java Code (using events):
textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
textField.setStyle("-fx-prompt-text-fill: transparent;");
} else {
textField.setStyle("-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);");
}
}
});
JavaFX FXML CSS:
Add the following to your CSS file.
.text-input {
-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);
}
.text-input:focused {
-fx-prompt-text-fill: transparent;
}
Hope this helped...
Solution
This example will allow TextFields in JavaFX whose prompt behaviour is to show the prompt when the field is empty, even if the field has focus. The solution is a combination of custom CSS and a custom TextField class, which manipulates the css styles of the TextField.
persistent-prompt.css
.persistent-prompt:focused {
-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);
}
.no-prompt {
-fx-prompt-text-fill: transparent !important;
}
PersistentPromptTextField.java
import javafx.scene.control.TextField;
public class PersistentPromptTextField extends TextField {
PersistentPromptTextField(String text, String prompt) {
super(text);
setPromptText(prompt);
getStyleClass().add("persistent-prompt");
refreshPromptVisibility();
textProperty().addListener(observable -> refreshPromptVisibility());
}
private void refreshPromptVisibility() {
final String text = getText();
if (isEmptyString(text)) {
getStyleClass().remove("no-prompt");
} else {
if (!getStyleClass().contains("no-prompt")) {
getStyleClass().add("no-prompt");
}
}
}
private boolean isEmptyString(String text) {
return text == null || text.isEmpty();
}
}
PromptChanger.java
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class PromptChanger extends Application {
#Override
public void start(Stage stage) throws Exception {
TextField textField1 = new PersistentPromptTextField("", "First name");
TextField textField2 = new PersistentPromptTextField("", "Last name");
VBox layout = new VBox(
10,
textField1,
textField2
);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
scene.getStylesheets().add(
getClass().getResource(
"persistent-prompt.css"
).toExternalForm()
);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
How prompt handling is currently implemented in JavaFX 8
Default CSS for JavaFX 8 (modena.css) for controlling the prompt text is as follows:
.text-input {
-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);
}
.text-input:focused {
-fx-prompt-text-fill: transparent;
}
This will make the prompt text transparent whenever a field is focused, even if the field has no data in it.
In Comparison to HTML
HTML input has a placeholder, which is specified as follows:
User agents should present this hint to the user . . . when the element's value is the empty string or the control is not focused (or both).
You can try this functionality in your browser at this test link.
I think the argument against this behaviour for JavaFX "Prompt text should be cleared when focus is grabbed to signal readiness to receiving user input" is moot because focused text fields get a clearly visible focus ring, so the user knows that the control is ready to receive input even when the prompt text is displayed.
I think JavaFX should by default operate the same way as most HTML user agents in this respect. Feel free to create a Tweak request in the JavaFX issue tracker to request that the input prompt in JavaFX work similarly to HTML implementations (if there isn't an issue created for this already).
Alternative
Glisten
The third-party Gluon Glisten has a custom TextField control can have the following attributes:
Float Text- A place-holder text inside a TextField which is transitioned to the top of the TextField when focus is received by it.
The nice thing about the Glisten-based TextField is that the prompt text is always visible, whether or not the user has typed anything into the control.
MaterialFX
MaterialFX also provides a variety of alternative implementations for text field prompts, similar to the Glisten operation, but matching the Google Material design. To see samples, click on the “Fields” demo option from the preview gifs.
You could configure the '-fx-prompt-text-fill' key in '.text-field:focused' area like in '.text-field' area (css):
`.text-field {
-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);
}
.text-field:focused {
-fx-prompt-text-fill: derive(-fx-control-inner-background,-30%);
}`
and add the 'promptText' entry in fxml file:
<TextField fx:id="XY_Id" promptText="First name">
That is all and works for me.
I am sure there may be several approaches and tricks to achieve what you're asking for, but, you are overriding a default, expected and standard UI behavior when you do so. Prompt text should be cleared when focus is grabbed to signal readiness to receiving user input, not after user starts typing.
Assuming you're setting prompt text via:
#FXML TextField tf;
public void someMethod() {
tf.setPromptText("This is the prompt text");
}
You can instead use tf.setText(String str) to set initial text.
Then, in the initialize method of your controller, add:
tf.setOnKeyTyped(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
tf.setText(null);
}
});
A link that you can refer to for additional help: Click me!
textField.setStyle("-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);");
or
textField.styleProperty().bind(
Bindings.createStringBinding(
()->"-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);"));

Alerts in Javafx do not close when "x" button is pressed

Hi for a different javafx application I've been testing alerts and the only thing which doesn't work in pressing the "X" button for the alert box.
I have added a code below but if you don't have time to run it here is a GIF of explaining what issue I have with the alertbox:
https://giant.gfycat.com/GeneralUntimelyBluewhale.webm
I am not quite sure how to upload gifs to the actual post so sorry for that.
Is there any way of fixing this issue?
Thanks
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Playground extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
VBox root = new VBox(100);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
Button button = new Button("Alert");
button.setOnAction(event -> {
ButtonType goodButton = new ButtonType("Good");
ButtonType badButton = new ButtonType("Bad");
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "", goodButton, badButton);
alert.showAndWait();
if (alert.getResult().equals(goodButton)) {
System.out.println("Good");
} else if (alert.getResult().equals(badButton)) {
System.out.println("Bad");
}
});
// Add the buttons to the layout
root.getChildren().addAll(button);
// Show the Stage
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
As per the "Dialog Closing Rules" in the Dialog API documentation, the default "X" button works normally only if atleast one of the buttons is of type "CANCEL". So changing any one of your button to ButtonType.CANCEL should close your dialog on click of "X".
If you are not interested in using built in buttons, then you have to explicitly handle the close request of dialog as per your requirement.
ButtonType goodButton = new ButtonType("Good");
ButtonType badButton = new ButtonType("Bad");
Alert alert = new Alert(Alert.AlertType.ERROR,"",goodButton,badButton);
Window window = alert.getDialogPane().getScene().getWindow();
window.setOnCloseRequest(e -> alert.hide());
Optional<ButtonType> result = alert.showAndWait();
result.ifPresent(res->{
if (res.equals(goodButton)) {
System.out.println("Good");
} else if (res.equals(badButton)) {
System.out.println("Bad");
}
});
To add to Sai Dandem's answer, here is the relevant javadoc from Dialog:
...
Dialog Closing Rules
It is important to understand what happens when a Dialog is closed, and also how a Dialog can be closed, especially in abnormal closing situations (such as when the 'X' button is clicked in a dialogs title bar, or when operating system specific keyboard shortcuts (such as alt-F4 on Windows) are entered). Fortunately, the outcome is well-defined in these situations, and can be best summarised in the following bullet points:
JavaFX dialogs can only be closed 'abnormally' (as defined above) in two situations:
When the dialog only has one button, or
When the dialog has multiple buttons, as long as one of them meets one of the following requirements:
The button has a ButtonType whose ButtonBar.ButtonData is of type ButtonBar.ButtonData.CANCEL_CLOSE.
The button has a ButtonType whose ButtonBar.ButtonData returns true when ButtonBar.ButtonData.isCancelButton() is called.
In all other situations, the dialog will refuse to respond to all close requests, remaining open until the user clicks on one of the available buttons in the DialogPane area of the dialog.
If a dialog is closed abnormally, and if the dialog contains a button which meets one of the two criteria above, the dialog will attempt to set the result property to whatever value is returned from calling the result converter with the first matching ButtonType.
If for any reason the result converter returns null, or if the dialog is closed when only one non-cancel button is present, the result property will be null, and the showAndWait() method will return Optional.empty(). This later point means that, if you use either of option 2 or option 3 (as presented earlier in this class documentation), the Optional.ifPresent(java.util.function.Consumer) lambda will never be called, and code will continue executing as if the dialog had not returned any value at all.
Normally, when using AlertType.CONFIRMATION, there would already be a cancel button. However, you're declaring your own buttons in the constructor of your Alert which overrides the default buttons.
Javadoc of Alert(AlertType,String,ButtonType...):
...
By passing in a variable number of ButtonType arguments, the developer is directly overriding the default buttons that will be displayed in the dialog, replacing the pre-defined buttons with whatever is specified in the varargs array.
...
And none of your buttons are a cancel button. Since you don't specify a ButtonData they all have ButtonBar.ButtonData.OTHER.
Alternatively, if you want the close "X" button of the alert window to default to one of the alert buttons you can set its ButtonData type:
ButtonType goodButton = new ButtonType("Good");
ButtonType badButton = new ButtonType("Bad", ButtonBar.ButtonData.CANCEL_CLOSE);
Closing the alert will then be equivalent to pressing the "Bad" button.

JavaFX TreeView: remove expand / collapse button (disclosure node) & functionality

I want to have a TreeView that has all of its children permanently expanded, and I don't want the user to be able to expand or collapse any of the children.
To do this I've found that I need to do the following:
Remove icon with CSS (Done)
Change expand and collapse image TreeView JavaFX 2.2
[edit] Above link should be used to change image; to remove completely, use this solution: https://stackoverflow.com/a/27831191/4430591
Remove double click functionality (Done)
Disable TreeItem's default expand/collapse on double click JavaFX 2.2
[edit] Remove ability to collapse / expand using keyboard arrrow keys (Done)
Given in José Pereda's solution below ( https://stackoverflow.com/a/27831085/4430591 )
[edit] Remove ability to right click for a ContextMenu (Done)
Given in José Pereda's solution below ( https://stackoverflow.com/a/27831085/4430591 )
Remove icon's clickablity (How do I do this?)
[edit] solution: https://stackoverflow.com/a/27831191/4430591
Even though the icon is no longer visible, it's still clickable. I don't see any way of filtering this; I only see ways to be able to respond to it after the fact.
Also, if I'm missing anything else that I need to do to ensure this functionality, please let me know.
I feel quite silly. I think this was mostly just a matter of not knowing what that darn arrow was called. Apparently it's a disclosureNode? Maybe that's common knowledge.
In the custom defined TreeCell, all I did was add this line in the updateItem method:
setDisclosureNode(null);
The solution to avoid modifying the skin or the default behavior is more simple if we trap the clicks before they are dispatched, and consume the right ones.
For that we can use an EventDispatcher, to filter both the mouse pressed and the right click over the arrows, which are StackPane nodes:
class CellEventDispatcher implements EventDispatcher {
private final EventDispatcher original;
public CellEventDispatcher(EventDispatcher original) {
this.original = original;
}
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (event.getEventType().equals(MouseEvent.MOUSE_PRESSED) ||
event.getEventType().equals(ContextMenuEvent.ANY)){
event.consume();
}
if(event instanceof KeyEvent && event.getEventType().equals(KeyEvent.KEY_PRESSED)){
if((((KeyEvent)event).getCode().equals(KeyCode.LEFT) ||
((KeyEvent)event).getCode().equals(KeyCode.RIGHT))){
event.consume();
}
}
return original.dispatchEvent(event, tail);
}
}
Now we apply our custom dispatcher to the tree view:
#Override
public void start(Stage primaryStage) {
TreeView<String> tree = new TreeView<>();
...
EventDispatcher treeOriginal = tree.getEventDispatcher();
tree.setEventDispatcher(new CellEventDispatcher(treeOriginal));
Scene scene = new Scene(tree);
primaryStage.setScene(scene);
primaryStage.show();
}
This will consume any click (left or right) over the arrows on the tree.
EDIT
Added to the event dispatcher class the case where the user uses the keyboard to traverse the tree view, consuming the collapse/expand events with arrow LEFT or RIGHT.

javafx setFocus after tabPaine change

Problem:
Have tabPane tabs OK.
In the first tab there is a text field. I am able to get focus on this field when starting the application.
After changing the tabs and coming back to the first tab I want focus to be on this textfield (barcodereader should be active in this field) without having to select the field with the mouse.
I am able to catch event from tabs with
tp.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>()
{ etc
(could not post with code)
and I am able to trigger en event for the first tab.
But field.requestFocus(); does not work. Probably because this method comes before rendering the textfield.
So here is my question:
How do you set focus on a control after clicking tabs in TabPane?
If you handle the mouse release event, it works: (The doFocus enables the requestFocus handling only when a tab selection changed before, otherwise it kicks in every time you click somewhere in the TabPane.)
final SimpleBooleanProperty doFocus = new SimpleBooleanProperty(false);
tabPane.setOnMouseReleased(new EventHandler<Event>() {
#Override
public void handle(Event event) {
if (!doFocus.get()) {
return;
}
doFocus.set(false);
switch (tabPane.selectionModelProperty().getValue().selectedIndexProperty().intValue()) {
case 0: tf1b.requestFocus(); break;
case 1: tf2a.requestFocus(); break;
default: break;
}
}
});
tabPane.selectionModelProperty().getValue().selectedIndexProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
doFocus.set(true);
}
});
When the TabPane has focus, one can change tab selection with the cursor keys and there the TextFields also won't get the focus with selection based approach. This probably should be handled too, if you need it.
(Recently I had a similar problem. I noticed, that the TabPane switches tabs immediately when you press the mouse button. My guess would be, that the selection based approach requests focus on the TextField right after mouse down, but the continued mouse down steals the focus back to the TabPane. Or maybe even the single mouse down event which changes selection causes the focus to go back to TabPane. However, my assumptions regarding the reasons may not be correct, as I am a newbie to JavaFX.)
EDIT: That handling certainly is not optimal. For instance, if you change tabs with the keys, the doFocus will be enabled and then clicking anywhere in the TabPane will trigger the requestFocus call. I thought this should be mentioned.
Also, take a look at my solution for setting focus on TextArea, when user changes selected tab(using mouse or keyboard) https://stackoverflow.com/a/19046535/2791746

Resources