JavaFX - Progress properties don't work? - javafx

I'm trying to get the progress value of my Picture Viewer when another picture is loading. I've tried two simple ways to do it, but it didn't work out for me.
First I was using the progressProperty from the Image class:
public boolean nextImageClicked()
{
if(PICTURE_INDEX < picturePaths.size() - 1)
{
String path = picturePaths.get(PICTURE_INDEX + 1).toURI().toString();
Image newImage = new Image(path);
newImage.progressProperty().addListener((observable, oldValue, newValue) -> System.out.println("Current progress: "+newValue));
GUI.getImageView().setImage(newImage);
adjustImageViewBounds();
PICTURE_INDEX += 1;
return true;
}
else return false;
}
It didn't print out anything on the console because the progress value doesn't change bizarrely. So I've tried to put all the work stuff in a Task<Void> and getting the progress value through the Task:
public boolean nextClicked()
{
if(PICTURE_INDEX < picturePaths.size() - 1)
{
Task<Void> task = new Task<Void>()
{
#Override protected Void call() throws Exception
{
String path = picturePaths.get(PICTURE_INDEX + 1).toURI().toString();
Image newImage = new Image(path);
GUI.getImageView().setImage(newImage);
adjustImageViewBounds();
PICTURE_INDEX += 1;
return null;
}
};
task.setOnRunning(e -> System.out.println(task.getProgress()));
task.progressProperty().addListener((observable, oldValue, newValue) ->
{
System.out.println(newValue);
});
task.run();
return true;
}
else return false;
}
Also didn't work out as hoped.
task.setOnRunning(e -> System.out.println(task.getProgress()));
I implemented this to see the default value, it printed out "-1".
What have I to change to let the console return single progress values like "0.1", "0.14" ?

You need the Image to load in the background, so that the call to the constructor returns before the image is completely loaded. By default, it will block until it is loaded (so the progress property will be 1 by the time you add the listener to it):
public boolean nextImageClicked()
{
if(PICTURE_INDEX < picturePaths.size() - 1)
{
String path = picturePaths.get(PICTURE_INDEX + 1).toURI().toString();
// note additional parameter:
Image newImage = new Image(path, true);
newImage.progressProperty().addListener((observable, oldValue, newValue) -> System.out.println("Current progress: "+newValue));
GUI.getImageView().setImage(newImage);
adjustImageViewBounds();
PICTURE_INDEX += 1;
return true;
}
else return false;
}
For a Task's progress to change, you need to explicitly call updateProgress(...) on the task. The only way to know what to pass in would be to observe the image's progress and pass it to the task's progress, so you would just have a more convoluted version of the code above. This is not a good use case for a task, since Image already supports background loading out of the box.

Don't try to do this on your own. You've got no idea, how much of the image has been loaded, unless you find the size of the image before loading and load the image from a steam observing the progress of the stream, which would be unnecessarily complicated. BTW: The Image constructor you use returns when the image is completely loaded. You can specify the image to be loaded asynchronically by using the right constructor however. Image provides a progress property to observe the loading progress:
#Override
public void start(Stage primaryStage) {
ImageView iv = new ImageView();
ProgressBar pb = new ProgressBar();
Button btn = new Button("Load Image");
btn.setOnAction((ActionEvent event) -> {
// ca. 6 MB image loaded from web
Image image = new Image("http://eoimages.gsfc.nasa.gov/images/imagerecords/79000/79793/city_lights_africa_8k.jpg", true);
pb.progressProperty().bind(image.progressProperty());
iv.setImage(image);
});
ScrollPane sp = new ScrollPane(iv);
VBox.setVgrow(sp, Priority.ALWAYS);
VBox root = new VBox(btn, pb, sp);
root.setFillWidth(true);
Scene scene = new Scene(root);
primaryStage.setMaximized(true);
primaryStage.setScene(scene);
primaryStage.show();
}

Related

Issue relating to JavaFX GUI event handling and updating

