How to restart a JavaFX application when a button is clicked - java

I went through almost every post here regarding the matter but most of them doesn't explain what to do properly.
To the question:
I created a javaFX application, a dice game, human player vs. computer, but during any time while playing the game human player should be able to click button "new game" and what it should do is to restart the game from beginning.
I tried relaunching the stage again but in javafx we cannot call the launch method twice.
1)Is there a way i can implement this without restarting the whole application?
2)if not how can i restart the application completely using a button click?
Main class
public class Main {
public static void main(String[] args) {
GameUI gameUI = new GameUI();
gameUI.launch(GameUI.class, args);
}
GameUI
(i removed many codes from this class to make it short. codes that i think enough to give an idea is included. sorry if it is too long.)
public class GameUI extends Application {
//all btn and label declarations
//creating instances for necessary classes
private Scene scene;
#Override
public void start(Stage primaryStage) throws Exception {
//Displaying Dice for Player and Computer
setLabelsPlyr(diesP);
setLabels(diesC);
btnThrow = new Button("Throw");
btnThrow.setPrefSize(70, 40);
//Throw action is performed
btnThrow.setOnAction(e -> {
//setting and displaying dies
DieClass[] com = getNewDiceArrC();
lblDiceOneC.setGraphic(new ImageView(diesC[0].getDieImageC()));
//so on.....
DieClass[] playerAr = getNewDiceArrP();
lblDiceOnePlyr.setGraphic(new ImageView(diesP[0].getDieImageP()));
//so on...
});
btnNewGame = new Button("New Game");
btnNewGame.setOnAction(e -> {
**//WHAT TO DO HERE?????**
});
//setting layouts
GridPane gridPane = new GridPane();
gridPane.add(lblComputer, 0, 0);
//so on.....
Scene scene = new Scene(gridPane, 1100, 400);
primaryStage.setScene(scene);
primaryStage.setTitle("dice Game");
primaryStage.show();
}
//some other methods
public void setLabels(DieClass[] dies) {
for (int i=0; i < dies.length; i++) {
lblDiceOneC = new Label();
lblDiceOneC.setGraphic(new ImageView(dies[0].getDieImageC()));
++i;
//so on.....
break;
}
}
public void setLabelsPlyr(DieClass[] dies){
for (int i=0; i<dies.length; i++) {
lblDiceOnePlyr = new Label();
lblDiceOnePlyr.setGraphic(new ImageView(dies[0].getDieImageP()));
++i;
lblDiceTwoPlyr = new Label();
//so on......
break;
}
}
p.s I am very new to JavaFX and somewhat new to java programming.

You already noticed that you cannot do the launching process again. Therefore your best option is to rewrite the application class and move the initialisation logic to a new method:
void cleanup() {
// stop animations reset model ect.
}
void startGame(Stage stage) {
// initialisation from start method goes here
btnNewGame.setOnAction(e -> {
restart(stage);
});
stage.show();
}
void restart(Stage stage) {
cleanup();
startGame(stage);
}
#Override
public void start(Stage primaryStage) {
startGame(primaryStage);
}
Notes
Depending on the parts of the scene changed, it may be enough to change the state of some of the nodes (more efficient than creating a new scene). (Just take a look at the changes you made during the game and decide for yourself)
launch() is a static method and you should not create a instance of your application class yourself for that reason. Use Application.launch(GameUI.class, args); instead and let the method handle the creation of the GameUI instance.
It may be a better design to move the UI creation to a class different to the application class. This way reuse of the code is easier, since it does not require the creation of a instance of a subclass of Application.

In your Start() function, add the following line:
Platform.setImplicitExit(false);
This will make your application run in background without exiting.
Now, use a wrapper function to start the second time, say:
void displayApplication() {
Platform.runLater(new Runnable() {
#Override
public void run() {
try {
start(new Stage());
} catch (Exception e) {
e.printStackTrace();
}
}
});
}

Related

Timer in JavaFX updating Label (Bindings)

I want to add a clock to my application that tells how long you have been doing the task. To simplify it, I have included a counter that increments every second in a new thread and update the label 'setTimer' with the counter number. For this I have a label fx:id="setTimer" in my .fxml file and imported it into my class.
#FXML
private Label setTimer;
And created another class in my class that extends the thread TimerTask and increments the counter by one on each call. Created a new Object 'text', which should always be updated with the current value of the counter.
SimpleStringProperty text = new SimpleStringProperty("undefined");
public class MyTask extends TimerTask {
#Override
public void run() {
counter++;
text.set(Integer.toString(counter));
}
}
To have this class called every second I created a timer in the initialize method and set it to one second.
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
MyTask myTask = new MyTask();
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(myTask, 0 , 1000);
setTimer.textProperty().bind(text);
}
At the moment I get the exception 'Not on FX application thread; currentThread = Timer-0'.
I've tried many ways to solve my problem, but I haven't gotten to the right point.
My idea of ​​what I want to do should be clear, and I would be happy if someone could help me.
My Problem is to update the changes of the counter in the GUI.
It doesn't have to be solved the way I thought it would, just need a tip on how to best implement it.
Thank you
Ok, my comments are too long. This is how I would try to do it.
Start the stopwatch on the application being loaded
Create a new thread that launches itself every so often.
Inside there, get the time from the Stopwatch in seconds (sw.getTime(TimeUntis.seconds)). Convert that to hours and minutes if you want like shown in this SO post
Then, write the time to the UI using Platform.runLater(new Runnable(){ /* access ui element and write time here */ });
Using Platform.runLater() in a background thread is kind of a messy kludge that should probably be avoided. JavaFX has mechanisms to handle this kind of thing which you should use. Specifically, Task<> is designed to allow background threads to update data which is connected to JavaFX screen elements which need to be updated on the FXAT.
You CAN do what you're trying to do with a JavaFX Task, but using the Java Timer inside of it seems impossible, since there doesn't seem to be any way for a Java thread to wait on a Timer to complete. So, instead I've used a "for" loop with a sleep to do the same thing. It's clumsy, but it does demonstrate how to connect partial results from a Task to screen display:
public class Sample1 extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new Timer1(), 300, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
public class Timer1 extends VBox {
public Timer1() {
Text time = new Text();
Button startButton = new Button("Start");
Button stopButton = new Button("Stop");
getChildren().addAll(time, startButton, stopButton);
startButton.setOnAction(startEvt -> {
Task<Integer> timerFxTask = new Task<>() {
{
updateValue(0);
}
#Override
protected Integer call() throws Exception {
for (int counter = 0; counter <= 1000; counter++) {
sleep(1000);
updateValue(counter);
}
return 1000;
}
};
stopButton.setOnAction(stopEvt -> timerFxTask.cancel());
time.textProperty().bind(Bindings.createStringBinding(() -> timerFxTask.getValue().toString(),
timerFxTask.valueProperty()));
Thread timerThread = new Thread(timerFxTask);
timerThread.start();
});
}
}
But there is a better way to do what you're trying to do, which is essentially an animation - and JavaFX has a facility to do exactly this. Usually, people use animations to morph the appearance of JavaFX screen elements, but you can also use it to animate the contents of a Text over time as well. What I've done here is create an IntegerProperty which can be transitioned from a start value to an end value interpolated linearly over time and then bound that value to the TextProperty of a Text on the screen. So you see it update once per second.
public class Sample1 extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new Timer2(), 300, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
public class Timer2 extends VBox {
public Timer2() {
Text time = new Text();
Button startButton = new Button("Start");
Button stopButton = new Button("Stop");
getChildren().addAll(time, startButton, stopButton);
startButton.setOnAction(startEvt -> {
IntegerProperty counter = new SimpleIntegerProperty(0);
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1000), new KeyValue(counter, 1000)));
stopButton.setOnAction(stopEvt -> timeline.stop());
time.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(counter.get()), counter));
timeline.play();
});
}
}

