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

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! :)

Related

Key Listener in JavaFX that changes on button press

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();
}
}

How to trigger an event in a JavaFX Application (Stage) when some keys are pressed on the KeyBoard?

I'm trying to setup a backdoor on an application which I'm working on.
I want to load a new Window when the user presses "CTRL + ALT + F12".
This is what I have tried so far though it is terribly bad.
//packages
import java.awt.EventQueue;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
//...
private void setupBackPass(){
HashMap<KeyStroke, Action> actionMap = new HashMap<KeyStroke, Action>();
KeyStroke key1 = KeyStroke.getKeyStroke(KeyEvent.ALT, KeyEvent.CTRL_DOWN_MASK);
actionMap.put(key1, new AbstractAction("action1") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Ctrl-ALT pressed: " +e);
}
});
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
kfm.addKeyEventDispatcher(new KeyEventDispatcher() {
#Override
public boolean dispatchKeyEvent(KeyEvent e) {
KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
if(actionMap.containsKey(keyStroke)){
final Action a = actionMap.get(keyStroke);
final ActionEvent ae = new ActionEvent(e.getSource(), e.getID(), null);
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
}
});
return false;
}
});
});
}
Am not sure how to do it but I would like it that if the keys are pressed then a Super admin window should be opened.
From the code you posted, it looks like you are using Swing and not JavaFX. It also looks like you are trying to use Key Bindings. As explained in that link, you need to modify both the input map and the action map. Also, you don't create an action map, you use the existing one. Again, that is explained in the page that I provided a link to.

Concurrent task updating complex object JavaFX - swingworker equivalent?

I want to run a task in background updating intermediate results in the view.I am trying to implement MVC JavaFX application. The task is defined in the Model.
I want to send to the main threath partial results in order to show them in the View.
I use updateValue() to do so. Also, I define object property and a listener in the controller.
My problem: The method changed() from the listener, is not being fired each time that updateValue() is executed in the Task. Why? How can I force it to do this?.
I have not found much complex examples.
What I have so far:
Model.cpp
ComplexObject _complexO;
public Task<ComplexObject> getModelTask() {
return new Task<ComplexObject>() {
#Override
protected ComplexObject call() throws Exception {
int numberOfFiles = 0;
boolean filesToRead = true;
while (filesToRead){
// ....
_complexO = new ComplexObject();
try{
//..
if(f.exists()){
_complexO.initialize();
numberOfScans ++;
}
else{
_complexO.initializeToNull();
}
String stringNumber = Converter.toString(numberOfFiles);
updateMessage(stringNumber);
updateValue(_complexO );
} catch(Exception ex){
ex.printStackTrace();
_complexO = null;
return _complexO;
}
filesToRead = areThereFilesToRead();
}
return _complexO;
}
};
}
Controller.cpp
...
Task< ComplexObject> task = _model.getModelTask();
_AJavaFXTextField.textProperty().bind(task.messageProperty());
_AJavaFXTextField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue observable, String oldValue, String newValue) {
System.out.println("Success with messageProperty!!" + newValue);
}
});
SimpleObjectProperty<ComplexObject> complexObjectProperty = new SimpleObjectProperty<>();
complexObjectProperty.bind(task.valueProperty());
complexObjectProperty.addListener(new ChangeListener<ComplexObject>(){
#Override
public void changed(ObservableValue<? extends ComplexObject> observable, ComplexObject oldValue, ComplexObject newValue) {
if(newValue.data == null ) {
System.out.println("value is new!!! " + scansNumber);
}
else if(newValue.isValid()){
System.out.println("I want to plot newValue data here");
}
}
});
Thread th= new Thread(task);
System.out.println("call TASK");
th.start();
}
My questions/conclusions here:
How to force to all times that I execute in the task updateValue() to really execute the listener - so execute the code where I want to plot data.
Why it is more times fire the bind for the messageProperty than the valueProperty? - it should be the same number of times.
Why I find that the code of the listener is fired more times when debug mode than normal execution?
Any recomendation of good sources about this topic (from a complex point of view) would be great.
I am looking from something in JavaFX to replace SwingWorker.
What I really whant at the end: To return a list of complexObjects from the task, and ideally, updateValue() would send the objects one per one (partial results)
I have followed:
https://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html
Thanks very much for any contribuction
Task only guaranties that a value passes to updateValue or a value passed later will be set to the value property. This is done to increase performance of the application thread by limiting the number of changes the listeners are notified of.
Why it is more times fire the bind for the messageProperty than the valueProperty? - it should be the same number of times.
As described above there simply is no guaranty about the number of updates.
Why I find that the code of the listener is fired more times when debug mode than normal execution?
In general debugging makes your program smaller. The smaller the update frequency from the thread of your Task, the smaller the number of updates between the times the Task class updates the properties and the smaller the number of skipped. (The updates are probably executed every frame or every few frames.) If you even use a break-point/stepper in the task, you probably make the Task extremely slow while the application thread runs at normal speed.
It should be easy enough to implement publish on your own by using a List to buffer the updates
public abstract class JavaFXWorker<S, T> extends Task<S> {
private List<T> chunks = new ArrayList<>();
private final Object lock = new Object();
private boolean chunkUpdating = false;
protected final void publish(T... results) {
synchronized (lock) {
chunks.addAll(Arrays.asList(results));
if (!chunkUpdating) {
chunkUpdating = true;
Platform.runLater(() -> {
List<T> cs;
synchronized (lock) {
cs = chunks;
// create new list to not unnecessary lock worker thread
chunks = new ArrayList<>();
chunkUpdating = false;
}
try {
process(cs);
} catch (RuntimeException ex) {
}
});
}
}
}
protected void process(List<T> chunks) {
}
}
Sample use
#Override
public void start(Stage primaryStage) {
ListView<Integer> lv = new ListView<>();
Button btn = new Button("Run");
btn.setOnAction((ActionEvent event) -> {
JavaFXWorker<Void, Integer> worker = new JavaFXWorker<Void, Integer>() {
#Override
protected Void call() throws Exception {
final int maxCount = 100;
Random random = new Random();
int breakIndex = random.nextInt(maxCount-1)+1;
for (int i = 0; i < breakIndex; i++) {
publish(i);
}
// some break simulating a part long part of the task with no updates
Thread.sleep(3000);
for (int i = breakIndex; i <= maxCount; i++) {
publish(i);
}
return null;
}
#Override
protected void process(List<Integer> chunks) {
lv.getItems().addAll(chunks);
}
};
new Thread(worker).start();
});
Scene scene = new Scene(new VBox(btn, lv));
primaryStage.setScene(scene);
primaryStage.show();
}

