So I'm trying to simply change between two scenes in javafx, but I've come into this re-occurring problem that I can't seem to fix. It is demonstrated in the following code:
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Group;
import javafx.scene.paint.Color;
public class TestApplication extends Application
{
private Stage stage;
private Scene scene, scene2;
public void start(Stage s)
{
scene=new Scene(new Group());
scene2=new Scene(new Group());
scene.setFill(Color.GREEN);
scene2.setFill(Color.ORANGE);
scene.setOnMouseClicked(e-> changeScene(scene2));
scene2.setOnMouseClicked(e-> changeScene(scene));
stage=s;
s.setScene(scene);
s.show();
}
public void changeScene(Scene nex)
{
stage.setScene(nex);
System.out.println("here");
}
public static void main(String[] args)
{
launch(args);
}
}
Am I doing something wrong? How can I fix this?
What's going wrong
You are not placing anything in the scenes (just an empty group). By default scenes are (usually) going to size to the preferred size of their contained content. As your scenes have no content of any size, then the scenes shouldn't really have any size. I think the fact that the first scene even shows up is a bit of a quirk of the JavaFX system where it seems to set some default size to the initial scene when it can't work out any preferred size for the scene (just so that the initial window shows up).
How to fix it
To fix it, put some content in the enclosed scenes (and/or set the initial scene size in your scene constructors).
import javafx.application.Application;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Group;
import javafx.scene.paint.Color;
public class TestApplication extends Application {
private Stage stage;
private Scene scene, scene2;
public void start(Stage s) {
scene = new Scene(new Group(new Label("1")), 200, 150);
scene2 = new Scene(new Group(new Label("2")), 200, 150);
scene.setFill(Color.GREEN);
scene2.setFill(Color.ORANGE);
scene.setOnMouseClicked(e -> changeScene(scene2));
scene2.setOnMouseClicked(e -> changeScene(scene));
stage = s;
s.setScene(scene);
s.show();
}
private void changeScene(Scene nex) {
stage.setScene(nex);
}
public static void main(String[] args) {
launch(args);
}
}
Related
I'm trying to write the simplest code to start learning how JavaFX works. Here is my code:
package app;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class mainClass extends Application{
Stage primaryStage;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
primaryStage = stage;
StackPane layout = new StackPane();
Scene scene = new Scene(layout, 300, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
}
It should just show an empty window. My compilator gives no errors, but nothing shows up, even if I can see my program running in my dock. How is this possible? I'm sure I'm missing some stupid simple thing, but I cannot figure out what it is.
What I want to do:
Have a slider, from 0 - 10, have it move only by a full tick(1), and show the value of the tick above the tick.
What happens:
It shows all values from 0-10 except for 9 which is mysteriously missing..
I wanted to upload an image here but I lack the reputation it seems ;)
https://pasteboard.co/HdPtzVp.png
Code:
Main Class:
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.stage.Screen;
import javafx.stage.Stage;
public class StartGui extends Application
{
#Override
public void start(Stage stage)
{
HBox box = new HBox();
PionnenSlider slider = new PionnenSlider();
box.getChildren().add(slider);
Scene scene = new Scene(box, 500, 300);
stage.setScene(scene);
stage.setTitle("SliderMinimalCode");
Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds();
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
Slider class:
import javafx.scene.control.Slider;
public class PionnenSlider extends Slider
{
public PionnenSlider()
{
setMinorTickCount(0);
setBlockIncrement(1);
setMajorTickUnit(1);
setValue(0);
setMax(10);
setShowTickMarks(true);
setShowTickLabels(true);
setSnapToTicks(true);
}
}
apparently it is due to lack of space for the slider, found a workaround to automaticly size it with HBox.setHgrow(sliderPionnen, Priority.ALWAYS);, although that just grows it to fill the entire HBOX.. so I guess I'l have to find a workaround for that
I would like to execute multiple stage operations in one frame :
stage.sizeToScene()
stage.centerOnScreen()
Currently I can see that the stage is first resized, then centered. I would like both operations to be done atomically on the same re-paint.
Here is a working example :
package sample;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application {
private HBox first = new HBox();
private HBox second = new HBox();
private Button change1 = new Button("Go to 2nd");
private Button change2 = new Button("Go to 1st");
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Hello World");
first.setSpacing(10);
first.setAlignment(Pos.CENTER_LEFT);
first.getChildren().addAll(
change1, new Label("Hello"), new Label("World")
);
second.setSpacing(10);
second.setAlignment(Pos.CENTER_LEFT);
second.getChildren().addAll(
change2, new Label("BYE MY FRIENDS, THIS IS MUCH LONGER!")
);
change1.setOnAction(event -> {
primaryStage.getScene().setRoot(second);
primaryStage.sizeToScene();
primaryStage.centerOnScreen();
});
change2.setOnAction(event -> {
primaryStage.getScene().setRoot(first);
primaryStage.sizeToScene();
primaryStage.centerOnScreen();
});
primaryStage.setScene(new Scene(first));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
To reproduce, launch the application, move the window top-left and make it bigger. Then click on the button. You will see that the window is first resized, then moved at the center.
On this example case it's really really fast, because the application is really light. But with a real-world application it's much more noticable.
When I run the following program
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Ellipse;
import javafx.stage.Stage;
public class Test1 extends Application {
public static void main(String[] args) throws Exception {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
Pane topPane = new Pane();
Scene scene = new Scene(topPane, 600, 400);
StackPane sp = new StackPane();
Label l1 = new Label("1 2");
Ellipse e1 = new Ellipse(100, 50);
e1.setOpacity(0.5);
sp.getChildren().addAll(l1, e1);
e1.radiusXProperty().bind(l1.widthProperty());
e1.radiusYProperty().bind(l1.heightProperty());
topPane.getChildren().add(sp);
sp.relocate(200, 100);
sp.setStyle("-fx-border-color: RED;");
Platform.runLater(() -> {
//l1.setText("123");
//l1.setText("1 2");
});
stage.setScene(scene);
stage.show();
}
}
I get a red box surrounding the text label only, but when I uncomment the two lines inside the Platform.runLater() block above, I get a red box surrounding the outer ellipse, which is what I want.
So it seems to me the layout bounds of the stack pane is not set correctly from the model description, since the bounds are determined only from the label control. But when I force an invalidation in Platform.runLater() the layout bounds are where they should be.
Why is this happening and how do I prevent it? I would like to be able to just specify my model/graph and then it should render correctly on the first show, or?
add this sp.requestLayout(); after stage.Show();
You can send a desktop notification with JavaFx like this (requires jdk 8u20 or later):
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
import org.controlsfx.control.Notifications;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
// Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
Button notifyButton = new Button("Notify");
notifyButton.setOnAction(e -> {
Notifications.create().title("Test").text("Test Notification!").showInformation();
});
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(notifyButton, 100, 50));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
But this way you have to create a main window (stage), is it possible to avoid this? I am looking for a way to send notification like using zenity in bash: most of the code is non-gui, but uses some gui elements for informing or interacting with user in a very basic fashion.
It looks like the ControlsFX notifications require a existing stage. You can create a hidden utility stage. Try something like this.
package sample;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.controlsfx.control.Notifications;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
}
public static void main(String[] args) {
new JFXPanel();
notifier("Good!", "It's working now!");
}
private static void notifier(String pTitle, String pMessage) {
Platform.runLater(() -> {
Stage owner = new Stage(StageStyle.TRANSPARENT);
StackPane root = new StackPane();
root.setStyle("-fx-background-color: TRANSPARENT");
Scene scene = new Scene(root, 1, 1);
scene.setFill(Color.TRANSPARENT);
owner.setScene(scene);
owner.setWidth(1);
owner.setHeight(1);
owner.toBack();
owner.show();
Notifications.create().title(pTitle).text(pMessage).showInformation();
}
);
}
}
new JFXPanel() initializes the JavaFX thread without having to extend Application. You have to call this before any calls to Platform.runLater() otherwise you will get a thread exception. You only need to call it once for the whole application though. Honestly it is probably better to create your own notification stage and display it directly. Create a stage like above and put your own contents. You can probably reuse some of the styling from the ControlsFX source.