How can I disable a JavaFX button while something is processing (to prevent user from spamming the button)?
primaryStage.setTitle("Update Stuff");
Label lbl = new Label();
lbl.setText("Nothing here yet");
Button btn = new Button();
btn.setText("Update");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
btn.setDisable(true); //I tried this, but to no avail
lbl.setText(getNumberOfViewers()); // this might take a few seconds, need to disable button during this time
btn.setDisable(false);
}
});
VBox root = new VBox();
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(btn, lbl);
primaryStage.setScene(new Scene(root, 200, 100));
primaryStage.show();
Based off of your code you posted, it would be something like this in javafx 8:
primaryStage.setTitle("Update Stuff");
Label lbl = new Label();
lbl.setText("Nothing here yet");
Button btn = new Button();
btn.setText("Update");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
btn.setDisable(true);
new Thread(()->{
final String numberOfViews = getNumberOfViewers();
Platform.runLater(()->{
lbl.setText(numberOfViews);
btn.setDisable(false);
});
}).start();
lbl.setText(getNumberOfViewers()); // this might take a few seconds, need to disable button during this time
}
});
VBox root = new VBox();
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(btn, lbl);
primaryStage.setScene(new Scene(root, 200, 100));
primaryStage.show();
The new thread will make it so that the UI thread is never actually blocked (and windows doesn't give you that nasty 'the application has stopped responding' business), and then inside the thread, anytime you want to change something on the GUI, use the Platform.runLater() syntax, technically you are making a new 'Runnable' object inside of the runLater method, but thanks to lambda expressions, you don't need to worry about that! If you don't use the Platform.runLater() you'll get a 'not on the Javafx application Thread' exception when you try to update stuff.
Also worth noting, I think in javafx 2.* (java 7) it's btn.setEnable(false) instead of btn.setDisable(true) just a change of how they went about it. same effect.
use the f.f.g code
btn.setEnabled(false);
within the handle method, then after:
btn.setEnabled(true);
Related
I can't figure out how to create a modal window in JavaFX. Basically I have file chooser and I want to ask the user a question when they select a file. I need this information in order to parse the file, so the execution needs to wait for the answer.
I've seen this question but I've not been able to find out how to implement this behavior.
In my opinion this is not good solution, because parent window is all time active.
For example if You want open window as modal after click button...
private void clickShow(ActionEvent event) {
Stage stage = new Stage();
Parent root = FXMLLoader.load(
YourClassController.class.getResource("YourClass.fxml"));
stage.setScene(new Scene(root));
stage.setTitle("My modal window");
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(
((Node)event.getSource()).getScene().getWindow() );
stage.show();
}
Now Your new window is REALY modal - parent is block.
also You can use
Modality.APPLICATION_MODAL
Here is link to a solution I created earlier for modal dialogs in JavaFX 2.1
The solution creates a modal stage on top of the current stage and takes action on the dialog results via event handlers for the dialog controls.
JavaFX 8+
The prior linked solution uses a dated event handler approach to take action after a dialog was dismissed. That approach was valid for pre-JavaFX 2.2 implementations. For JavaFX 8+ there is no need for event handers, instead, use the new Stage showAndWait() method. For example:
Stage dialog = new Stage();
// populate dialog with controls.
...
dialog.initOwner(parentStage);
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.showAndWait();
// process result of dialog operation.
...
Note that, in order for things to work as expected, it is important to initialize the owner of the Stage and to initialize the modality of the Stage to either WINDOW_MODAL or APPLICATION_MODAL.
There are some high quality standard UI dialogs in JavaFX 8 and ControlsFX, if they fit your requirements, I advise using those rather than developing your own. Those in-built JavaFX Dialog and Alert classes also have initOwner and initModality and showAndWait methods, so that you can set the modality for them as you wish (note that, by default, the in-built dialogs are application modal).
You can create application like my sample. This is only single file JavaFX application.
public class JavaFXApplication1 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) {
Stage stage;
stage = new Stage();
final SwingNode swingNode = new SwingNode();
createSwingContent(swingNode);
StackPane pane = new StackPane();
pane.getChildren().add(swingNode);
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle("Swing in JavaFX");
stage.setScene(new Scene(pane, 250, 150));
stage.show();
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
private void createSwingContent(final SwingNode swingNode) {
SwingUtilities.invokeLater(() -> {
try {
Path currentRelativePath = Paths.get("");
String s = currentRelativePath.toAbsolutePath().toString();
JasperDesign jasperDesign = JRXmlLoader.load(s + "/src/reports/report1.jrxml");
String query = "SELECT * FROM `accounttype`";
JRDesignQuery jrquery = new JRDesignQuery();
jrquery.setText(query);
jasperDesign.setQuery(jrquery);
JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign);
JasperPrint JasperPrint = JasperFillManager.fillReport(jasperReport, null, c);
//JRViewer viewer = new JRViewer(JasperPrint);
swingNode.setContent(new JRViewer(JasperPrint));
} catch (JRException ex) {
Logger.getLogger(AccountTypeController.class.getName()).log(Level.SEVERE, null, ex);
}
});
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
I have two buttons: buttonPoint1 and buttonPoint2. When I click one of the buttons it calls action method for this button. In this function I want program to wait for me to click wherever on a screen. Then I'll call MouseInfo.getPointerInfo().getLocation(). When I click outside my app window (e.g on other window), I lose focus on my app. How to make program to wait for click, don't hide, lose focus and give me response that I clicked?
One idea is using a transparent window like so
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
VBox vBox = new VBox(new Label("IMAGINE A BEAUTIFUL APPLICATION HERE"));
vBox.setAlignment(Pos.CENTER);
Button button = new Button("Im gonna steal your next click");
button.setOnAction(event -> {
System.out.println("Firing Button Action");
openTransparentWindow();
System.out.println("Button Action Finished Firing");
});
vBox.getChildren().add(button);
Scene scene = new Scene(vBox, 300, 150);
primaryStage.setScene(scene);
primaryStage.show();
}
private void openTransparentWindow(){
VBox vBox = new VBox();
Stage stage = new Stage();
stage.setScene(new Scene(vBox));
//stage.setOpacity(0.5f); //Uncomment if you want to test and see the stage
stage.setOpacity(0f);
stage.setMaximized(true);
vBox.setOnMouseClicked(event ->{
System.out.println("event.getX() = " + event.getX());
System.out.println("event.getY() = " + event.getY());
System.out.println("event.getScreenX() = " + event.getScreenX());
System.out.println("event.getScreenY() = " + event.getScreenY());
stage.close();
});
stage.showAndWait();
}
}
I tried a lot but just couldn't find any solution. At the moment the opened window(popup window) is always on top but the user can still access the main window. That's how it should be, but it shouldn't be possible to open the same popup window again.
Stage stage = new Stage();
stage.setTitle(panelTitle);
stage.setScene(new Scene(root));
stage.initModality(Modality.WINDOW_MODAL);
stage.setAlwaysOnTop(true);
stage.showAndWait();
Thank you in advance!
As LazerBanana said, I would disable the button that opens the window, and I would enable it when you close it.
Stage stage = new Stage();
button.setDisable(true);
stage.setTitle(panelTitle);
stage.setScene(new Scene(root));
stage.initModality(Modality.WINDOW_MODAL);
stage.setAlwaysOnTop(true);
stage.showAndWait();
// your logic here
button.setDisable(false);
An alternative solution to creating a new one each time is to create one and just setup and show.
public class Stack extends Application {
private final Stage popup = new Stage();
#Override
public void start(Stage stage) throws Exception {
BorderPane root = new BorderPane();
root.setPrefWidth(400);
root.setPrefHeight(200);
Button button = new Button("ClickMePopup");
root.setCenter(button);
button.setOnAction(
event -> {
if (!popup.isShowing()) {
// you dont set modality because after the stage is set to visible second time it will throw an exception.
// Again depends on what you need.
// popup.initModality(Modality.WINDOW_MODAL);
// this focuses the popup and main window is not clickable
// popup.initOwner(stage);
VBox dialogVbox = new VBox(20);
dialogVbox.getChildren().add(new Text("Some Dialog"));
Scene dialogScene = new Scene(dialogVbox, 300, 200);
popup.setScene(dialogScene);
// you can actually put all above into the method called initPopup() or whatever, do it once, and just show it here or just bind the property to the button.
popup.show();
}
});
Scene scene = new Scene(root);
stage.setTitle("Stack");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Or disable the button when clicked, but if your popup is not driven by the button or can be opened from other places the first idea would be a bit better in my opinion. Depends on what you need.
Or just create your own class and Springify it.
I've been struggling for weeks to resolve the memory leaks within our JavaFX application, and thought today I was at a complete loss with it so decided to write the simplest application application I could think of to prove JavaFX could in fact release memory and therefore proving I was doing something wrong. To my surprise this seems to leak memory too.
Can anyone advise why the following application still has a javafx.scene.control.Label in the heap after the button is clicked? The way I checked it was there was with jprofiler.
public class MemoryTestApplication extends Application {
#Override
public void start(Stage primaryStage) {
//root pane
final VBox root = new VBox();
//label to remove when button is clicked
final Label label = new Label("Remove Me");
root.getChildren().add(label);
//button to remove label
Button btn = new Button("Remove label");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
root.getChildren().remove(label);
}
});
root.getChildren().add(btn);
//create scene and stage
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Your anonymous inner class for the event handler is holding a reference to the label, and your button is holding a reference to the event handler.
The anonymous inner class is going to have two synthetically generated fields, created by the compiler, like:
final Label val$label;
final MemoryTestApplication this$0;
Since these are final, you can't clear label after using it by label = null;, so I think your best bet is to change the event handler to a normal, named class, pass the label reference in to its constructor, and then clear it after using it to remove the label from the layout. (You would want to test that it was not null before removing, so that it would only be removed the first time the button is pressed).
In JavaFx, I want to show a modal dialog after an animation ends. For some reason, calling showAndWait in the EventHandler that gets executed after the animation ends doesn't work. A new window is shown, but it seems like nothing is drawn inside it.
This example demonstrates the issue:
public void start(Stage primaryStage) {
Rectangle rect = RectangleBuilder.create().width(200).height(200).fill(Color.RED).build();
StackPane root = new StackPane();
root.getChildren().add(rect);
Timeline animation = new Timeline();
animation.getKeyFrames().addAll(
new KeyFrame(new Duration(1000),
new KeyValue(rect.widthProperty(), 100),
new KeyValue(rect.heightProperty(), 100)),
new KeyFrame(new Duration(2000),
new KeyValue(rect.widthProperty(), 300),
new KeyValue(rect.heightProperty(), 300))
);
animation.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
Stage stage = new Stage();
StackPane pane = new StackPane();
pane.getChildren().add(new Label("Hello world"));
stage.setScene(new Scene(pane, 100, 100));
stage.showAndWait();
}
});
animation.setCycleCount(1);
Scene scene = new Scene(root, 300, 300);
primaryStage.setScene(scene);
primaryStage.show();
animation.play();
}
The full code can be found at https://gist.github.com/bmesuere/9605866
I'd like to know why this doesn't work (on my macbook using java 1.7.0_51) and get suggestions for a workaround.
It seems to work if you wrap the code to show the stage in a Platform.runLater():
animation.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
Platform.runLater(new Runnable() {
#Override
public void run() {
Stage stage = new Stage();
StackPane pane = new StackPane();
pane.getChildren().add(new Label("Hello world"));
stage.setScene(new Scene(pane, 100, 100));
stage.showAndWait();
}
});
}
});
No idea why. It fails on Java FX 8 too.
I have reported this as a bug in JavaFX (Jira issue).
A comment from that issue:
The problem is that no subsequent pulse is scheduled when showAndWait() is called while pulse is being processed.
This was 'fixed' in Java 8u20. An exception is now thrown. Changes can be seen here.