What's wrong with this JavaFX system message pop up method? [duplicate]

I'm currently working with two controller classes.
In Controller1 it creates a new stage that opens on top of the main one.
Stage stage = new Stage();
Parent root = FXMLLoader.load(getClass().getResource("Controller2.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
Now once that stage is open, I want it to stay open for about 5 seconds before closing itself.
Within Controller2, I've tried implementing something like
long mTime = System.currentTimeMillis();
long end = mTime + 5000; // 5 seconds
while (System.currentTimeMillis() > end)
{
//close this stage
}
but I have no idea what to put inside the while loop to close it. I've tried all sorts and nothing works.
Use a PauseTransition:
PauseTransition delay = new PauseTransition(Duration.seconds(5));
delay.setOnFinished( event -> stage.close() );
delay.play();
Doing it your way, this would work:
long mTime = System.currentTimeMillis();
long end = mTime + 5000; // 5 seconds
while (mTime < end)
{
mTime = System.currentTimeMilis();
}
stage.close();
You need to save your stage into a variable.
Maybe it is better to run that in a Thread, so that you can do something within the 5 seconds.
Another way would be to run a Thread.sleep(5000); and this would also be more performant than the while loop.
This code sets the text of a TextArea element and makes it visible for a certain amount of time. It essentially creates a pop up system message:
public static TextArea message_text=new TextArea();
final static String message_text_style="-fx-border-width: 5px;-fx-border-radius: 10px;-fx-border-style: solid;-fx-border-color: #ff7f7f;";
public static int timer;
public static void system_message(String what,int set_timer)
{
timer=set_timer;
message_text.setText(what);
message_text.setStyle("-fx-opacity: 1;"+message_text_style);
Thread system_message_thread=new Thread(new Runnable()
{
public void run()
{
try
{
Thread.sleep(timer);
}
catch(InterruptedException ex)
{
}
Platform.runLater(new Runnable()
{
public void run()
{
message_text.setStyle("-fx-opacity: 0;"+message_text_style);
}
});
}
});
system_message_thread.start();
}
This solution is completely general. You can change the setStyle methods to any code that you want. You can open and close a stage if you like.

JavaFX Timeline - How to stop it from elsewhere in class?

first post on here so please be gentle...
I am fairly new to JavaFX and have successfully set up quite a complicated GUI which reads a csv file in order to populate certain components within the GUI.
I'm using a timeline in the intialize function for the GUI Controller which fires a button every second on the GUI - the button calls a function which reads the csv file form disc.. all this is working fine.
When I quit/exit the GUI stage I want to stop the timeline from running... but can't seem to manage this...
I have a small function which loads the Stage and also has an event listener to detect when it's closed... what I'd like to do is be able to close the timeline at the commented line... in the try/catch section.
public void Show_MACD() throws IOException
{
Parent root = FXMLLoader.load(getClass().getResource("MACD Turbo.fxml"));
Scene scene = new Scene(root);
Stage stage = new Stage();
stage.setScene(scene);
stage.setTitle("FX AlgoTrader MACD Turbo");
stage.show();
JavaFX.thisstage=stage;
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent we) {
LoginController sp=new LoginController();
try {
//how can I stop the timeline here?
sp.Show_Products(); // this loads up another stage - a menu in fact
} catch (IOException ex) {
Logger.getLogger(MACD_Controller.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
//System.out.println("running");
}
Here's the section in the initialize function where the timeline is set up and run from....(this is in the same class as the controller called 'MACD_Controller' which is also home to the 'Show_MACD' function which has a event listener for window close events.. that's kind of where I would like to stop the timeline ie when the window closes)
#Override
public void initialize(URL url, ResourceBundle rb) {
final Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent)
{
Refresh.fire(); //Refresh is a button on the GUI which calls the csv file
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
I know I need to somehow create a reference to 'timeline' so that I can use the 'timeline.stop' function... I've tried all sorts of mumbo jumbo but I keep getting an NPE.
I know this is super basic but I'm a bit stuck..
Cheers
Crispin

Resources