JavaFX KeyEvents during Drag & Drop operation

I need to know whether a certain key is down while performing a drag & drop operation.
So I tried to use setOnKeyPressed / setOnKeyReleased of a Scene with a combination of HashMap, but I have a problem with this approach:
Imagine a scenario that one drags & drops a TableView item to somewhere while holding Control down. Now if I display a dialog at the end of the drop, while still holding Control down, the setOnKeyReleased is never called with this approach... as the Dialog is the one receiving the key released event.
How could I fix this?
Hope I understand your question here is a possible solution(work with any key):
public class Main extends Application {
SimpleBooleanProperty isKeyPress = new SimpleBooleanProperty(false);
#Override
public void start(Stage primaryStage) throws Exception{
Parent window = new VBox();
((VBox) window).getChildren().add(new Label("example of small window:"));
primaryStage.setTitle("example");
Scene scene=new Scene(window);
primaryStage.setScene(scene);
primaryStage.show();
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
System.out.println("Press");
isKeyPress.set(true);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Information Dialog");
alert.setHeaderText(null);
alert.setContentText("I have a great message for you!");
Scene alertScene = alert.getDialogPane().getScene();
alertScene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
System.out.println("Released on dialog");
isKeyPress.set(false);
}
});
alert.showAndWait();
}
});
scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
System.out.println("Released");
isKeyPress.set(false);
}
});
}
public static void main(String[] args) {
launch(args);
}
}
output exmple:
Press
Released on dialog
From your comment the goal is to change the behavior of the drag and drop depending on whether or not Ctrl is down. When it is do a copy operation, otherwise do a move operation. You do not need to deal with KeyEvents to implement this behavior. Instead, you would determine whether to copy or move in the onDragDetected handler. The onDragDetected handler uses a MouseEvent which has methods for querying the status of modifier keys—such as isControlDown(). Using this, we can specify what transfer modes are allowed based on the modifier keys.
Node node = ...;
node.setOnDragDetected(event -> {
Dragboard board;
if (event.isControlDown()) {
board = node.startDragAndDrop(TransferMode.COPY);
} else {
board = node.startDragAndDrop(TransferMode.MOVE);
}
// add contents to Dragboard
});
Note it may be more cross-platform to use isShortcutDown().