apologies for the length of my code. I realized last night that I was on the wrong path and now have gotten stuck on an issue that I think relates to JavaFX event handling. Initially I had the logic functioning outside a GUI in a basic loop that depended on interaction through the console. Everything was working great. I've now tried to get this to work in a GUI with interaction from the user.
I have two main problems with the code below.
The first is that the text in textArea is not updating with additional text after the startButton executes the start of my main logic sequence. The first append starts right under the first while loop. I was hoping to have this show up in the GUI as the logic executes. I'm not sure if I need to tell the GUI to update at certain intervals or if there's something else wrong.
Second, I'm not sure how to get the program to wait for the user to type in something into textField before hitting the textButton I created to continue on. I used to have a scanner created which caused the program to wait in the console for input. I realize I need some way of telling it to wait for a button press when it's running inside JavaFX.
I chose not to include the rest of the code to make things easier to read, but I can add it on if it will help resolve this issue.
Thank you everyone for your help!
public class FxApp extends Application {
//Creates FileParser object with methods that alter the incoming Array of Strings into the format we need
FileParser fileParser = new FileParser();
Configure configure = new Configure();
private String text;
private String initialState;
private ArrayList<Machine> machines = new ArrayList<Machine>();
private Map<String, String> initialStates = new HashMap<String, String>();
private Map<String, String> states = new HashMap<String, String>();
private Map<String, ArrayDeque<String>> queues = new HashMap<String, ArrayDeque<String>>();
private Map<Integer, ArrayList<String>> parsedData = new HashMap<Integer, ArrayList<String>>();
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("File Chooser");
FileChooser fileChooser = new FileChooser();
fileChooser.getExtensionFilters().addAll(new ExtensionFilter("Text Files", "*.txt"));
Button startButton = new Button("Start");
Button openButton = new Button("Click to open a file...");
openButton.setPrefSize(200, 80);
Button textButton = new Button("Enter");
TextArea textArea = new TextArea();
textArea.setWrapText(true);
TextField textField = new TextField();
Label lbl = new Label();
VBox vbox = new VBox(lbl, openButton, startButton, textArea, textField, textButton);
vbox.setSpacing(10);
vbox.setPadding(new Insets(15));
lbl.setText("This tool creates virtual automata based \ron the file.");
Scene scene = new Scene(vbox, 640, 480);
primaryStage.setScene(scene);
primaryStage.show();
openButton.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
File file = fileChooser.showOpenDialog(primaryStage);
if (file != null) {
//Execute the method to convert to string array before sending to file parser
try {
fileParser.convertFile(file);
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
});
textButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
text = textField.getText();
}
});
startButton.setOnAction(new EventHandler <ActionEvent>()
{
public void handle(ActionEvent event)
{
machineCreation();
String exit = "no";
String nextLine = null;
ArrayList<String> listOfCurrentTransitions = new ArrayList<String>();
int nextInt = 0;
states = initialStates;
while(!(exit.toLowerCase().equals("yes"))) {
textArea.appendText("Choose a state to load");
//Print out the states possible for each machine
ArrayList<String> tempTrans = machines.get(nextInt).getTransitions();
//This loops through the list of transitions of the machine and pulls possible transitions from its current state
for(int i = 0; i < tempTrans.size(); i++) {
String pull = tempTrans.get(i);
String[] apart = pull.split(" ");
pull = apart[0];
if(states.get(Integer.toString(nextInt)).equals(pull)) {
listOfCurrentTransitions.add(tempTrans.get(i));
}
}
if(!(listOfCurrentTransitions.isEmpty())) {
textArea.appendText("The following transitions are possible. Choose one: " + listOfCurrentTransitions);
}
else {
textArea.appendText("No transitions for this machine exist from its current state");
}
//Tell GUI to wait for user input in textField and execute textButton which assigns to String text. Resume on button click.
The while loop blocks the JavaFX application thread which prevents updates of the GUI and handling of events.
You need to execute the logic of a single iteration of the loop on each "text commit" instead:
private TextArea textArea;
private void activateState(int nextInt) {
ArrayList<String> listOfCurrentTransitions = new ArrayList<String>();
textArea.appendText("Choose a state to load");
//Print out the states possible for each machine
ArrayList<String> tempTrans = machines.get(nextInt).getTransitions();
//This loops through the list of transitions of the machine and pulls possible transitions from its current state
for(int i = 0; i < tempTrans.size(); i++) {
String pull = tempTrans.get(i);
String[] apart = pull.split(" ");
pull = apart[0];
if(states.get(Integer.toString(nextInt)).equals(pull)) {
listOfCurrentTransitions.add(tempTrans.get(i));
}
}
if(listOfCurrentTransitions.isEmpty()) {
textArea.appendText("No transitions for this machine exist from its current state");
} else {
textArea.appendText("The following transitions are possible. Choose one: " + listOfCurrentTransitions);
}
}
#Override
public void start(Stage primaryStage) throws Exception {
...
textArea = new TextArea();
...
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
machineCreation();
activateState(0);
}
});
textButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// read input and ask for more input...
int nextState = Integer.parseInt(textField.getText()); // TODO: deal with invalid input
activateState(nextState);
}
});
You probably need to fix the logic a bit to verify a transition is valid, change the values of some fields ect...

How to handle wrong drop location in JavaFx?

In my GUI I create some dices (ImageView) and I can drag and drop them in one specific GridPane. When I drop a dice into the specific GridPane, the dice disappears from the initial location and it moves to the right position. This works fine only if I choose the right drop location.
The problem is how can I manage the wrong drop location?
Actually if I drop a dice in a wrong location (like outside the Gridpane) the dice disappears like it was moved to the right position.
I want to restore the dice to the original location if the dice isn't placed to the GridPane.
Is there a method can help me to check if I drop into the right location? Or something can prevent to drop into the wrong location?
You can check the transferMode property of the DragEvent passed to the onDragDone event:
dragSource.setOnDragDone(evt -> {
if (evt.getTransferMode() == null) {
System.out.println("drag aborted");
} else {
System.out.println("drag successfully completed");
}
});
Note: this requires you to mark the drag gesture as completed in the onDragDropped event handler using setDropCompleted. Example:
#Override
public void start(Stage primaryStage) {
Button source = new Button("Not dragged yet");
Button target = new Button("target");
HBox root = new HBox(20, source, target);
source.setOnDragDetected(evt -> {
Dragboard db = source.startDragAndDrop(TransferMode.COPY);
ClipboardContent content = new ClipboardContent();
content.putString(source.getText());
db.setContent(content);
});
source.setOnDragDone(evt -> {
source.setText(evt.getTransferMode() == null ? "failure" : "success");
});
target.setOnDragOver(evt -> {
if (evt.getDragboard().hasString()) {
evt.acceptTransferModes(TransferMode.COPY);
evt.consume();
}
});
target.setOnDragDropped(evt -> {
String value = evt.getDragboard().getString();
target.setText(value);
evt.setDropCompleted(true);
evt.consume();
});
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}

JavaFX : ChangeListener to get the resolution of screen on mouse released

How do I get the resolution of the screen application (during resize) but only once the mouse has been released ?
I've looked around, but I found nothing. I've done this :
scene.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
ConfigBusiness.getInstance().setScreenWidth(newValue.intValue());
}
});
scene.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
ConfigBusiness.getInstance().setScreenHeight(newValue.intValue());
}
});
But as you expect, everytime the width / height changes, it calls the function to save the value (in my case, in the registry, which results in many calls).
Is there a way to get the value only when the mouse button has been released ? Or maybe use another kind of listener (setOnMouseDragRelease or something like that) ?
EDIT : I want the user to be able to resize the application window, and once he releases the mouse button after resizing I would like to trigger an event (and not during the whole resizing process).
Thanks
EDIT 2 : Following a bit the idea of #Tomas Bisciak, I came up with this idea.
scene.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if(!widthHasChanged()){
setWidthChanged(true);
System.out.println("Width changed");
}
}
});
scene.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if(!heightHasChanged()){
setHeightChanged(true);
System.out.println("Height changed");
}
}
});
scene.addEventFilter(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if(widthHasChanged()){
ConfigBusiness.getInstance().setScreenWidth(scene.getWidth());
System.out.println("Width changed > release");
setWidthChanged(false);
}
if(heightHasChanged()){
ConfigBusiness.getInstance().setScreenHeight(scene.getHeight());
System.out.println("Height changed > release");
setHeightChanged(false);
}
}
});
I flagged the changes done during the resizing process (with the help of widthProperty and heightProperty) and set them to true if changed, and then when releasing the mouse, I set the values if they have changed.
The problem is that the last event MOUSE_RELEASED is not triggered. I see the output from the changeListeners but not the eventFilter. Any ideas ?
Hook listener onto scene Handle mouse event anywhere with JavaFX (use MouseEvent.MOUSE_RELEASED) and on mouse release write values of the setScreenHeight/width(on every mouse release ),
if you want to only write values when drag happened with intention of resize , use boolean flag in change() "flag=true on change" methods to indicate that change has started, aferwards on mouse release just write values wherever you want and set flag to (flag=false).
If you want every time the mouse is released to get the resolution of the Monitor Screen here is the code:
Assuming you have the code for the Stage and Scene you can add a Listener for MouseReleased Event like this:
public double screenWidth ;
public double screenHeight;
.......
scene.setOnMouseReleased(m->{
screenWidth = Screen.getPrimary().getBounds().getWidth();
screenHeight = Screen.getPrimary().getBounds().getHeight();
});
......
In case you have an undecorated window that you resize it manually
when the mouse is dragged i recommend you using setOnMouseDragged();
Assuming that you are doing the above this code for moving the window
manually can be useful:
public int initialX;
public int initialY;
public double screenWidth = Screen.getPrimary().getBounds().getWidth() ;
public double screenHeight = Screen.getPrimary().getBounds().getHeight();
setOnMousePressed(m -> {
if (m.getButton() == MouseButton.PRIMARY) {
if (m.getClickCount() == 1) { // one Click
setCursor(Cursor.MOVE);
if (window.getWidth() < screenWidth ) {
initialX = (int) (window.getX() - m.getScreenX());
initialY = (int) (window.getY() - m.getScreenY());
} else
setFullScreen(false);
} else if (m.getClickCount() == 2) // two clicks
setFullScreen(true);
}
});
setOnMouseDragged(m -> {
if (window.getWidth() < screenWidth && m.getButton() == MouseButton.PRIMARY) {
window.setX(m.getScreenX() + initialX);
window.setY(m.getScreenY() + initialY);
}
});
setOnMouseReleased(m -> setCursor(Cursor.DEFAULT));
I finally found a way to accomplish "more or less" what I wanted. Here is the code for those who are looking for how to do it.
ChangeListener<Number> resizeListener = new ChangeListener<Number>() {
Timer timer = null; // used to schedule the saving task
final long delay = 200; // the delay between 2 changes
TimerTask task = null; // the task : saves resolution values
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
// cancels the old task as a new one has been queried
// at every change, we cancel the old task and start a new one
if(task != null) task.cancel();
// reset of the timer
if(timer == null) timer = new Timer();
task = new TimerTask() {
#Override
public void run() {
ConfigBusiness.getInstance().setScreenWidth(scene.getWidth());
ConfigBusiness.getInstance().setScreenHeight(scene.getHeight());
// stopping the timer once values are set
timer.cancel();
timer = null;
// stopping the task once values are set
task.cancel();
task = null;
}
};
timer.schedule(task, delay);
}
};
scene.widthProperty().addListener(resizeListener);
scene.heightProperty().addListener(resizeListener);
I added some comments just to help understanding the way it works. During resizing, a timer schedules a task to retrieve the width / height of the window and is started.
If another changes incomes, then the task is "reset" in order to get only the last values set.
Once the values are retrieved, it is important to clear the timer and the task, else threads will continue running.
Thanks to all who gave me hints on how to do it, hope this helps !

