I posted a question similar to this earlier but I wasn't specific enough. Here is a simplified version of my code:
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.scene.control.ProgressBar;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.layout.HBox;
import javafx.scene.Scene;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.control.Button;
import javafx.scene.control.MenuBar;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Separator;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.geometry.Pos;
import java.net.URL;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.control.Slider;
public class ProgressTest extends Application {
boolean play = true;
int x = 0;
#Override
public void start(Stage stage) {
GridPane pane = new GridPane(); //pane and Hboxes
HBox hbox = new HBox();
Button update = new Button("Start");
update.setOnAction( e -> {
while(play == true)
{
System.out.println(++x);
}
});
Button pause = new Button("Pause");
pause.setOnAction( e -> {
if(play == true)
{
pause.setText("Play");
play = false;
}
else
{
pause.setText("Pause");
play = true;
}
});
hbox.getChildren().addAll(update, pause);
pane.add(hbox, 0, 1);
Scene scene = new Scene(pane);
stage.setMaxWidth(655);
stage.setMaxHeight(620);
stage.setTitle("Gallery!");
stage.setScene(scene);
stage.sizeToScene();
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The idea with this code is that, when the user clicks the "Start" button, the program should print out x going up, and pause whenever the user hits "Pause", and resume again when the user hits "Play".
The problem is that whenever I click the "Play" button, the program goes into an infinite loop and I am unable to press the pause button to stop it. Is there something wrong with the way I am going about this? Are there any tricks to getting this to work? Any help would be very appreciated.
Also, I know that some of this may have syntax errors but I know it's correct on my copy of the code, I'm more considered with the logic behind how to get this to work.
There are 2 big problems with your implementation:
The GUI runs on the JavaFX thread. For it to remain responsive any operations in it must finish quickly. When you are running a long computation like your loop on this thread the whole GUI is blocked.
After you manage to set play to false the loop will just exit and clicking on resume will do nothing.
There are several ways to approach this. I will demonstrate one using Thread and CountDownLatch.
I can't explain what a thread is in the scope of the question (tons of material on SO and everywhere), but what is relevant here is that executing the costly operation on a thread which is not the JavaFX thread will solve point 1.
A CountDownLatch is used to block a thread's (or more than one) execution until the latch is released/broken, upon which the thread will continue. It is initialized with an int representing the number of times it needs to count down before it is released. A thread reaching the latch's await method blocks until the latch's countDown method is called the specified amount of times.
Here is a sample code:
import java.util.concurrent.CountDownLatch;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class ProgressTest extends Application {
volatile CountDownLatch cl = new CountDownLatch(1);
#Override
public void start(Stage stage) {
Thread thread = new Thread(() -> {
int x = 0;
while (true) {
try {
cl.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println(++x);
}
});
thread.setDaemon(true);
Button update = new Button("Start");
update.setOnAction(e -> {
if (!thread.isAlive()) {
cl.countDown();
thread.start();
}
});
BooleanProperty running = new SimpleBooleanProperty(true);
Button pause = new Button("Pause");
pause.textProperty().bind(Bindings.when(running).then("Pause").otherwise("Play"));
pause.setOnAction(e -> {
if (running.get()) {
cl = new CountDownLatch(1);
}
else {
cl.countDown();
}
running.set(!running.get());
});
HBox hbox = new HBox(update, pause);
Scene scene = new Scene(hbox);
stage.setScene(scene);
stage.setTitle("Gallery!");
stage.sizeToScene();
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The thread spits out numbers "as fast as possible" with the while(true) loop as you did, only it can be paused by reaching the await method. When you press Start the latch is broken and the thread starts and executes continuously. When you press Pause a new latch is created in its place (a latch is a 1-time thing, it can't be reset), which causes the thread to wait in await until someone breaks it with the countDown method (one call is enough because we instantiated it with 1). That is what pressing Resume does.
Calling setDaemon(true) on the thread makes sure it allows the JVM to exit without waiting for it. If the thread must finish before the JVM exists (e.g., it is not a background thread), you can remove it.
Iv'e made the latch volatile which guarantees that different threads will see the same value for it. See also Do you ever use the volatile keyword in Java? and other available sources. In this specific case you don't need poinpoint thread synchronization so it won't have a noticeable effect, but it should be there nonetheless.
Note that I've added a small check on Start that the thread is not already running because starting a thread while it's running throws an exception. You didn't specify what to do if Start is pressed during execution.
While irrelevant to your question, Iv'e demonstrated how you can utilize the JavaFX binding API to have the button's text update automatically with the boolean's value. I "promoted" the control boolean to a property and bound the button's text to its value. It might not be so useful for this situation though.
Notes:
You're setting the height and width of the scene and then call sizeToScene, which makes the previous calls redundant.
You don't need to check a boolean with ==, you can use it directly: if (b == true) is equivalent to if (b) and if (b == false) is equivalent to if (!b).
A better name for your boolean would be one that represents a state ("running"/"paused") rather than an action ("run"/"pause").
Related
I am trying to build a JavaFX application, where I have a button named "Start" and an ImageView. With the robot class of JavaFX-12, I am trying to take a screenshot of the laptop screen when the button is clicked and show the images one by one during the runtime in the ImageView. My problem is that the JavaFX window does not respond and the program crashes (probably). Even putting the thread into sleep does not seem to work. I assume that it isn't working as I have not set any fps rule, but how can I do that? At the moment, I am creating writable images, converting them into a separate image with a number, saving them, and again reusing them. My goal is to create a screen sharing of the same laptop in the image view. I know that's difficult. I'm new to the JavaFx robot class (not he awt one). Any help is appreciated.
P.S.: The images are properly formed in the directory.
package sample;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Rectangle2D;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.VBox;
import javafx.scene.robot.Robot;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
ImageView iv = new ImageView();
iv.setFitWidth(100);
iv.setFitHeight(100);
Button b = new Button("Start");
VBox v = new VBox(10);
v.getChildren().addAll(b,iv);
b.setOnAction(event -> {
Robot r = new Robot();
WritableImage wi = new WritableImage(300,300);
WritableImage i;
Rectangle2D rect = Screen.getPrimary().getVisualBounds();
while(true){
i = r.getScreenCapture(wi,rect);
try {
ImageIO.write(SwingFXUtils.fromFXImage(i,null),"png",new File("F:/Pic/pic" + x + ".png"));
iv.setImage(new Image(new FileInputStream("F:/Pic/pic" + x + ".png")));
//Thread.sleep(500);
//iv.setImage(null);
} catch (Exception e) {
System.out.println(e);
}
}
});
primaryStage.setScene(new Scene(v, 500, 500));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
JavaFX is, like most UI frameworks, single-threaded. You must never block (e.g. sleep) or otherwise monopolize (e.g. while (true)) the JavaFX Application Thread. That thread is responsible for everything related to the UI and if it's not free to do its job then the UI will become unresponsive. Note that a render pass cannot happen until the FX thread returns from whatever it's doing, so setting the image of an ImageView in a loop will have no visible effect until some time after the loop terminates.
Also, a full-throttle while loop will attempt to get a screen capture as fast as the CPU can execute said loop. That is likely to be much faster than the rate at which your UI refreshes and is thus a waste of resources. The rate of screen captures should not exceed the frame rate.
If you need to loop on the FX thread and/or be constrained by the (JavaFX's) frame rate then use the javafx.animation API. In your case, an AnimationTimer seems apt. Here's an example which continuously screenshots the primary screen:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.scene.robot.Robot;
import javafx.stage.Screen;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
ImageView view = new ImageView();
view.setFitWidth(720);
view.setFitHeight(480);
// Keep a reference to the AnimationTimer instance if
// you want to be able to start and stop it at will
new AnimationTimer() {
final Robot robot = new Robot();
final Rectangle2D bounds = Screen.getPrimary().getBounds();
#Override
public void handle(long now) {
WritableImage oldImage = (WritableImage) view.getImage();
WritableImage newImage = robot.getScreenCapture(oldImage, bounds);
if (oldImage != newImage) {
view.setImage(newImage);
}
}
}.start();
primaryStage.setScene(new Scene(new StackPane(view)));
primaryStage.show();
}
}
Some notes:
The AnimationTimer#handle(long) method is invoked once per frame. JavaFX tries to target 60 frames-per-second. If that's too fast (the above lags somewhat on my computer) then you can use the method's argument, which is the timestamp of the current frame in nanoseconds, to throttle the rate of screen captures. You could also look into using a Timeline or PauseTransition instead of an AnimationTimer. See JavaFX periodic background task for more information.
The above gives a Droste Effect (I think that's the term?) since the screen capture is displayed on the screen which is being captured.
My example does not include saving each image to a file. You seem to already understand how to do that so you should be able to easily adapt the code. It'd probably be a good idea, however, to move the I/O to a background thread. Unfortunately, that will likely require using different WritableImage instances for each capture to avoid the image being mutated by the FX thread while the background thread reads it. It may also require some tuning or dropped images; I'm not sure how well the I/O will keep up with the influx of screen captures (i.e. standard producer-consumer problems).
As an aside, your question explains you're attempting to share the entire screen. If that's the case then continue using Robot. However, if you only need to share something from the JavaFX application itself then consider using Node#snapshot(SnapshotParameters,WritableImage) or Scene#snapshot(WritableImage) (assuming you can't just send model data instead of images).
Would it be possible to wrap an entire JavaFX application into a while loop to trigger automatic events? For example in a auction house simulator:
package main;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
// Standard JavaFX boilerplate
primaryStage.show();
while(true){
// Get price of this item
// Update table of listings
}
}
public static void main(String[] args) {
launch(args);
}
I know that the loop would block the main thread for the GUI so I was thinking of using the system time + a few seconds in the while loop instead:
double systemTime = systemTime;
double executeTime = systemTime + 5;
while(systemTime != executeTime){
//Do things
executeTime = systemTime + 5;
}
At any rate I know what I need, I just don't know what it's called or implemented.
Well you were right this would most likely block the JavaFX thread, but so would your second statement. As its still looping blocking the thread. What you could do is use
the ScheduledExecutorService to run a Runnable or thread to periodically refresh the gui and update it with what ever information. But you should make sure to wrap the parts which change the GUI within the JavaFX Thread which you can simply do so by using Platform.runLater method. Or for more heavy duty background tasks, use the Task class for JavaFX.
I will use the runLater method for simplicity sakes.
Example:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.lang.management.PlatformManagedObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class Example extends Application {
private Scene myscene;
private TextArea exampleText;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
//JavaFX boilerplate
VBox rootVBox = new VBox();
exampleText = new TextArea();
VBox.setVgrow(exampleText, Priority.ALWAYS);
myscene = new Scene(rootVBox);
rootVBox.getChildren().add(exampleText);
//End of JavaFX boilerplate
// Scheduler to update gui periodically
ScheduledExecutorService executor =
Executors.newSingleThreadScheduledExecutor();
Random r = new Random();
Runnable addNewNumber = () -> {
Platform.runLater(()->{
System.out.println("I Just updated!!!");
String newNumber = Integer.toString(r.nextInt(100));
System.out.println("adding "+ newNumber +" to textfield ");
exampleText.appendText(newNumber+"\n");
});
};
executor.scheduleAtFixedRate(addNewNumber, 0, 500, TimeUnit.MILLISECONDS);
primaryStage.setScene(myscene);
primaryStage.show();
}
}
(James_D has showed me the solution. I'll just elaborate on how it helped me and what I did at the end of this question. Hopefully others find it useful.)
My wrong initial approach:
There are several FXML files and their controllers in my program. I loaded the FXML files and changed the scenes. It caused the window size to change when it was maximized.
Then this answer showed that I should change the root instead.
Current approach and question:
I tried the code given in the answer there and it works flawlessly. First I set a root in the start method and switched to anther root after a time delay. It's just to check. It looks like this:
package com;
import java.io.IOException;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Launcher extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
StackPane root = FXMLLoader.load(getClass().getResource("/com/Scene2.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setMaximized(true);
stage.show();
Timeline timeline2 = new Timeline(new KeyFrame(Duration.millis(1000), e2 -> {
try {
StackPane root2 = FXMLLoader.load(getClass().getResource("/com/Scene1.fxml"));
scene.setRoot(root2);
} catch (IOException e1) {
e1.printStackTrace();
}
}));
timeline2.play();
}
}
In my actual program the start method loads an FXML file. Later I click buttons or something on the screen which should change the root. So I'll have to use a similar code in controllers. But to set the root, I need to get the scene. In the start method the scene is already created and available. I can't figure out how to get that in a controller. So I tried creating a new scene in the controller. But it's not working. It doesn't throw exceptions or anything. The program simply doesn't do anything when I press the button. This is the relevent code in the controller.
public void changeRoot(ActionEvent event) throws IOException {
try {
Parent root2 = FXMLLoader.load(getClass().getResource("/com/Scene2.fxml"));
// I added the line below.
Scene scene = new Scene(root2);
scene.setRoot(root2);
} catch (IOException e1) {
e1.printStackTrace();
}
}
The solution:
Right then. As you can see from the comment below, I can get the scene by calling getScene() on any node in the current scene. In my example code I have a button on the current screen. A button is a node. So I use it to get the scene. I'll just share the code of the controller class so you know what I mean.
package com;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Scene1Controller implements Initializable {
#FXML
private Button button;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
public void changeRoot(ActionEvent event) throws IOException {
try {
Parent root2 = FXMLLoader.load(getClass().getResource("/com/Scene2.fxml"));
button.getScene().setRoot(root2); // Here I get the scene using the button and set the root.
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
Now there's something you should keep in mind. You have to make sure you give an fx:id for the node in FXML. It should match the given variable name of the node in the controller. For example, in my controller I have a Button. I've named it 'button'. I've also made sure the id for the Button in the FXML file is 'button'. It's very easy to set the fx:id. I used the Gluon Scene Builder. (Sometimes the changes I make there doesn't get updated in Eclipse right away. Refreshing the project makes sure the changes are updated in Eclipse too.)
I use JavaFX NumberBindings in order to calculate certain values. Initially everything works as expected. After a rather small amount of time, however, the binding just stops working. I don't receive an Exception, either.
I've tried several bindings, as well as high- and low-level approaches. Even the calculation itself (when overridden) just stops and isn't called anymore. I've also updated to the latest JDK (1.8.0_05) and rebuilt/restarted everything.
The following Minimal Working Example illustrates the problem. It should System.out.println the current width of the main window to STDOUT. After resizing the window for about 10 seconds, the output simply stops. I've also tried to bind the resulting property to a JavaFX control, in order to ensure the Property's continued usage, but that was of no avail. I believe I'm missing some very basic behaviour of the Property/Bindings here, Google doesn't seem to know that behaviour at all.
import javafx.application.Application;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class BindingsProblem extends Application {
#Override
public void start(Stage primaryStage) {
// Initialization...
StackPane root = new StackPane();
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
// Binding - The problem occurrs here!
NumberBinding currentWidthPlusTen = primaryStage.widthProperty().add(10);
IntegerProperty boundNumberProperty = new SimpleIntegerProperty();
boundNumberProperty.bind(currentWidthPlusTen);
boundNumberProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
System.out.println(newValue.toString());
}
});
}
public static void main(String[] args) {
launch(args);
}
}
The binding uses a WeakListener to observe the value of currentWidthPlusTen. Since you don't keep a reference to the boundNumberProperty, it is eligible for garbage collection as soon as the start(...) method exits. When the garbage collector kicks in, the reference is lost entirely and the binding no longer works.
To see this directly, add the line
root.setOnMousePressed( event -> System.gc());
to the start(...) method. You can force the listener to "stop working" by clicking on the window.
Obviously, that's not what you want: the fix is to retain the reference to boundNumberProperty after start(...) exits. For example:
import javafx.application.Application;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class BindingsProblem extends Application {
IntegerProperty boundNumberProperty;
#Override
public void start(Stage primaryStage) {
// Initialization...
StackPane root = new StackPane();
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
// Binding - The problem occurrs here!
NumberBinding currentWidthPlusTen = primaryStage.widthProperty()
.add(10);
boundNumberProperty = new SimpleIntegerProperty();
boundNumberProperty.bind(currentWidthPlusTen);
boundNumberProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
System.out.println(newValue.toString());
}
});
}
public static void main(String[] args) {
launch(args);
}
}
Update
Anyone running into this issue might also want to look at Tomas Mikula's ReactFX, which provides a cleaner workaround for this (at the expense of using a third-party library, which you would need to spend some time learning). Tomas explains this issue and how ReactFX resolves it in this blog and the subsequent post.
I created a little View and put them into a stage. I set the stage APPLICATION_MODAL:
stage.initModality(Modality.APPLICATION_MODAL);
If I click on the calling Window a blocking sound appears. So this is the right action.
But is there any posibility to replace this blocking sound, with an own function? I want to close the new stage if I click on the calling window, but I really tried all what comes to my mind, even an Eventfilter for Event.ANY doesn't work:
stage.addEventFilter(Event.ANY, new EventHandler<Event>() {
#Override
public void handle(Event event) {
System.out.println("Event catched: "+event);
}
});
Is there any way to handle the click on the calling window?
When you set something to APPLICATION_MODAL, you prevent any of the events from being dispatched to ANY window. This means that your event listener is of no use. Here is what the docs say:
APPLICATION_MODAL
Defines a modal window that blocks events from being
delivered to any other application window.
My suggestion is that you disable all your components when your custom view is visible and when a click occurs on the parent window while your view is visible, close your view. This will solve the problem of explicitly setting the modality (Anyways your events are not being passed).
SSCCE:
package stack;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFieldBuilder;
import javafx.scene.effect.Reflection;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextBuilder;
import javafx.stage.Modality;
import javafx.stage.Popup;
import javafx.stage.Stage;
import javafx.stage.StageBuilder;
public class DismissPopup extends Application {
Text hello;
Scene primaryScene;
TextField f;
Stage extraStage;
Scene extraScene;
#Override
public void start(Stage primaryStage) throws Exception {
primaryScene = SceneBuilder
.create()
.width(300)
.height(300)
.root(new StackPane())
.fill(Color.BLACK)
.build();
hello = TextBuilder
.create()
.text("Hello")
.effect(new Reflection())
.build();
f = TextFieldBuilder
.create()
.promptText("Enter Some Text")
.build();
extraScene = SceneBuilder
.create()
.width(300)
.height(300)
.root(new StackPane())
.fill(Color.WHEAT)
.build();
StackPane p = (StackPane) extraScene.getRoot();
p.getChildren().addAll(hello);
p = (StackPane) primaryScene.getRoot();
p.getChildren().addAll(f);
extraStage = StageBuilder
.create()
.scene(extraScene)
.build();
extraStage.sizeToScene();
primaryScene.setOnMouseClicked(new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent me) {
if(f.disabledProperty().get() == false){
f.setDisable(true);
f.setText("The TextField is disabled");
extraStage.show();
}else{
f.setText("The TextField is enabled");
f.setDisable(false);
extraStage.close();
}
}
});
primaryStage.setScene(primaryScene);
primaryStage.sizeToScene();
primaryStage.show();
}
public static void main(String[] args) {
Application.launch("stack.DismissPopup");
}
}
Output: