I'm new to JavaFX. Unlike Swing, JavaFX's combo box's action event seems to be fired when the selection actually changed. In Swing, you can add an ActionListener on a JComboBox and it will fire an event whenever you make a selction (by clicking on one of the choices in the combo box), regardless of if the selected value actually changed. Can we achieve the same behavior in JavaFX? Some code below. What I want is to select "Hello" and have it printed, and select "Hello" again and have it printed again.
public class ComboBoxSelection extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
VBox layout = new VBox();
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().addAll("Hello", "World");
comboBox.setOnAction(event -> System.out.println("Selected " + comboBox.getValue()));
layout.getChildren().addAll(comboBox);
Scene scene = new Scene(layout);
primaryStage.setScene(scene);
primaryStage.show();
}
}
I noticed a thread here: ComboBox SAME item selected action listener. This almost gives me what I want, except this fires when the selection cancels (press Esc) as well. Is there any other solution? Thanks in advance.
Related
I use a JavaFX Popup in my application to change the Shipping Address of a customer. That often requires copy pasting the Address from the web browser. If I click on the Popup after the application lost focus it will not regain focus. I first need to click the parent window behind the popup and then click the Popup again to be able to interact with it.
Is there any way to solve that problem, besides using an undecorated stage instead of a Popup?
EDIT:
Here I made a minimal example:
public class HelloApplication extends Application {
#Override
public void start(Stage stage) throws IOException {
Scene scene = new Scene(new VBox(), 600, 600);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
Popup popup = new Popup();
Button btn = new Button("This is a very nice button!");
btn.setOnAction(actionEvent -> System.out.println("button clicked"));
TextField textField = new TextField();
textField.setOnMouseClicked(mouseEvent -> System.out.println("textField clicked"));
VBox popupVBox = new VBox(btn, textField);
popupVBox.setStyle("-fx-background-color: white");
popupVBox.setPadding(new Insets(10));
popup.getContent().add(popupVBox);
popup.show(scene.getWindow());
}
public static void main(String[] args) {
launch();
}
}
If you click outside of the application (for example the web browser, to copy some value) then you can not enter Text into the TextField anymore. The button action listener and even the TextField mouse Listener still work and output "button clicked" and "textField clicked" but the blue halo around them does not apppear and you can not enter (or paste) text into the TextField. After you click somewhere on the main Stage of the application it works again.
I tried adding textField.requestFocus();
to the mouse Listener of the textfield but that does not do anything.
EDIT 2 after the comment from kleopatra:
I tried adding the following code:
popupVBox.setOnMouseEntered(mouseEvent -> {
popup.getOwnerWindow().requestFocus();
parentVBox.toFront(); //This is created above and passed to the new Scene instead of diectly passing new VBox() as in the code snippet posted above
});
With that code added, popup and its children seem to have focus if the mouse is hoverd over it after an other application had focus, i.e. if clicked with the mouse the blue border appears around the button and textField, and if there is any text in the textField I can move the cursor with the mouse and select the text with the mouse, buy any keyboard inputs are ignored. Actually they are executed in the other application which had focus before. If I ctrl+c in Intellij and try to ctrl+v into the textfield it will be pasted to the Intellij editor. Also the arrow keys move the cursor in the intellij window instead of the textField.
The only way to be able to type anything in the TextField is to click the parent window first.
Also
textField.setOnMouseClicked(mouseEvent -> {
textField.requestFocus();
});
does not do anything
not an answer, just to clarify the comment (will delete if seen by OP :)
public class PopupFocus extends Application {
#Override
public void start(Stage stage) throws IOException {
Scene scene = new Scene(new VBox(), 300, 300);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.setX(10);
stage.show();
Popup popup = new Popup();
Button btn = new Button("This is a very nice button!");
btn.setOnAction(actionEvent -> System.out.println("button clicked"));
TextField textField = new TextField();
textField.setOnMouseClicked(mouseEvent -> {
System.out.println("textField clicked");
stage.toFront();
});
VBox popupVBox = new VBox(btn, textField);
popupVBox.setStyle("-fx-background-color: white");
popupVBox.setPadding(new Insets(10));
popup.getContent().add(popupVBox);
// original
popup.show(scene.getWindow());
}
public static void main(String[] args) {
launch();
}
}
When embedding a Menu in a Swing window through a JFXPanel, I cannot close the menu by clicking on it. Sometimes it blinks, as if it closed and immediately reopened.
package testjavafx;
public class TestMenuJavaFX extends Application {
#Override
public void start(Stage primaryStage) {
MenuBar menuBar = new MenuBar(
new Menu("Menu 1", null,
new MenuItem("Menu item 1-1"),
new MenuItem("Menu item 1-2")),
new Menu("Menu 2", null,
new MenuItem("Menu item 2-1"),
new MenuItem("Menu item 2-2")),
new Menu("Menu 3", null,
new MenuItem("Menu item 3-1"),
new MenuItem("Menu item 3-1")));
menuBar.setPrefWidth(300);
Region root = new Pane(menuBar);
root.setPrefSize(300, 185);
useJFXPanel(root);
//usePrimaryStage(primaryStage, root);
}
private static void useJFXPanel(Region root) {
JFXPanel jfxPanel = new JFXPanel();
jfxPanel.setScene(new Scene(root));
JFrame jFrame = new JFrame("test menu JavaFX");
jFrame.setSize((int) root.getWidth(), (int) root.getHeight());
jFrame.add(jfxPanel);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
}
private static void usePrimaryStage(Stage primaryStage, Parent root) {
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(TestMenuJavaFX.class, args);
}
}
By using the method usePrimaryStage I get the expected behavior (click on the menu to open it, click again to close it), but with useJFXPanel the problem appears.
This is an event handling issue, the mouse click is first dispatched to the JFXPanel as a Swing mouse event then the JFXPanel internally dispatches a JavaFX mouse event to its embedded Scene.
It appears that, during the Swing part, the menu loses focus and closes, and when the event reaches the Menu instance it finds it closed and therefore opens it.
I tried to inherit the Menu class to add a mouse click event handler to it, however it does not handle mouse clicks, and using the showing/shown and hiding/hidden events provided did not help it (because the problem happens earlier).
I also tried to subclass MenuBar to add a mouse click event handler, but the handler is only called when clicking on the bar outside of a menu, so no luck here, and to subclass JFXPanel to override processMouseEvent and retrieve the MenuBarButton instance through reflection black magic but i couldn't make it work.
This is a bug, right? And is there a (easy and clean, ideally) workaround to this issue?
I'm using OpenJDK 11.0.10.9 and JavaFX 17.0.0.1.
On my system, this wouldn't run, but hung on startup because the JFrame is created and shown on the wrong thread. Correcting that did display the behavior you describe, which does appear to be a bug.
I found one workaround, which is to capture a ON_HIDING event and schedule a call to hide the menu further down the event queue, using Platform.runLater(...). The resulting code looks like;
public class TestMenuJavaFX extends Application {
#Override
public void start(Stage primaryStage) {
MenuBar menuBar = new MenuBar(
new Menu("Menu 1", null,
new MenuItem("Menu item 1-1"),
new MenuItem("Menu item 1-2")),
new Menu("Menu 2", null,
new MenuItem("Menu item 2-1"),
new MenuItem("Menu item 2-2")),
new Menu("Menu 3", null,
new MenuItem("Menu item 3-1"),
new MenuItem("Menu item 3-1")));
menuBar.setPrefWidth(300);
menuBar.getMenus().forEach(menu -> {
menu.addEventHandler(Menu.ON_HIDING, e -> {
Platform.runLater(menu::hide);
});
});
Region root = new Pane(menuBar);
root.setPrefSize(300, 185);
useJFXPanel(root);
//usePrimaryStage(primaryStage, root);
}
private static void useJFXPanel(Region root) {
JFXPanel jfxPanel = new JFXPanel();
jfxPanel.setScene(new Scene(root));
SwingUtilities.invokeLater(() -> {
JFrame jFrame = new JFrame("test menu JavaFX");
jFrame.setSize((int) root.getWidth(), (int) root.getHeight());
jFrame.add(jfxPanel);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
});
}
private static void usePrimaryStage(Stage primaryStage, Parent root) {
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(TestMenuJavaFX.class, args);
}
}
My controller class has a moveButton method that on button click moves the button to a new location. This works fine and is called by a number of buttons which do the same thing. I want to add a key listener so when a button has been clicked once, until a different button is clicked, the user can use the up arrow to move the button (ie call the same moveButton function). The below is how I have tried to implement it, I also tried putting the key listener in the initialize method but neither seem to be working. Any advice would be greatly appreciated!
public void moveButton(ActionEvent actionEvent) {
Button buttonPressed = (Button) actionEvent.getSource();
double newAnchor = getNewAnchor(AnchorPane.getBottomAnchor(buttonPressed)) // separate method that returns new anchor location
AnchorPane.setBottomAnchor(buttonPressed, newAnchor);
buttonPressed.getScene().setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if(event.getCode() == KeyCode.UP){
moveButton(actionEvent);
}
}
});
}
Don't treat the events like data that you need to pass around. Use them as triggers to do work. Generally, don't write generic event handlers that are called from multiple events and multiple nodes. Write short event handlers that just call methods to do something, and pass them the minimum from the event that they need to do the job.
If you do this, then it changes your thinking about how all of this stuff works and then it's just plain old Java, with no magic. And it's simple:
public class MoveButton extends Application {
private Node activeButton;
private Pane pane;
#Override
public void start(Stage primaryStage) throws Exception {
pane = new Pane();
Scene scene = new Scene(pane, 1200, 800);
primaryStage.setScene(scene);
primaryStage.show();
Button button1 = new Button("Button 1");
Button button2 = new Button("Button 2");
button2.setTranslateX(80);
button1.setOnAction(evt -> buttonClick(button1));
button2.setOnAction(evt -> buttonClick(button2));
pane.getChildren().addAll(button1, button2);
pane.setOnKeyPressed(evt -> moveButton(evt.getCode()));
}
private void moveButton(KeyCode keyCode) {
switch (keyCode) {
case UP -> activeButton.setTranslateY(activeButton.getTranslateY() - 30);
case RIGHT -> activeButton.setTranslateX(activeButton.getTranslateX() + 30);
case DOWN -> activeButton.setTranslateY(activeButton.getTranslateY() + 30);
case LEFT -> activeButton.setTranslateX(activeButton.getTranslateX() - 30);
}
}
private void buttonClick(Node button) {
activeButton = button;
pane.requestFocus();
}
}
I'm working with ComboBox control and I want to do something easy with it. I want ComboBox to fire an ActionEvent when its value is changed during mouse click on the ComboBox dropdown list (This is automatically done). In the opposite side, I want ComboBox Not to fire ActionEvent when its value is changed programmatically (e.g. when using comboBox.getSelectionModel().selectFirst()).
Here is a simple code to demonstrate the problem:
#Override
public void start(Stage primaryStage) {
VBox vBox = new VBox();
ComboBox<String> comboBox = new ComboBox<>();
comboBox.setItems(FXCollections.observableArrayList("John", "Josh", "Mosh"));
comboBox.setOnAction(event -> {
System.out.println("Action");
});
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
return null;
}
};
task.setOnSucceeded(e -> comboBox.getSelectionModel().select("John"));
new Thread(task).start();
vBox.getChildren().addAll(comboBox);
vBox.setPrefWidth(200);
vBox.setPrefHeight(200);
vBox.setAlignment(Pos.CENTER);
Scene scene = new Scene(vBox);
Stage stage = new Stage();
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
As you can see, ComboBox has a setOnAction method which should be invoked only when ComboBox value is changed by a mouse click on the dropdown list. Also, there is a Task that does some operations. (Those operations are omitted for code simplicity reasons). After the Task is completed successfully, the ComboBox's value changes and setOnAction method is invoked too, while the value should be changed without invoking setOnAction method. I don't know how to achieve this. Any useful suggestions or tips are greatly appreciated.
I have successfully used Slaw's approach to suppress event firing across multiple controls at once -
set up a boolean field
private boolean programmedAction = false;
in the programmatic method updating the control, set the flag first to suppress actions
private void someMethod(){
programmedAction = true;
// manipulate controls
programmedAction = false;
}
in the control related events, check the boolean before firing
private void someControlsAction(ActionEvent actionEvent) {
if (programmedAction) return;
// do regular action stuff
}
Firstly, sorry for my English. I hope, you unterstand it.
Last Month I started developing a little programm with FXML/ JavaFX.
I have two screens, and I'm switching between them. This is not the problem.
The problem is: On one Screen i have a listview, where i can choose an item. When i have chosen an item, i want to open a new tab on the other screen with the content of the item. Therefore, i have to click on a button.
When i have chosen an item, i can print out the selected item, but how do I open a new tab on the other screen. Both Screens a two different FXML and are activited from the controller. How can I add a Tab, although loading fxml?
public class Controller{
#FXML
private ListView<String> lv;
#FXML
private Button check;
#FXML
public void projectview (Event e3) throws Exception{
Stage stage = null;
Parent root = null;
if(e3.getSource()==check){
//Check is the declaration for the Button on the screen with the listview
String projectview= lv.getSelectionModel().getSelectedItem();
stage = (Stage) check.getScene().getWindow();
root = FXMLLoader.load(getClass().getResource("FXML1.fxml"));
//Here I want to add a tab in FXML1.fxml
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
}
}
If something is missing or not clear, please ask. I read other similar questions, but i don't know, what to do.
Thanks for help