Add level in JavaFX during loading operation

I have the following UI
So , you insert a keyword and then press "Cerca" (it's like Search in Italian) , then the Google Custom Search Api show the first 10 pictures. During the Custom search API are processing the results (the pictures) I want to show an other picture like this
(I know it's big but the dimension is not the main point now). My idea is simple, I want to put the picture one "level"(don't know exactly how to call ) over the UI, then the picture will be not visible in 3 case: 1) When the API will end their job 2)If I don't have results 3) If I get an exception. My question is, which is the best approach to do this ? And then, Should I use Threads?
I hope I was clear
UPDATE:
This is the code of "cerca" button
cerca.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
// Task<Boolean> task = new Task <Boolean>(){
//
// #Override
// protected Boolean call() throws Exception {
// // TODO Auto-generated method stub
// return null;
// }
//
//
// };
String searchKeyWord = userTextField.getText();
result = getSearchResult(searchKeyWord);
for ( i=0; i<result.size(); i++)
{
System.out.println("" +result.get(i));
ImageView resultview;
resultview = new ImageView(result.get(i));
resultview.setFitWidth(130);
resultview.setFitHeight(130);
// resultview.setStyle("-fx-border:6; -fx-border-color: green;");
if(j==4)
{
j=0;
k++;
}
resultgrid.add(resultview, j,k );
j++;
VBox vbox = new VBox();
resultgrid.setHgap(50);
resultgrid.setVgap(50);
// resultgrid.setStyle("-fx-border:1; -fx-border-color: red;");
vbox.getChildren().add(resultgrid);
vbox.setSpacing(10);
vbox.setPadding(new Insets(90, 0, 10, 220)); //TOP RIGHT BOTTOM LEFT
// content.setAlignment(resultgrid, Pos.TOP_RIGHT);
getChildren().add(vbox);
final int ind = i;
resultview.setOnMouseClicked((ev) ->{
if (ev.getClickCount()==2)
{
image = SwingFXUtils.fromFXImage(resultview.getImage(), null);
parent.setCrop(image);
}
});
}
}
});
Have a look at Task. It has setOnSucceeded() and setOnFailed() which will come handy in your case.
You can basically create a new Task when the search takes place. The search will run in background and you can show the loading UI in the screen.
If the task completes successfully, you can load the new screen with the results.
If the task fails, you can show an error message near the search TextField.
In case of exception, you can catch it and write the necessary code.

How to wait for user's action in JavaFX (in the same stage)