How to detect key event in diapason of time JavaFX

I'm making simple space craft game. I have a player (space ship) that fires rockets. When the space button is pressed the player launches a rocket. The problem is that when I hold "space", rockets are launched continuously. How can I prevent this effect and make pressing the space button detected only every 2 seconds, for example (and launch only one rocket)?
You possibly could try to create a list where pressed keys gonna be stored. After pressing the button listener will add that button to list, and will start to count time of pressure. Not sure if is this the best way, but I know, that the same procedure used when developer want to handle few pressed key at once.
You can store in your Ship class whether it is ready to fire a rocket and then in the method which fires the rocket, check for this member.
On rocket launch set this member to false and then you can start a Timeline for example which will reset the flag as it finishes.
Ship class
public class Ship {
private boolean readyToFireRocket = true;
private static final double ROCKET_LAUNCHING_DELAY = 2d;
public void fireRocket() {
if(readyToFireRocket) {
readyToFireRocket = false;
System.out.println("Rocket is fired");
KeyFrame keyFrame = new KeyFrame(Duration.seconds(ROCKET_LAUNCHING_DELAY),
e -> readyToFireRocket = true);
Timeline reloadRockets = new Timeline(keyFrame);
reloadRockets .play();
}
}
}
An example Application
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = new BorderPane();
Ship ship = new Ship();
Scene scene = new Scene(root, 300, 275);
scene.setOnKeyPressed(e -> {
if(e.getCode() == KeyCode.SPACE) {
ship.fireRocket();
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
One option is to deregister the event handler for a duration of 2 seconds whenever it is used (and re-register it at the end of the duration). I assume that you are using a setOnKeyPressed(e -> ...) to register the handler that reacts to pressing space.
Create and hold a reference to that handler so you can register and deregister it at will.
Create a PauseTransition with a delay of 2 seconds that registers the above handler when it's done (doing nothing).
In the handle() method, deregister the handler and start the transition.
Here is an example:
public class FXExmple extends Application{
private Button button = new Button("PRESS SPACE");
private PauseTransition pt = new PauseTransition(Duration.seconds(2));
#Override
public void start(Stage stage) throws Exception {
MyEventHandler eh = new MyEventHandler();
pt.setOnFinished(e -> button.setOnKeyPressed(eh));
button.setOnKeyPressed(eh);
Pane pane = new Pane();
pane.getChildren().add(button);
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.sizeToScene();
stage.show();
}
private class MyEventHandler implements EventHandler<KeyEvent> {
#Override
public void handle(KeyEvent event) {
System.out.println("FIRED");
button.setOnKeyPressed(null);
pt.play();
}
}
public static void main(String[] args) {
launch(args);
}
}
Note that this won't give you fine control over the duration since there is duration associated with (de)registering the handler and starting the transition, but they are very small. There is also a limit for the frequency in which the OS sends the events, so if you are planning to go down to ~50ms delays - beware.

JavaFX - Block user from changing stages without using MODAL

I have an application that looks like the following:
When a user clicks on the deck of cards, it opens up a new Stage.
This stage can be closed in one of two ways:
Right click the stage.
Click outside of the stage (it has a evenhandler for when it loses focus).
However, sometimes I NEED the user to select one or more cards from the deck using this window. I do not want to allow him to close the window until he has selected at least one card. This means I had to use MODAL to stop him from being able to access the stage underneath (My Applicaiton). The problem with MODAL is now he can never leave the window like he could before by clicking outside the stage, even when I want him to be able to. He is now only able to leave through right clicking. I could add a button but I'd really rather not.
I hope I explained my problem well enough. What would you guys recommend I do? Is there a way I could somehow block the user from going back to the previous stage without MODAL? I'm also not able to change Modality after the Stage has been shown, so that won't work.
Thanks!
The idea is to use the onCloseRequestProperty property of your pop-up Stage.
Called when there is an external request to close this Window. The
installed event handler can prevent window closing by consuming the
received event.
With this property you can interrupt the closing of the Stage if a condition (in your case at lest one card is selected) is not met by calling consume on the WindowEvent.
Note: As the documentation states: it is only valid if the request is external, so if you call the close method of the Stage, the attached listener will be not executed. As a solution rather than calling this method you can fire the WindowEvent.WINDOW_CLOSE_REQUEST event manually.
Example:
public class PopUpApp extends Application {
Stage popupStage;
Stage primaryStage;
#Override
public void start(Stage stage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
primaryStage = stage;
initPopUpStage();
// When the Pop-Up stage is showing, do not handle any action on the
// main GUI
root.disableProperty().bind(popupStage.showingProperty());
Button b = new Button("Open deck");
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// Add some ToggleButtons to simulate the cards
VBox vbox = new VBox();
vbox.setAlignment(Pos.CENTER);
List<ToggleButton> toggles = new ArrayList<ToggleButton>();
for (int i = 0; i < 4; i++) {
ToggleButton tb = new ToggleButton("Card " + i + 1);
toggles.add(tb);
}
vbox.getChildren().addAll(toggles);
Scene sc = new Scene(vbox, 300, 300);
popupStage.setScene(sc);
// On close request check for the condition
popupStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
Boolean readytoClose = false;
for (ToggleButton toggle : toggles) {
if (toggle.isSelected()) {
readytoClose = true;
break;
}
}
// Consume the event a show a dialog
if (!readytoClose) {
event.consume();
Alert alert = new Alert(AlertType.INFORMATION,
"At least one card has be to be selected!");
alert.showAndWait();
}
}
});
popupStage.show();
}
});
root.setCenter(b);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
private void initPopUpStage() {
popupStage = new Stage();
popupStage.initOwner(primaryStage);
popupStage.initStyle(StageStyle.UNDECORATED);
// On focus loss, close the window
popupStage.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
// Rather than popupStage.close(); fire the event manually
if (!newValue)
popupStage.fireEvent(new WindowEvent(popupStage, WindowEvent.WINDOW_CLOSE_REQUEST));
}
});
}
public static void main(String[] args) {
launch(args);
}
}
Update:
To make the main Stage unavailable I have added this line:
root.disableProperty().bind(popupStage.showingProperty());
This will disable the root BorderPane while the pop-up stage is showing. As soon as the pop-up window closed, the main window is enabled again.

