What Event is called in JavaFX, when a Node is focused?
I have TextField, which can be focused either by mouse (setOnMouseClicked) or by TAB key from other Node (here my problem comes in).
How can I handle the second focus possibility? Is there a way how to handle both at once?
You can listen to the focused property:
TextField tf = new TextField();
TextField tf2 = new TextField();
tf.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
System.out.println("Node 1: Mine!");
}
});
tf2.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
System.out.println("Node 2: Mine!");
}
});
Scene scene = new Scene(new VBox(tf, tf2), 300, 250);
If you change the focus, you can observe see the 2 TextField "arguing who's got the focus".
Related
This question already has answers here:
Get the height of a node in JavaFX (generate a layout pass)
(2 answers)
Closed 3 years ago.
So I'm not sure how to calculate the height of the node during the .setOnAction event I have tried .requestLayout()/.applyCss() not sure what else to try I am trying to find the height of the vBox after adding a node but it is only printing the height of the node before the new one was added
public class Main extends Application {
#Override
public void start(Stage stage) {
VBox vBoxContainer = new VBox();
vBoxContainer.setAlignment(Pos.CENTER);
vBoxContainer.setPrefSize(200,200);
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
for (int i = 0; i < 5; i++)
vBox.getChildren().add(new Label("newLabel"));
vBoxContainer.getChildren().add(vBox);
Button button = new Button("Add Label");
button.setOnAction(event -> {
System.out.println("Height Before new Label:"+vBox.getHeight());
vBox.getChildren().add(new Label("newLabel"));
//here is where I was adding code to produce expected result
System.out.println("Height After new Label:"+vBox.getHeight());
});
Button checkButton = new Button("Print VBox Height");
checkButton.setOnAction(event -> System.out.println("VBox Height:"+vBox.getHeight()));
vBoxContainer.getChildren().addAll(button, checkButton);
stage.setScene(new Scene(vBoxContainer));
stage.show();
}
}
Run the example and Click the button that adds a Label to the vBox and it outputs
Actual Result:
Height Before new Label:85.0
Height After new Label:85.0
Expected Result:
Height Before new Label:85.0
Height After new Label:102.0
But if you then click the Print VBox Height Button it will show the correct height of:
VBox Height:102.0
You can try adding a listener to the VBox's height property.
VBox vBoxContainer = new VBox();
vBoxContainer.setAlignment(Pos.CENTER);
vBoxContainer.setPrefSize(200, 200);
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
vBoxContainer.getChildren().add(vBox);
vBox.heightProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Height changed to: " + newValue.doubleValue());
if(newValue.doubleValue() > 100)
{
//do something!
}
});
for (int i = 0; i < 5; i++) {
vBox.getChildren().add(new Label("newLabel"));
}
Button button = new Button("Add Label");
button.setOnAction(event -> {
vBox.getChildren().add(new Label("newLabel"));
});
Button checkButton = new Button("Print VBox Height");
checkButton.setOnAction(event -> System.out.println("VBox Height:" + vBox.getHeight()));
vBoxContainer.getChildren().addAll(button, checkButton);
stage.setScene(new Scene(vBoxContainer));
stage.show();
requestLayout does not actually do a layout pass. It simply tells JavaFX, that a layout pass is required which will result in JavaFX doing the layout pass some time after your method returns. To do the layout yourself, you need to call layout yourself, i.e. change the logic in the event handler like this:
button.setOnAction(event -> {
System.out.println("Height Before new Label:"+vBox.getHeight());
vBox.getChildren().add(new Label("newLabel"));
// manually doing layout on the root here
vBoxContainer.applyCss();
vBoxContainer.layout();
System.out.println("Height After new Label:"+vBox.getHeight());
});
Note that I do the layout pass for the root, since the ancestor layouts can also be involved in determining the actual size of a Node...
We created a Custom Dialog without an FXML file. We are using JavaFX 8.
The dialog loads and functions as expected but we can not move the Buttons and the TextField to enhance the styling.
We have tried to use tf.setLayoutY(50) this has no effect.
We used this tf.setPromptText("This Works ?") and it works.
We would rather not use css to accomplish this styling.
And we will consider a FXML file if we can keep the two event handlers that force data to be entered in the TextField.
So the question is: How to style this Custom Dialog?
The code is a mess as it includes some concepts we tried:
public void CustomDialog() {
Dialog dialog = new Dialog<>();
dialog.setResizable(false);
final Window window = dialog.getDialogPane().getScene().getWindow();
stage = (Stage) window;
stage.setMinHeight(600);
stage.setMinWidth(400);
TextField tf = new TextField();
tf.setLayoutX(10);
tf.setLayoutY(50);
dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
dialog.getDialogPane().getChildren().add(tf);
dialog.getDialogPane().setContent(tf);
// Create an event filter that consumes the action if the text is empty
EventHandler<ActionEvent> filter = event -> {
if (tf.getText().isEmpty()) {
event.consume();
}
};
// lookup the buttons
ButtonBase okButton = (Button) dialog.getDialogPane().lookupButton(ButtonType.OK);
Button cancelButton = (Button) dialog.getDialogPane().lookupButton(ButtonType.CANCEL);
// add the event-filter
okButton.addEventFilter(ActionEvent.ACTION, filter);
cancelButton.addEventFilter(ActionEvent.ACTION, filter);
stage.setOnCloseRequest(event -> {
if (tf.getText().isEmpty()) {
event.consume();
}
}
//Scene scene = new Scene(root);
//dialogStage.setScene(scene);
dialog.initModality(Modality.APPLICATION_MODAL);
//dialogStage.setAlwaysOnTop(true);
//dialogStage.setResizable(false);
tf.setPromptText("This Works ?");
tf.requestFocus();// This does not work
dialog.showAndWait();
}
Grendel we enhanced your answer so anyone who comes by and sees the code you posted in your question will understand as you said it was a mess
Your posted answer was real old school but less work perhaps than building a FXML file
Besides it is good to know some old school tricks
public void NewDialog(){
Label lblAmt = new Label("Enter Amount");
Button btnOK = new Button("OK");
TextField txtAmt = new TextField();
AnchorPane secondaryLayout = new AnchorPane();
secondaryLayout.setStyle("-fx-border-color:red;-fx-border-width:10px; -fx-background-color: lightblue;");
secondaryLayout.getChildren().addAll(lblAmt,btnOK,txtAmt);
lblAmt.setLayoutX(30);
lblAmt.setLayoutY(30);
txtAmt.setLayoutX(164);
txtAmt.setLayoutY(25);
txtAmt.setMaxWidth(116);
btnOK.setLayoutX(190);
btnOK.setLayoutY(100);
btnOK.setStyle("-fx-font-size: 18px;-fx-font-weight: bold;");
lblAmt.setStyle("-fx-font-size: 18px;-fx-font-weight: bold;");
txtAmt.setStyle("-fx-font-size: 18px;-fx-font-weight: bold;");
Scene secondScene = new Scene(secondaryLayout, 300, 180);
EventHandler<ActionEvent> filter = event -> {
if(txtAmt.getText().isEmpty()) {
event.consume();
}
};
// New window (Stage)
Stage newWindow = new Stage();
newWindow.initStyle(StageStyle.UNDECORATED);
//newWindow.initModality(Modality.APPLICATION_MODAL);
newWindow.setResizable(false);
newWindow.setTitle("Second Stage");
newWindow.setScene(secondScene);
btnOK.addEventHandler(ActionEvent.ACTION,filter);
btnOK.setOnAction(evt -> {
String str = txtAmt.getText();
System.out.println("################ str "+str);
if(txtAmt.getText().equals("")) {
evt.consume();
txtAmt.requestFocus();
}else{
newWindow.close();
}
});
newWindow.setOnCloseRequest(event -> {
if(txtAmt.getText().isEmpty()) {
event.consume();
}
});
txtAmt.requestFocus();
newWindow.showAndWait();
}
I have a very simple task to accomplish. I just want to press any letter on a button, matches the key code, and move the focus to a text field. I wrote
a simple test code as shown. I have no problem to shift the focus. However,
I don't want the letter I press shows up in the text field. Seemingly a simple programming solution turns out to be not so simple.
I don't understand why the event consume method doesn't stop the event from propagating down the event chain and have the typed letter shown up at the text field.
It seems like after the requestFocus is called, the text field picks up the letter typed from the button. This happens on Mac. Any help will be greatly appreciated.
package testkeynavigation;
public class TestKeyNavigation extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
StackPane root = new StackPane();
TextField txt1 = new TextField();
TextField txt2 = new TextField();
VBox vbox = new VBox();
vbox.getChildren().add(btn);
vbox.getChildren().add(txt1);
vbox.getChildren().add(txt2);
root.getChildren().add(vbox);
Scene scene = new Scene(root, 300, 250);
btn.setOnKeyPressed((KeyEvent e) ->{
if (e.getCode() == KeyCode.A) {
e.consume();
System.out.println("e.isConsumed: "+e.isConsumed());
txt2.requestFocus();
}
});
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
btn.requestFocus();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
There are three kinds of key event: KEY_PRESSED, KEY_TYPED, and KEY_RELEASED. In a key stroke, an event of each of these types is fired, in that order, to the UI node that has the keyboard focus at the time of the event.
A TextField has an internal listener for KEY_TYPED events; so if a KEY_TYPED event occurs when the text field has focus, a character is entered into the text field (or other actions occur, e.g. deleting characters or moving the caret, depending on the key).
In your code, you listen for the first one of these - KEY_PRESSED - to occur on the button. If the key press has a code of A, you consume that event (the KEY_PRESSED event) then transfer keyboard focus to the text field. At a slightly later moment, the user releases the key, and since the text field now has focus, a KEY_TYPED event is fired on the text field. Note that this is a new event, so it is not consumed, so the text fields reacts as though a character has been entered. Finally, a KEY_RELEASED event is fired on the text field.
You can see this in action if you add the debugging code:
txt2.addEventFilter(KeyEvent.ANY, e -> {
System.out.printf("Key event on text field: type=%s, code=%s, character=%s%n",
e.getEventType(), e.getCode(), e.getCharacter());
});
To fix the problem, just listen for the last event in the series of events: the key released event. Note that you don't need to consume the event.
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
StackPane root = new StackPane();
TextField txt1 = new TextField();
TextField txt2 = new TextField();
VBox vbox = new VBox();
vbox.getChildren().add(btn);
vbox.getChildren().add(txt1);
vbox.getChildren().add(txt2);
root.getChildren().add(vbox);
Scene scene = new Scene(root, 300, 250);
btn.setOnKeyReleased((KeyEvent e) ->{
if (e.getCode() == KeyCode.A) {
txt2.requestFocus();
}
});
txt2.addEventFilter(KeyEvent.ANY, e -> {
System.out.printf("Key event on text field: type=%s, code=%s, character=%s%n",
e.getEventType(), e.getCode(), e.getCharacter());
});
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
btn.requestFocus();
}
I have 3 ToggleGroups, which I need to attach to a TextField, so that the TextField display the appropriate number each time the ToggleGroup property changes.
first I've added a listener to each ToggleGroup as that
int f; //to calculate the first property condition
group1.selectedToggleProperty().addListener((v, oldValue, newValue) -> {
if (group1.getSelectedToggle() != null) {
//call a method which test the radioButtons to give the value to each one
f = testChoix(rdF1, rdF2, rdF3, rdF4);
}
});
then I've added a listener to the TextField
txtCriticite.textProperty().addListener((observable, oldValue, newValue) -> {
calculCriticite();//method to calculate
});
//method to calculate the textField Value
public void calculCriticite() {
int c = f * g * d;
txtCriticite.setText(String.valueOf(c));
}
when compiling and clicking on the RadioButtons, I don't see any text in my TextField
Don't change the text of your TextField inside the listener of the TextField, this can lead to problems. Instead, call your calculCriticite() method from the ToggleGroup listener, this should work.
I have an Accordian with multiple TitledPanes. When a TitledPane is expanded, there are "dead areas" on the pane that do not have sub-components (e.g., buttons, text, etc.).
Right now, when I check MouseEvent.getSource(), it returns an instance of TitledPane for all areas. Is there a way to specifically constrain/check for a mouse-click on the "title" section of the TitledPane?
I don't think there is a public API to detect mouse click on title region, however it is possible to do that this way:
titledPane.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
EventTarget target = event.getTarget();
String clazz = "class com.sun.javafx.scene.control.skin.TitledPaneSkin$TitleRegion";
if(target.getClass().toString().equals(clazz) || // anywhere on title region except title Text
(target instanceof Node && ((Node) target).getParent().getClass().toString().equals(clazz))) // title Text
System.out.println("title was clicked");
}
});
But this method is highly discouraged as it relies on some internal implementation detail that may be subject to change.
May be it's better to think more about what you actually need. Maybe your actual requirement can be fulfilled by uisng TitledPane's public API methods and properties.
For insatnce expandedProperty()'s value gets changed every time mouse click occurs on title region (if isCollapsible() is set to true).
titledPane.expandedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
System.out.println("mouse click changed expanded from " + oldValue + " to " + newValue);
}
});
In the css reference you can find out that there is a child of the TitledPane that has the style class title. It isn't hard to guess that this part is the title. You can go from the pick result up through the scene graph until you either find a node with the style class title or until you reach the Accordion.
The following code colors the rect below the Accordion green, iff the mouse is on a title and red otherwise:
#Override
public void start(Stage primaryStage) {
TitledPane tp1 = new TitledPane("foo", new Rectangle(100, 100));
TitledPane tp2 = new TitledPane("bar", new Circle(100));
Accordion accordion = new Accordion(tp1, tp2);
Rectangle rect = new Rectangle(100, 20, Color.RED);
accordion.addEventFilter(MouseEvent.MOUSE_MOVED, evt -> {
Node n = evt.getPickResult().getIntersectedNode();
boolean title = false;
while (n != accordion) {
if (n.getStyleClass().contains("title")) {
// we're in the title
title = true;
break;
}
n = n.getParent();
}
rect.setFill(title ? Color.LIME : Color.RED);
});
Scene scene = new Scene(new VBox(accordion, rect), 100, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
Note that with an event filter for MouseEvent.MOUSE_CLICKED you could simply consume the event, if the pick result is not in a title...