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
}
Related
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 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.
I have written a controller for two windows /stages.
The first window is opened in the MainClass. The second in the Controller, if the user clicks onto a button.
How can I get the TextFields from second.fxml in the applyFor()-method?
Thanks.
#FXML
protected void requestNewAccount(ActionEvent event) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("second.fxml")); // TextFields in there
Parent root = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle("Second Window");
Scene scene = new Scene(root);
String css = MainOnlineCustomer.class.getResource("/style.css").toExternalForm();
scene.getStylesheets().clear();
scene.getStylesheets().add(css);
stage.setScene(scene);
stage.show();
} catch (IOException e) {
logger.error(e);
}
}
/**
* closes the "second"-Window
* #param event
*/
#FXML
protected void cancel(ActionEvent event) {
final Node source = (Node) event.getSource();
final Stage stage = (Stage) source.getScene().getWindow();
stage.close();
}
#FXML
protected void applyFor(ActionEvent event) {
// get values from TextField in second.fxml here!!!
}
It's not good to share controllers between fxmls unless they serve the same purpose. Here both fxml seem to serve a different purpose (account management, login or something similar for one of them and creating a new account for the other). What is even worse is that those classes do not share the same controller instance, which means the small (and probably only) benefit you could get from using the same controller, is not used here. You should better use different controllers.
Since you use Modality.APPLICATION_MODAL as modality, I'd recommend using showAndWait instead of show to open the new stage. This will enter a nested event loop, which allows the UI to remain responsive and continues after the invocation of showAndWait once the stage is closed.
Furthermore add a method to the controller of second.fxml that allows you to retrieve the result.
Example
This creates a Person object with given name and family name.
"primary window (opening the "inner" stage)
FXMLLoader loader = new FXMLLoader(getClass().getResource("second.fxml"));
Stage subStage = new Stage();
subStage.initModality(Modality.APPLICATION_MODAL);
subStage.setTitle("Second Window");
Scene scene = new Scene(loader.load());
subStage.setScene(scene);
subStage.showAndWait();
Optional<Person> result = loader.<Supplier<Optional<Person>>>getController().get();
if (result.isPresent()) {
// do something with the result
}
controller for "inner" content
public class SecondController implements Supplier<Optional<Person>> {
#FXML
private TextField givenName;
#FXML
private TextField familyName;
private boolean submitted = false;
// handler for submit action
#FXML
private void submit() {
submitted = true;
givenName.getScene().getWindow().hide();
}
// handler for cancel action
#FXML
private void cancel() {
givenName.getScene().getWindow().hide();
}
#Override
public Optional<Person> get() {
return submitted ? Optional.of(new Person(givenName.getText(), familyName.getText())) : Optional.empty();
}
}
Note that you can gain access to any data available to the controller this way. I wouldn't recommend accessing any nodes (like TextFields) directly though, since this makes changing the UI harder.
Using the Supplier interface here is not necessary, but I chose to do this to achieve a loose coupling between SecondController and the main window.
I'm writing a program in netbeans with javaFX
The view has several buttons in it with some bad buttons(like bombs is minesweeper), I'm trying to freeze the program when a bad button is pushed but i don't find how to do it
thanks!
There are various solutions to your problem. 2 among them are simply ignoring the action event or disabling the buttons like this:
public class ButtonAction extends Application {
final BooleanProperty buttonActionProperty = new SimpleBooleanProperty();
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
FlowPane root = new FlowPane();
CheckBox checkBox = new CheckBox( "Enabled");
checkBox.setSelected(true);
// solution 1: check if action is allowed and process it or not
buttonActionProperty.bind( checkBox.selectedProperty());
Button button = new Button( "Click Me");
button.setOnAction(e -> {
if( buttonActionProperty.get()) {
System.out.println( "Allowed, processing action");
} else {
System.out.println( "Not allowed, no action");
}
});
// solution 2: remove comments to activate the code
// button.disableProperty().bind(buttonActionProperty.not());
root.getChildren().addAll(checkBox, button);
primaryStage.setScene(new Scene(root, 600, 200));
primaryStage.show();
}
}
Add a ROOT typed event filter that consumes all kind of events (mouse, keyboard etc.)
btnThatHasHiddenMine.setOnAction(( ActionEvent event ) ->
{
System.out.println("Ohh no! You just stepped over the mine!");
getGameboardPane().addEventFilter( EventType.ROOT, Event::consume );
});
Add the filter to your GameboardPane only, since we don't want to freeze other part of the app.
I'm trying to set a variable value when a MenuItem i chosen in a MenuButton object.
I've tried to search for this but I've came up empty handed.
Here's the code to set the MenuItems:
private ObservableList<MenuItem> templateMenuItems = FXCollections.observableArrayList();
#FXML private MenuButton menu = new MenuButton();
#FXML
protected void getTemplates() throws IOException {
CaspReturn tls = this.socket.runCmd(new Tls(""));
String tlsList = tls.getResponse();
String[] tlsListSplitt = tlsList.split("\\n");
for (int i = 0; i < tlsListSplitt.length; i++) {
String[] tlsLine = tlsListSplitt[i].split("\"");
this.templateMenuItems.add(new MenuItem(tlsLine[1]));
}
this.menu.getItems().setAll(this.templateMenuItems);
}
I'm not sure how to write the code to get the text from a menuItem or which field in scenebuilder the method should be in.
It's not clear what your asking, but I'll assume that you want to know the text of a menu item when it is clicked. To do that, you need to add an event handler onto the menu item. The following is clipped from the JavaDoc for ContextMenu:
MenuItem item1 = new MenuItem("About");
item1.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("About");
}
});
You can get the event source, cast in to MenuItem and then get the text from that.
There's a real problem with your code the way it's written, though. You have calls to an external database in getTemplates, and as it's implemented as an #FXML element that almost guarantees that it'll be run on the FXAT, which is really, really bad.
I'd refactor that so that the database access is in a Task, and then the MenuItem creation is a handler for the onSucceeded event of the Task. Then you need instantiate a ContextMenu and install the MenuItem's on it in that event handler.
The getTemplates() method should be called as the onAction event for the button.