JavaFX integration with Spring rest client [duplicate]

How to call the launch() more than once in java i am given an exception as "ERROR IN MAIN:java.lang.IllegalStateException: Application launch must not be called more than once"
I have create rest cleint in my java application when request comes it call javafx and opening webview after completing webview operarion am closing javafx windows using Platform.exit() method. when second request comes am getting this error how to reslove this error.
JavaFx Application Code:
public class AppWebview extends Application {
public static Stage stage;
#Override
public void start(Stage _stage) throws Exception {
stage = _stage;
StackPane root = new StackPane();
WebView view = new WebView();
WebEngine engine = view.getEngine();
engine.load(PaymentServerRestAPI.BROWSER_URL);
root.getChildren().add(view);
engine.setJavaScriptEnabled(true);
Scene scene = new Scene(root, 800, 600);
stage.setScene(scene);
engine.setOnResized(new EventHandler<WebEvent<Rectangle2D>>() {
public void handle(WebEvent<Rectangle2D> ev) {
Rectangle2D r = ev.getData();
stage.setWidth(r.getWidth());
stage.setHeight(r.getHeight());
}
});
JSObject window = (JSObject) engine.executeScript("window");
window.setMember("app", new BrowserApp());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
RestClient Method:
Calling to JavaFX application
// method 1 to lanch javafx
javafx.application.Application.launch(AppWebview.class);
// method 2 to lanch javafx
String[] arguments = new String[] {"123"};
AppWebview .main(arguments);
You can't call launch() on a JavaFX application more than once, it's not allowed.
From the javadoc:
It must not be called more than once or an exception will be thrown.
Suggestion for showing a window periodically
Just call Application.launch() once.
Keep the JavaFX runtime running in the background using Platform.setImplicitExit(false), so that JavaFX does not shutdown automatically when you hide the last application window.
The next time you need another window, wrap the window show() call in Platform.runLater(), so that the call gets executed on the JavaFX application thread.
For a short summary implementation of this approach:
See the answer by sergioFC
If you are mixing Swing you can use a JFXPanel instead of an Application, but the usage pattern will be similar to that outlined above.
For an example of the JFXPanel apprach, see Irshad Babar
s answer.
Wumpus Sample
This example is bit more complicated than it needs to be because it also involves timer tasks. However it does provide a complete stand-alone example, which might help sometimes.
import javafx.animation.PauseTransition;
import javafx.application.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.*;
// hunt the Wumpus....
public class Wumpus extends Application {
private static final Insets SAFETY_ZONE = new Insets(10);
private Label cowerInFear = new Label();
private Stage mainStage;
#Override
public void start(final Stage stage) {
// wumpus rulez
mainStage = stage;
mainStage.setAlwaysOnTop(true);
// the wumpus doesn't leave when the last stage is hidden.
Platform.setImplicitExit(false);
// the savage Wumpus will attack
// in the background when we least expect
// (at regular intervals ;-).
Timer timer = new Timer();
timer.schedule(new WumpusAttack(), 0, 5_000);
// every time we cower in fear
// from the last savage attack
// the wumpus will hide two seconds later.
cowerInFear.setPadding(SAFETY_ZONE);
cowerInFear.textProperty().addListener((observable, oldValue, newValue) -> {
PauseTransition pause = new PauseTransition(
Duration.seconds(2)
);
pause.setOnFinished(event -> stage.hide());
pause.play();
});
// when we just can't take it anymore,
// a simple click will quiet the Wumpus,
// but you have to be quick...
cowerInFear.setOnMouseClicked(event -> {
timer.cancel();
Platform.exit();
});
stage.setScene(new Scene(cowerInFear));
}
// it's so scary...
public class WumpusAttack extends TimerTask {
private String[] attacks = {
"hugs you",
"reads you a bedtime story",
"sings you a lullaby",
"puts you to sleep"
};
// the restaurant at the end of the universe.
private Random random = new Random(42);
#Override
public void run() {
// use runlater when we mess with the scene graph,
// so we don't cross the streams, as that would be bad.
Platform.runLater(() -> {
cowerInFear.setText("The Wumpus " + nextAttack() + "!");
mainStage.sizeToScene();
mainStage.show();
});
}
private String nextAttack() {
return attacks[random.nextInt(attacks.length)];
}
}
public static void main(String[] args) {
launch(args);
}
}
Update, Jan 2020
Java 9 added a new feature called Platform.startup(), which you can use to trigger startup of the JavaFX runtime without defining a class derived from Application and calling launch() on it. Platform.startup() has similar restrictions to the launch() method (you cannot call Platform.startup() more than once), so the elements of how it can be applied is similar to the launch() discussion and Wumpus example in this answer.
For a demonstration on how Platform.startup() can be used, see Fabian's answer to How to achieve JavaFX and non-JavaFX interaction?
I use something like this, similar to other answers.
private static volatile boolean javaFxLaunched = false;
public static void myLaunch(Class<? extends Application> applicationClass) {
if (!javaFxLaunched) { // First time
Platform.setImplicitExit(false);
new Thread(()->Application.launch(applicationClass)).start();
javaFxLaunched = true;
} else { // Next times
Platform.runLater(()->{
try {
Application application = applicationClass.newInstance();
Stage primaryStage = new Stage();
application.start(primaryStage);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
try this, I tried this and found successful
#Override
public void start() {
super.start();
try {
// Because we need to init the JavaFX toolkit - which usually Application.launch does
// I'm not sure if this way of launching has any effect on anything
new JFXPanel();
Platform.runLater(new Runnable() {
#Override
public void run() {
// Your class that extends Application
new ArtisanArmourerInterface().start(new Stage());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}

Categories