So I have a little program where you can fly your spaceship between planets and I want to use the arrow keys to rotate the ship. First I tried adding the key listener to the panel and the ship did rotate, but only when I pressed ctrl/alt. Then I tried adding the key listener to the scene instead (which people say is the right thing to do because it doesn't depend on focus), but although the function that rotates the ship is called, you can't see anything on the screen.
The main class:
#Override
public void start(Stage stage) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root, 700, 500);
Controller controller = new Controller();
loader.setController(controller);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
stage.setScene(scene);
stage.setFullScreen(true);
stage.show();
controller.setScene(scene);
} catch(Exception e) {
e.printStackTrace();
}
}
The controller:
#FXML
private BorderPane contentPane;
private OrbiterPanel orbiterPanel = new PerfectCirclePanel(this);
private Scene scene;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
contentPane.setCenter(orbiterPanel);
}
public void setScene(Scene scene) {
this.scene = scene;
scene.addEventFilter(KeyEvent.KEY_PRESSED, e -> keyStrokesPressed(e));
scene.addEventFilter(KeyEvent.KEY_RELEASED, e -> keyStrokesReleased(e));
}
private void keyStrokesPressed(KeyEvent e) {
switch(e.getCode()) {
case LEFT -> {
orbiterPanel.leftPressed();
}
case RIGHT -> {
orbiterPanel.rightPressed();
}
default -> {}
}
}
private void keyStrokesReleased(KeyEvent e) {
switch(e.getCode()) {
case LEFT -> {
orbiterPanel.leftReleased();
}
case RIGHT -> {
orbiterPanel.rightReleased();
}
default -> {}
}
}
The superclass of all my "worlds/maps":
protected AtomicReference<Spaceship> shipReference = new AtomicReference<>();
protected RotateTransition rotatingTransition;
public OrbiterPanel() {
rotatingTransition = new RotateTransition();
rotatingTransition.setCycleCount(RotateTransition.INDEFINITE);
rotatingTransition.setInterpolator(Interpolator.LINEAR);
}
protected void rightPressed() {
if(rotatingTransition.getStatus().equals(Status.RUNNING)) return;
rotatingTransition.stop();
rotatingTransition.setNode(shipReference.get());
rotatingTransition.setByAngle(360);
rotatingTransition.setDuration(Duration.seconds(2));
rotatingTransition.play();
}
protected void leftPressed() {
if(rotatingTransition.getStatus().equals(Status.RUNNING)) return;
rotatingTransition.stop();
rotatingTransition.setNode(shipReference.get());
rotatingTransition.setByAngle(-360);
rotatingTransition.setDuration(Duration.seconds(2));
rotatingTransition.play();
}
protected void rightReleased() {
rotatingTransition.stop();
}
protected void leftReleased() {
rotatingTransition.stop();
}
PerfectCirclePanel, a subclass of OrbiterPanel doesn't really do much besides displaying the spaceship and a planet.
FXML:
<BorderPane prefHeight="607.0" prefWidth="877.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
<center>
<BorderPane fx:id="contentPane" prefHeight="704.0" prefWidth="963.0" style="-fx-background-color: black;">
</BorderPane>
</center>
</BorderPane>
There are two different ways to connect a controller to the UI defined by an FXML file.
Either:
Specify the controller class by providing a fx:controller attribute to the root element of the FXML. In this case, by default, a controller instance will be created from that class by invoking its no-argument constructor, and that instance will be the controller associated with the UI.
Or:
Call setController(...) on the FXMLLoader instance.
When you call loader.load(), if a controller exists, any #FXML-annotated fields in the controller will be initialized from the corresponding FXML elements, and then the initialize() method will be called.
In your code, you create two Controller instances: one is created from the fx:controller attribute, and associated with the UI. The other you create "by hand" in the Java code. The latter is not connected to the UI (the #FXML-annotated fields are not injected, and initialize() is not invoked), because you call setController(...) after calling load().
Because there are two Controller instances, there are two OrbiterPanel instances. The one created from the fx:controller attribute is the one displayed in the UI (because that's the one referenced when initialize() is invoked). The one created from the Controller instance you create in code is not displayed; however that is the one referenced by your event handler.
Remove the fx:controller attribute, and move the call to setController() before the call to load().
Related
I cant convert this code into scene builder...
The problem is in event handler....
I am not getting how to use the confirmCloseEventHandler event handeler in java
fx scene builder...
thanks in advance.
mainly i cant use those event handlers... in fxml controllers...
public class Javafxpopupmessage extends Application {
private Stage mainStage;
#Override
public void start(Stage stage) throws Exception {
this.mainStage = stage;
stage.setOnCloseRequest(confirmCloseEventHandler);
Button closeButton = new Button("Close Application");
closeButton.setOnAction(event ->
stage.fireEvent(
new WindowEvent(
stage,
WindowEvent.WINDOW_CLOSE_REQUEST
)
)
);
StackPane layout = new StackPane(closeButton);
layout.setPadding(new Insets(100));
stage.setScene(new Scene(layout));
stage.show();
}
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
Alert closeConfirmation = new Alert(
Alert.AlertType.CONFIRMATION,
"Are you sure you want to exit?"
);
Button exitButton = (Button)
closeConfirmation.getDialogPane().lookupButton(
ButtonType.OK
);
exitButton.setText("Exit");
closeConfirmation.setHeaderText("Confirm Exit");
closeConfirmation.initModality(Modality.APPLICATION_MODAL);
closeConfirmation.initOwner(mainStage);
// normally, you would just use the default alert positioning,
// but for this simple sample the main stage is small,
// so explicitly position the alert so that the main window can still be
seen.
// closeConfirmation.setX(mainStage.getX());
//closeConfirmation.setY(mainStage.getY() + mainStage.getHeight());
Optional<ButtonType> closeResponse = closeConfirmation.showAndWait();
if (!ButtonType.OK.equals(closeResponse.get())) {
event.consume();
}
};
public static void main(String[] args) {
launch(args);
}
}
Registering some handler for the primary stage via fxml could only be done with a bad hack, since FXMLLoader only has access to objects it creates itself.
You could add a listener to the Node.scene property of some node in your scene and add a listener to the window property of that scene as soon as it's set and access the window as soon as it's assigned, which is quite complex for something that could be done using much simpler code in the start method.
Other than that hack you won't get around registering that event handler in the start method (or passing the Stage to the controller resulting in more complex code than the one posted).
As for the close button onAction event: You can use a method of your controller as handler:
fxml
<StackPane xmlns:fx="http://javafx.com/fxml" fx:controller="mypackage.MyController">
<children>
<Button text="Close Application" onAction="#close"/>
</children>
<padding>
<Insets topRightBottomLeft="100"/>
</padding>
</StackPane>
controller
package mypackage;
...
public class MyController {
#FXML
private void close(ActionEvent event) {
Node source = (Node) event.getSource();
Window window = source.getScene().getWindow();
window.fireEvent(new WindowEvent(
window,
WindowEvent.WINDOW_CLOSE_REQUEST));
}
}
I've used Scenebuilder to place few shapes in my GUI (simplified version of my project). I would like the shapes to change colours but wait 2 seconds between changing colours. I want these changes to happen in my controller class after a button is pressed.
Circle1.setFill(YELLOW)
Wait(2 seconds)
Circle2.setFill(BLUE)
I'm not sure how to do that. I have read online about threading, but I don't really understand how to implement that from my Main and into my Controller class. Also, I could not really find any examples online. My Main class looks like:
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = (BorderPane)FXMLLoader.load(getClass().getResource("File.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
}
Please help. Also, if you could provide an example would be helpful for me to understand as I could not really find one online that gives an example of this.
Answering this question is easiest through an example I believe. So I've created a small Traffic Light application, since it allows me to use Circle and a timed sequence similar to your problem, whilst being a familiar concept for all.
I'll be using java.util.Timer alongside java.util.TimerTask for handling the sequence of lights. You may choose to use some animation / time line in JavaFX, but I think that is overkill for this kind of task.
I include the three files used in this project:
FXMLTrafficLight.fxml - which defines my FXML layout
FXMLTrafficLightController.java - my FXML controller
TrafficLightApplication.java - for completeness my subclass of Application, this is just the boiler plate.
FXMLTrafficLight.fxml
Not a fancy layout, just a VBox with three circles redLight, amberLight and greenLight, plus two Button objects startLights and stopLights used to start and stop the timer.
<VBox fx:id="root" id="VBox" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxtimer.FXMLTrafficLightController">
<children>
<Circle fx:id="redLight" radius="100"></Circle>
<Circle fx:id="amberLight" radius="100"></Circle>
<Circle fx:id="greenLight" radius="100"></Circle>
<Button fx:id="startLights" text="Start Lights" onAction="#startLights"></Button>
<Button fx:id="stopLights" text="Start Lights" onAction="#stopLights"></Button>
</children>
</VBox>
FXMLTrafficLightController.java
I've included the model/state in the controller for simplicity. Whether a light is red / amber / green is determined by a boolean flag. The initial state is set in the initialize() method, and is updated by calling updateState().
When startLights(ActionEvent) is invoked (the EventHandler for startLights) a new Timer is constructed with a TimerTask implementation that first invokes updateState() on the thread created by the Timer and then invokes updateLights() which changes the color of the lights based on the current state on the JavaFX Application Thread using Platform.runLater(Runnable).
Note: the TimerTask itself will not be run on the JavaFX Application Thread, hence the need to use Platform.runLater(Runnable) for updating the GUI.
When stopLights(ActionEvent) is invoked, it will cancel the Timer.
Note that both startLights(ActionEvent) and stopLights(ActionEvent) toggle which Button objects are enabled on the interface as well.
public class FXMLTrafficLightController implements Initializable {
#FXML
private Circle redLight;
#FXML
private Circle amberLight;
#FXML
private Circle greenLight;
#FXML
private Button startLights;
#FXML
private Button stopLights;
private Timer timer;
private static final int DELAY = 2000; // ms
private boolean red, amber, green;
#Override
public void initialize(URL url, ResourceBundle rb) {
red = true;
amber = false;
green = false;
stopLights.setDisable(true);
updateLights();
}
#FXML
private void startLights(ActionEvent e) {
toggleButtons();
timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
// Not run on the JavaFX Application Thread!
updateState();
// Using Platform.runLater(Runnable) to ensure updateLights()
// is run on the JavaFX Application Thread
Platform.runLater(new Runnable() {
#Override
public void run() {
updateLights();
}
});
}
}, 0, DELAY); // no initial delay, trigger again every 2000 ms (DELAY)
}
#FXML
private void stopLights(ActionEvent e) {
toggleButtons();
timer.cancel();
}
private void toggleButtons() {
startLights.setDisable(!startLights.isDisable());
stopLights.setDisable(!stopLights.isDisable());
}
private void updateState() {
if (red && !amber && !green) {
amber = true;
} else if (red && amber && !green) {
red = false;
amber = false;
green = true;
} else if (!red && !amber && green) {
green = false;
amber = true;
} else {
red = true;
amber = false;
green = false;
}
}
private void updateLights() {
redLight.setFill(red ? Color.RED : Color.GREY);
amberLight.setFill(amber ? Color.ORANGE : Color.GREY);
greenLight.setFill(green ? Color.GREEN : Color.GREY);
}
}
TrafficLightApplication.java
For completeness... Just the standard boiler plate with file names changed.
public class TrafficLightApplication extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLTrafficLight.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have written a controller for two windows /stages.
The first window is opened in the MainClass. The second in the Controller, if the user clicks onto a button.
How can I get the TextFields from second.fxml in the applyFor()-method?
Thanks.
#FXML
protected void requestNewAccount(ActionEvent event) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("second.fxml")); // TextFields in there
Parent root = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle("Second Window");
Scene scene = new Scene(root);
String css = MainOnlineCustomer.class.getResource("/style.css").toExternalForm();
scene.getStylesheets().clear();
scene.getStylesheets().add(css);
stage.setScene(scene);
stage.show();
} catch (IOException e) {
logger.error(e);
}
}
/**
* closes the "second"-Window
* #param event
*/
#FXML
protected void cancel(ActionEvent event) {
final Node source = (Node) event.getSource();
final Stage stage = (Stage) source.getScene().getWindow();
stage.close();
}
#FXML
protected void applyFor(ActionEvent event) {
// get values from TextField in second.fxml here!!!
}
It's not good to share controllers between fxmls unless they serve the same purpose. Here both fxml seem to serve a different purpose (account management, login or something similar for one of them and creating a new account for the other). What is even worse is that those classes do not share the same controller instance, which means the small (and probably only) benefit you could get from using the same controller, is not used here. You should better use different controllers.
Since you use Modality.APPLICATION_MODAL as modality, I'd recommend using showAndWait instead of show to open the new stage. This will enter a nested event loop, which allows the UI to remain responsive and continues after the invocation of showAndWait once the stage is closed.
Furthermore add a method to the controller of second.fxml that allows you to retrieve the result.
Example
This creates a Person object with given name and family name.
"primary window (opening the "inner" stage)
FXMLLoader loader = new FXMLLoader(getClass().getResource("second.fxml"));
Stage subStage = new Stage();
subStage.initModality(Modality.APPLICATION_MODAL);
subStage.setTitle("Second Window");
Scene scene = new Scene(loader.load());
subStage.setScene(scene);
subStage.showAndWait();
Optional<Person> result = loader.<Supplier<Optional<Person>>>getController().get();
if (result.isPresent()) {
// do something with the result
}
controller for "inner" content
public class SecondController implements Supplier<Optional<Person>> {
#FXML
private TextField givenName;
#FXML
private TextField familyName;
private boolean submitted = false;
// handler for submit action
#FXML
private void submit() {
submitted = true;
givenName.getScene().getWindow().hide();
}
// handler for cancel action
#FXML
private void cancel() {
givenName.getScene().getWindow().hide();
}
#Override
public Optional<Person> get() {
return submitted ? Optional.of(new Person(givenName.getText(), familyName.getText())) : Optional.empty();
}
}
Note that you can gain access to any data available to the controller this way. I wouldn't recommend accessing any nodes (like TextFields) directly though, since this makes changing the UI harder.
Using the Supplier interface here is not necessary, but I chose to do this to achieve a loose coupling between SecondController and the main window.
This seems like it should be easy, so I must be missing something obvious: I have 4 standalone applications in the same package, us.glenedwards.myPackage,
myClass1 extends Application
myClass2 extends Application
etc...
I need each class to act as its own standalone application. Yet I want to be able to start the other 3 classes from the one I'm in by clicking a link. Android allows me to do this using Intents:
Intent intent = new Intent(this, EditData.class);
overridePendingTransition(R.layout.edit_data_scrollview, R.layout.state);
startActivity(intent);
I've tried starting myClass2 from myClass1 using
myClass2.launch("");
But I get an error, "Application launch must not be called more than once". The only way I can get it to work is if I remove both "extends application" and the start() method from myClass2, which means that myClass2 is no longer a standalone application.
How can I start myClass2, myClass3, or myClass4 from myClass1 with all 4 of them being standalone applications?
You can make this work by calling start(...) directly on a new instance of one of the Application subclasses, but it kind of feels like a bit of a hack, and is contrary to the intended use of the start(...) method. (Just semantically: a method called start in a class called Application should be executed when your application starts, not at some arbitrary point after it is already running.)
You should really think of the start method as the replacement for the main method in a traditional Java application. If you had one application calling another application's main method, you would (hopefully) come to the conclusion that you had structured things incorrectly.
So I would recommend refactoring your design so that your individual components are not application subclasses, but just plain old regular classes:
public class FirstModule {
// can be any Parent subclass:
private BorderPane view ;
public FirstModule() {
// create view; you could also just load some FXML if you use FXML
view = new BorderPane();
// configure view, populate with controls, etc...
}
public Parent getView() {
return view ;
}
// other methods as needed...
}
and, similarly,
public class SecondModule {
private GridPane view ;
public SecondModule {
view = new GridPane();
// etc etc
}
public Parent getView() {
return view ;
}
}
Now you can just do things like
FirstModule firstModule = new FirstModule();
Scene scene = new Scene(firstModule.getView());
Stage stage = new Stage();
stage.setScene(scene);
stage.show();
anywhere you need to do them. So you can create standalone applications for each module:
public class FirstApplication extends Application {
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new FirstModule().getView());
primaryStage.setScene(scene);
primaryStage.show();
}
}
or you can instantiate them as part of a bigger application:
public class CompositeModule {
private HBox view ;
public CompositeModule() {
Button first = new Button("First Module");
first.setOnAction(e -> {
Parent view = new FirstModule().getView();
Scene scene = new Scene(view);
Stage stage = new Stage();
stage.initOwner(first.getScene().getWindow());
stage.setScene(scene);
stage.show();
});
Button second = new Button("Second Module");
second.setOnAction(e -> {
Parent view = new SecondModule().getView();
Scene scene = new Scene(view);
Stage stage = new Stage();
stage.initOwner(second.getScene().getWindow());
stage.setScene(scene);
stage.show();
});
HBox view = new HBox(10, first, second);
view.setAlignment(Pos.CENTER);
}
public Parent getView() {
return view ;
}
}
and
public class CompositeApplication extends Application {
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new CompositeModule().getView(), 360, 150);
primaryStage.setScene(scene);
primaryStage.show();
}
}
The way I think of this is that Application subclasses represent an entire running application. Consequently it makes sense only to ever instantiate one such class once per JVM, so you should consider these inherently not to be reusable. Move any code you want to reuse into a different class somewhere.
have you tried this?
Runtime.getRuntime().exec("myClass1 [args]"); //put all args as you used in command
Also, handle/catch the exceptions, as needed.
I was right; it was a no-brainer. That's what I get for writing code on 4 hours of sleep:
myClass2 class2 = new myClass2();
try {
class2.start(stage);
} catch (Exception e) { e.printStackTrace(); } }
#FXML
private Pane pane;
#Override
public void initialize(URL location, ResourceBundle resources) {
pane.getScene().setOnKeyPressed(....);
}
I want set the scene event on the FXMLController class , what should i do?
The scene won't be set on the pane until the root element of the FXML is added to a scene graph. In the controller, you have no control over when that will happen, but it has to happen after the initialize() method has completed.
The best approach here is to find some other way to register the event; e.g. do you really want to register it on the scene: can you register it on the root element of the FXML-generated node graph instead?
If you really need to access the scene from the controller, you need to register a listener with the scene property of one of the nodes and set the key pressed handler when the scene is initialized. To be really bullet-proof, you should handle the possibility that your pane may be removed from a scene at some point.
public void initialize() {
EventHandler<KeyEvent> sceneKeyPressedHandler = ... ;
pane.sceneProperty().addListener((ov, oldScene, newScene) -> {
if (oldScene != null) {
oldScene.removeEventHandler(KeyEvent.KEY_PRESSED, sceneKeyPressedHandler);
}
if (newScene != null) {
newScene.addEventHandler(KeyEvent.KEY_PRESSED, sceneKeyPressedHandler);
}
}
// ...
}