Do you know how to wait for the user's input in a for loop? I don't mean the showAndWait() method, because I am not opening a new dialogue stage for the user. So for example, each round of the for loop should be waiting for the user to push a button before going ahead with the next round.
How is it possible? Many thanks!
UPDATE:
Now it came to my mind, that it would work with a while(buttonNotPressed){} but is it a good solution? I mean the while loop is running in this case as crazy until the user won't push the button. Or doest it work somehow similarly with wait methods?
Imagine it as a session:
User starts session with handleStart() You give the user 5 questions, one after one. In every iteration, the user can answer the upcoming question and he can save or submit the answer by handleSaveButton() You process the answer as you want, and go ahead with the next iteration. The point is, that the iteration must stop, until the save button hasn't been pressed.
Don't do it like that. The FX toolkit, like any event-driven GUI toolkit, already implements a loop for the purposes of rendering the scene graph and processing user input each iteration.
Just register a listener with the button, and do whatever you need to do when the button is pressed:
button.setOnAction(event -> {
// your code here...
});
If you want the action to change, just change the state of some variable each time the action is performed:
private int round = 0 ;
// ...
button.setOnAction(event -> {
if (round < 5) {
System.out.println("Round "+round);
System.out.println("User's input: "+textArea.getText());
round++ ;
}
});
I recently ran into a similar problem where I wanted something to be executed with an interval (if that's what you mean), until the user fired an event. I found 3 ways to do this:
UPDATE
You should use the stop/cancel method for the custom runnable and timer or else the thread will still be running when you exit the application. Timeline seems do it by itself.
Using a Timer:
Timer timer = new Timer();
TimerTask task = new TimerTask() {
#Override
public void run() {
System.out.println("Printed every second.");
}
};
timer.scheduleAtFixedRate(task, 0, 1000);
//timer.cancel();
With a TimeLine:
Timeline tl = new Timeline(new KeyFrame(Duration.millis(1000), e -> {
System.out.println("Timeline");
}));
tl.setCycleCount(Timeline.INDEFINITE);
tl.play();
//tl.stop();
Or making your own runnable class:
public class Runner implements Runnable {
private final Thread thread = new Thread(this);
private boolean run;
#Override
public void run() {
while(run) {
try {
System.out.println("Printed from loop");
Thread.sleep(1000);
} catch (InterruptedException e) {
run = false;
}
}
}
public void start() {
run = true;
thread.start();
}
public void stop() {
if(run) {
thread.interrupt();
System.out.print("Thread has stopped.");
}
}
}
And then when a person clicks fx. a button the event would stop using the example James_D posted:
Button btn = new Button("Button");
btn.setOnAction(e -> {
timer.cancel();
tl.stop();
runner.stop();
});
In my case, for inside for, had to create 2 index in class, use:
//start method
Timer timer = new Timer();
TimerTask task = new TimerTask()
{
public void run()
{
Platform.runLater(()->{
//... code to run after time, calling the same mehtod, with condition to stop
});
}
};
timer.schedule(task, time);
//end method
Had to use recursive method, incrementing the index with conditions, cause the tasks were been schedule all at the same time, without wait time.
I do not know if it is rigth, but was the solution that i found.
Hope it helps.
ALTERNATIVE SOLUTION W/O PAUSING:
I'm creating a game where I want the user to pick the game difficulty before the game starts. Instead of trying to pause the program midway through, I just put the next step of the code in a separate method which you call once a button is clicked:
private static difficulty;
public static void main(String[] args) {
try {
Application.launch(args);
} catch (UnsupportedOperationException e) {
}
}
public void start(Stage startStage) {
HBox buttons = new HBox();
Button easyButton = new Button("Easy");
Button mediumButton = new Button("Medium");
Button hardButton = new Button("Hard");
buttons.getChildren().addAll(easyButton, mediumButton, hardButton);
buttons.setAlignment(Pos.CENTER);
hbox.getChildren().addAll(buttons);
Scene startScene = new Scene(buttons, 200, 200);
startStage.setScene(startScene);
startStage.show(); // MENU
EventHandler<ActionEvent> playEasy = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
difficulty = 1; // SET DIFFICULTY
startStage.close(); // CLOSE MENU
play(); // RUN GAME ON EASY
}
};
EventHandler<ActionEvent> playMedium = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
difficulty = 2; // SET DIFFICULTY
startStage.close(); // CLOSE MENU
play(); // RUN GAME ON MEDIUM
}
};
EventHandler<ActionEvent> playHard = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
difficulty = 3; // SET DIFFICULTY
startStage.close(); // CLOSE MENU
play(); // RUN GAME ON HARD
}
};
easyButton.setOnAction(playEasy);
mediumButton.setOnAction(playMedium);
hardButton.setOnAction(playHard);
}
public void play() {
// WRITE GAME CODE HERE
}
To solve your specific problem, you could probably pass the startStage into the play method and then just update the scene there...but regardless I do hope this helps someone whos having trouble on how to use buttons! :)

Resources