How can I use the scene on FXML controller - javafx

#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);
}
}
// ...
}

Related

JavaFX KeyEvent doesn't do anything

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().

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 can I use .setText on a non-Static Label from a different Class [duplicate]

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.

JavaFX secondary stage onCloseRequest

I have a little issue close a secondary stage after clicking the close at the top right corner.
I'm using fxml with controller class, i need a way to handle this situation.
Here is what i do but i get a nullpointer exception :
#Override
public void initialize(URL location, ResourceBundle resources) {
Stage stage = (Stage) tbTabPaneHome.getScene().getWindow();
stage.setOnCloseRequest(e -> {
Platform.exit();
System.exit(0);
});
}
Because the stage not yet intialized completly, so any other ideas ?
Since the Scene nor the Stage are created yet, you can't call them or you get a NPE, as you already mentioned.
One way to install the event handler on the stage will be listening to changes in the sceneProperty() of tbTabPaneHome.
Once the node is added to the scene, that property will give you the Scene instance.
But the scene is not added to the Stage yet, so you need to wait till this is done, with Platform.runLater():
public void initialize() {
tbTabPaneHome.sceneProperty().addListener((obs, oldScene, newScene) -> {
Platform.runLater(() -> {
Stage stage = (Stage) newScene.getWindow();
stage.setOnCloseRequest(e -> {
Platform.exit();
System.exit(0);
});
});
});
}
Did you try to deal with your secondary stage entirely in the main stage controller?
I want to hide or show a help windows from a button or help menu in my main application controller. Something like the following:
public Button helpBtn;
Stage anotherStage = new Stage();
boolean secondaryInitialyzed = false;
boolean secondaryShowing = false;
public void showOrHideHelp(ActionEvent actionEvent) throws IOException {
if (!secondaryInitialyzed){
Parent anotherRoot = FXMLLoader.load(getClass().getResource("mySecondaryStage.fxml"));
anotherStage.setTitle("Secondary stage");
Scene anotherScene = new Scene(anotherRoot, 500, 350);
anotherStage.setScene(anotherScene);
secondaryInitialyzed = true;
}
if (secondaryShowing){
anotherStage.hide();
secondaryShowing = false;
helpBtn.setText("Show Help");
}
else {
anotherStage.show();
secondaryShowing = true;
helpBtn.setText("Hide Help");
}
It does work, and there might be a way for you to handle your setOnCloseRequest within the main controller.
I have the opposing issue, i.e preventing closing the secondary stage window by clicking the close at the top right corner. I'll look into setOnCloseRequest and see if there is a way there.
I also have an other unrelated problem: can I position the secondary in reference to the primary one?

JavaFX 2. How to stop or pause all animations?

I am working on creating a small game.
I use scene switching (scene with a main game's screen and scene with some Help information). So, I decided to use HashMap scenes and set all scenes there. And clicking the button "Help" causes the scene switching.
But I have an issue. I want to stop all animations on non-active scene and currently I just set scene as NULL. I know that it is very bad realization.
Could somebody help me and also explain how to pause or stop all animations on scene?
Setting scene on primaryStage:
public static void setSceneToStage(String sceneId, Stage stage) {
SceneCollection.instance().clearCurrentActiveScene();
SceneCollection.instance().setNewActiveScene(sceneId);
stage.setScene(SceneCollection.instance().getScene(sceneId));
stage.setTitle(sceneId);
}
Clearing non-active scene:
public void clearCurrentActiveScene() {
if(activeScene != null) {
scenes.get(activeScene).clearScene();
}
}
public void clearScene() {
scene = null;
}
Initialize new scene:
public void setNewActiveScene(String sceneId) {
activeScene = sceneId;
scenes.get(sceneId).init();
}
public void init() {
scene = new Scene(new Pane(), 300, 300);
}
I think your program has to have a structure that you can have access to all transitions at time, for example you can have ''TransitonManager Class'' and in that class you have a arraylist of transitions and then you can add this transitions to that in their constructors and then when ever you want , you can have access to all transitions and stop them.
class TransitionManger{
public static ArrayList<Transition> transitions = new ArrayList<>();
}
class CustomTransition extends Transition{
CustomTransition(){
TransitionManger.transitions.add(this);
}
#Override
protected void interpolate(double v) {
//todo do what you want
}
}
you can also use singleton design pattern for TransitionManger for better encapsulation.

Resources