My question is when the showAndWait's output is null? Because in my program it has two ok and cancel buttons and neither of the cancel and close buttons gives null output.
dialog.showAndWait().ifPresent(buttonType -> {
System.out.println(buttonType);
}
when I press the close button(X) and the cancel button the output is(not null):
ButtonType [text=Cancel, buttonData=CANCEL_CLOSE]
Note that Dialog#showAndWait() returns an Optional and will never return null. However, the returned Optional may be empty. From reading through the documentation, this will occur in the following cases:
The result converter returns null.
There is only one non-cancel button and the dialog is closed "abnormally" (e.g. the X button is clicked).
But note this will give you an empty Optional only if the result converter returns null (or there's no result converter) and the result property contains null.
Here's an example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
var btn = new Button("Show dialog...");
btn.setOnAction(
e -> {
Dialog<Object> dialog = new Dialog<>();
dialog.setContentText("Empty Optional test.");
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
dialog
.showAndWait()
.ifPresentOrElse(System.out::println, () -> System.out.println("NO RESULT"));
});
var scene = new Scene(new StackPane(btn), 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Running the above, showing the dialog, and then clicking the X button will result in the following output:
NO RESULT
In other words, the Optional was empty.
You mention your tests include cancel buttons. That's why you see a non-empty Optional, because by default the result will be the first matching cancel button when the dialog is closed "abnormally". See the "Dialog Closing Rules" section of Dialog's Javadoc for more information.
Related
I've been puzzling over this one for a while now and just wanted to see if others had run into this problem before or if maybe I'm doing something wrong.
Given a javafx implementation in which we use a combobox whose items are set from an ObservableArrayList which can be updated, modified, replaced, etc. and a combobox with an action listener just logging out whenever it's triggered.
package sample;
import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class Main extends Application {
ObservableList<String> theList = FXCollections.observableArrayList();
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Sample");
FlowPane root = new FlowPane(Orientation.VERTICAL);
root.setVgap(20);
List<String> initialColors = Arrays.asList("red", "green", "blue", "black");
theList.addAll(initialColors);
ComboBox<String> theComboBox = new ComboBox<>();
theComboBox.setItems(theList);
theComboBox.setOnAction( event -> {
System.out.println(String.format("theComboBox action listener triggered, current value is %s", theComboBox.getValue()));
});
Button bttn1 = new Button("Press me");
bttn1.setOnAction(event -> {
List<String> someColors = Arrays.asList("red", "orange", "mauve", "pink", "blue", "salmon", "chiffon");
System.out.println("About to issue setAll against observable list");
theList.setAll(someColors);
});
root.getChildren().add(theComboBox);
root.getChildren().add(bttn1);
primaryStage.setScene(new Scene(root, 100, 150));
primaryStage.show();
System.out.println("Setting initial selection to \"blue\"");
theComboBox.setValue("blue");
}
public static void main(String[] args) {
launch(args);
}
}
I think the action event should only be triggered when the user changes the combobox through a direct action but I'm seeing the event trigger when the observablelist is modified.
We're using javafx version 11.0.2
Should I log this as a bug with Gluon?
EDIT: Updated example
In this example you can see that whenever the underlying data is modified via setAll the action event is triggered. This would be fine if the action event had some sort of way to differentiate between whether it's from a direct user interaction or from programmatic changes to the underlying data.
Other strange behaviour, the action event won't trigger if you select "black" and then hit the "press me" button and the combobox will then select pink because it's in the same position in the list - but then select red or even pink again and you'll get null, red, null and null, pink, null respectively.
I'd expect the combobox to retain its value even if the selection is not present anymore and I also would not expect these events to be triggered when the observable list is being modified - if you needed/wanted to listen for events when the observable list is modified you can attach a listener directly to it instead.
Just to wrap this up, take a look through the comments on the question for more detail.
We think this is a bug in javafx 11.0.2 - I've filed a bug report through the openjdk jira.
A workaround for now is to set a flag boolean variable and only perform actions within the listener when it is true.
something as simple as adding:
private boolean actionEventsOn = true;
theComboBox.setOnAction( event -> {
if(actionEventsOn){
System.out.println(String.format("theComboBox action listener triggered, current value is %s", theComboBox.getValue()));
}
});
bttn1.setOnAction(event -> {
List<String> someColors = Arrays.asList("red", "orange", "mauve", "pink", "blue", "salmon", "chiffon");
System.out.println("About to issue setAll against observable list");
actionEventsOn = false;
theList.setAll(someColors);
actionEventsOn = true;
});
This should prevent action events triggering unnecessarily.
I have a problem with JavaFx ListView component. I'm using popup with TextField and ListView inside of VBox. When TextField is in focus, I can normally close this popup window pressing the Esc key on the keyboard, but when ListView item is in focus popup stays open, nothing happens.
Minimal reproducible example:
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
MenuItem rightClickItem = new MenuItem("CLICK!");
rightClickItem.setOnAction(a -> showdialog());
ContextMenu menu = new ContextMenu(rightClickItem);
Label text = new Label("Right Click on me");
text.setContextMenu(menu);
StackPane root = new StackPane(text);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("RightClick MenuItem And Dialog");
primaryStage.setScene(scene);
primaryStage.show();
}
private void showdialog() {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
VBox vBox = new VBox();
ListView listView = new ListView();
listView.getItems().add("Item 1");
listView.getItems().add("Item 2");
vBox.getChildren().add(new TextField());
vBox.getChildren().add(listView);
vBox.addEventHandler(KeyEvent.KEY_PRESSED, keyEvent -> System.err.println("Key pressed: " + keyEvent.getCode()));
dialog.getDialogPane().setContent(vBox);
dialog.showAndWait();
}
public static void main(String[] args) {
launch(args);
}
}
It seems to me that Esc key is consumed in ListView, and this cause a problem with closing a popup.
Just to mention, I'm using zulu-11.0.8 JDKFx version.
It seems to me that Esc key is consumed in ListView, and this cause a problem with closing a popup.
That's indeed the problem - happens with all controls that have a consuming KeyMapping to ESCAPE added by their respective Behavior (f.i. also for a TextField with TextFormatter).
There is no clean way to interfere with it (Behavior and InputMap didn't yet make to move into public api). The way to hack around is to remove the KeyMapping from the Behavior's inputMap. Beware: you must be allowed to go dirty, that is use internal api and use reflection!
The steps:
grab the control's skin (available after the control is added to the scenegraph)
reflectively access the skin's behavior
remove the keyMapping from the behavior's inputMap
Example code snippet:
private void tweakInputMap(ListView listView) {
ListViewSkin<?> skin = (ListViewSkin<?>) listView.getSkin();
// use your favorite utility method to reflectively access the private field
ListViewBehavior<?> listBehavior = (ListViewBehavior<?>) FXUtils.invokeGetFieldValue(
ListViewSkin.class, skin, "behavior");
InputMap<?> map = listBehavior.getInputMap();
Optional<Mapping<?>> mapping = map.lookupMapping(new KeyBinding(KeyCode.ESCAPE));
map.getMappings().remove(mapping.get());
}
It's usage:
listView.skinProperty().addListener(ov -> {
tweakInputMap(listView);
});
To avoid using private API, you can use an event filter that, if the ListView is not editing, copies the Escape key event and fires it on the parent. From there, the copied event can propagate to be useful in other handlers such as closing a popup.
Also, if you need this on all ListViews in your application, you can do it in a derived class of ListViewSkin and set that as the -fx-skin for .list-view in your CSS file.
listView.addEventFilter( KeyEvent.KEY_PRESSED, keyEvent -> {
if( keyEvent.getCode() == KeyCode.ESCAPE && !keyEvent.isAltDown() && !keyEvent.isControlDown()
&& !keyEvent.isMetaDown() && !keyEvent.isShiftDown()
) {
if( listView.getEditingIndex() == -1 ) {
// Not editing.
final Parent parent = listView.getParent();
parent.fireEvent( keyEvent.copyFor( parent, parent ) );
}
keyEvent.consume();
}
} );
I want to create a Popup for main Stage TextField. Popup contains ScrollPane which holds possible options as Buttons.
After Popup shown TextField key events like left, rigth, home, end have no effect. Key events are received on TextField.
Is there any reasonable solution or workaround for this issue.
To reproduce please type in some text and try to press left arrow.
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class PopupApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
TextField textField = new TextField();
StackPane stackPane = new StackPane(textField);
stage.setScene(new Scene(stackPane));
stage.show();
ScrollPane scrollPane = new ScrollPane(new VBox(new Button("Option1"), new Button("Option2")));
Popup popup = new Popup();
popup.getContent().add(scrollPane);
Point2D pinPoint = textField.localToScreen(0., textField.getHeight());
popup.show(textField, pinPoint.getX(), pinPoint.getY());
textField.addEventHandler(KeyEvent.KEY_RELEASED, event -> {
System.out.println("KEY_RELEASED " + event);
});
textField.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
System.out.println("KEY_PRESSED " + event);
});
}
}
Actually, the KEY_PRESSED event is not being received by your textField when pressing Keys like Left, Right, ... This is shown by the output from your program.
You are facing this problem because Events are being redirected to Popup. If these events are consumed while dispatching, then the effect of these events are not shown by textField. In your case it is due to the presence of Button on Popup. (Most likely due to the presence of node that can receive focus. Not sure though). To solve this you can set your own EventDispatcher. Something like this before popup.show():
popup.setEventDispatcher((event, tail) -> {
if (event.getEventType() != RedirectedEvent.REDIRECTED) {
tail.dispatchEvent(event);
}
return null;
});
All the redirected events into the Popup will now be discarded and further dispatching does not occur. You can tweak this to suit your requirement.
Suppose I have an ObservableList and a Button in the same controller class:
private ObservableList<NameItem> _selectedList = _database.getONameList();
#FXML
private Button nextButton;
How do I make it so that the Button is only enabled while the ObservableList is not empty? Is there a binding property I can use to set this?
This can be done fairly easily with just a couple of simple Bindings, actually.
First, you want to create an IntegerBinding that is bound to the size of your ObservableList:
IntegerBinding listSize = Bindings.size(_selectedList);
Then create a new BooleanBinding that is bound to whether or not the listSize binding is greater than 0:
BooleanBinding listPopulated = listSize.greaterThan(0);
Now, all you need to do is bind the button's disableProperty to the opposite of the listPopulated property using the not() method (since listPopulated will be true if items are in the list, you want to actually pass false to the button's disableProperty):
button.disableProperty().bind(listPopulated.not());
Here is a quick MCVE to demonstrate:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.IntegerBinding;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
// Create an empty ObservableList
ObservableList<String> list = FXCollections.observableArrayList();
// Create a binding to extract the list's size to a property
IntegerBinding listSizeProperty = Bindings.size(list);
// Create a boolean binding that will return true only if the list has 1 or more elements
BooleanBinding listPopulatedProperty = listSizeProperty.greaterThan(0);
// Create the button we want to enable/disable
Button button = new Button("Submit");
// Bind the button's disableProperty to the opposite of the listPopulateProperty
// If listPopulateProperty is false, the button will be disabled, and vice versa
button.disableProperty().bind(listPopulatedProperty.not());
// Create another button to add an item to the list, just to demonstrate the concept
Button btnAddItem = new Button("Add Item");
btnAddItem.setOnAction(event -> {
list.add("New Item");
System.out.println(list.size());
});
// Add the buttons to the layout
root.getChildren().addAll(btnAddItem, button);
// Show the Stage
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
In the above example, the "Submit" button is disabled until you add an item to the ObservableList using the "Add Item" button.
EDIT: As Lukas excellently points out in the comments below, these Bindings can also all be chained together to simplify things (either method is equally valid; it just depends on which you find more readable, really):
button.disableProperty().bind(Bindings.size(list).greaterThan(0).not())
Another Method
Another way to do this is with a ListChangeListener that enables or disables the button any time the list changes:
list.addListener((ListChangeListener<String>) c -> {
// If the size of the list is less than 1, disable the button; otherwise enable it
button.setDisable(c.getList().size() < 1);
});
This will essentially do exactly the same thing as the first method, but you'll need to set the initial state of the button yourself before the listener can keep it updated for you.
I have trouble when closing a window in JavaFX.
I define my setOnCloseRequest as I wanted and it works when I click the x in the window. However, I also need a button to close the window and this onCloseRequest has to work, the problem is it does not. The event does not fire at all.
I am using JavaFX 2.2 (Java 7) and I notice that the reference for setOnCloseRequest says close the window on external request
Solution
Fire an event from your internal close request (on the button push), so that the application thinks it received an external close request. Then your close request logic can be identical whether the request came from an external event or an internal one.
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
// close event handling logic.
// consume the event if you wish to cancel the close operation.
}
...
stage.setOnCloseRequest(confirmCloseEventHandler);
Button closeButton = new Button("Close Application");
closeButton.setOnAction(event ->
stage.fireEvent(
new WindowEvent(
stage,
WindowEvent.WINDOW_CLOSE_REQUEST
)
)
);
Note
This is a Java 8+ solution, for JavaFX 2, you will need to convert the lambda functions in anonymous inner classes and will be unable to use the Alert dialog box but will need to provide your own alert dialog system as JavaFX 2 does not feature an in-built one. I strongly recommend upgrading to Java 8+ rather than staying with JavaFX 2.
Sample UI
Sample Code
The sample code will show the user a close confirmation alert and cancel the close request if the user does not confirm the close.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.stage.*;
import javafx.stage.WindowEvent;
import java.util.Optional;
public class CloseConfirm extends Application {
private Stage mainStage;
#Override
public void start(Stage stage) throws Exception {
this.mainStage = stage;
stage.setOnCloseRequest(confirmCloseEventHandler);
Button closeButton = new Button("Close Application");
closeButton.setOnAction(event ->
stage.fireEvent(
new WindowEvent(
stage,
WindowEvent.WINDOW_CLOSE_REQUEST
)
)
);
StackPane layout = new StackPane(closeButton);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
Alert closeConfirmation = new Alert(
Alert.AlertType.CONFIRMATION,
"Are you sure you want to exit?"
);
Button exitButton = (Button) closeConfirmation.getDialogPane().lookupButton(
ButtonType.OK
);
exitButton.setText("Exit");
closeConfirmation.setHeaderText("Confirm Exit");
closeConfirmation.initModality(Modality.APPLICATION_MODAL);
closeConfirmation.initOwner(mainStage);
// normally, you would just use the default alert positioning,
// but for this simple sample the main stage is small,
// so explicitly position the alert so that the main window can still be seen.
closeConfirmation.setX(mainStage.getX());
closeConfirmation.setY(mainStage.getY() + mainStage.getHeight());
Optional<ButtonType> closeResponse = closeConfirmation.showAndWait();
if (!ButtonType.OK.equals(closeResponse.get())) {
event.consume();
}
};
public static void main(String[] args) {
launch(args);
}
}