JavaFX Window dragging snaps and is not smooth - javafx

I am working on an application that begins with an TRANSPARENT AnchorPane (no title bar and round corners). I want to be able to drag and move the window around. I have gotten it to work, but when I click it, the window snaps upwards to where you are dragging from the center instead of where you click.
CSS:
.root {
-fx-background-radius: 20;
-fx-border-radius: 20;
-fx-background-color: transparent;
}
Main.java:
public void start(Stage primaryStage) throws Exception {
primaryStage.initStyle(StageStyle.TRANSPARENT);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("../Scenes/Login.fxml"));
//Creates the layout for the new scene
AnchorPane layout = (AnchorPane) loader.load();
Scene scene = new Scene(layout);
scene.setFill(Color.TRANSPARENT);
scene.getStylesheets().add(getClass().getResource("../StyleSheets/application.css").toExternalForm());
LoginController.allowDrag(layout, primaryStage);
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
Controller:
private static final Rectangle2D SCREEN_BOUNDS = Screen.getPrimary().getVisualBounds();
public static void allowDrag(AnchorPane root, Stage primaryStage) {
root.setOnMousePressed((MouseEvent mouseEvent1) -> {
xOffset = mouseEvent1.getSceneX();
yOffset = mouseEvent1.getScreenY();
});
root.setOnMouseDragged((MouseEvent mouseEvent2)-> {
if (!mouseEvent2.isPrimaryButtonDown()) return;
//Ensures the stage is not dragged past the taskbar
if (mouseEvent2.getScreenY()<(SCREEN_BOUNDS.getMaxY()-20))
primaryStage.setY(mouseEvent2.getScreenY() - yOffset);
primaryStage.setX(mouseEvent2.getScreenX() - xOffset);
primaryStage.setY(mouseEvent2.getScreenY() - yOffset);
});
root.setOnMouseReleased((MouseEvent mouseEvent3)-> {
//Ensures the stage is not dragged past top of screen
if (primaryStage.getY()<0.0) primaryStage.setY(0.0);
});
}
I have a feeling that I need to account for where the cursor is, but I am not sure how to. Am I correct or is there something easier I am missing?

Yes! you're right! And I have a simpler workaround for you to do so :)
Add the following code in your Main.java class,
private double gapX = 0, gapY = 0;
private void calculateGap(MouseEvent event, Stage stage) {
gapX = event.getScreenX() - stage.getX();
gapY = event.getScreenY() - stage.getY();
}
private void dragStage(MouseEvent event, Stage stage) {
stage.setX(event.getScreenX() - gapX);
stage.setY(event.getScreenY() - gapY);
}
calculateGap(MouseEvent event, Stage stage) as method-name says, it calculates the gap between MouseEvent and Stage coordinates.
dragStage(MouseEvent event, Stage stage) It lets you drag your stage based on the MouseEvent and the calculated-gap.
Set these EventHandlers on your parent root layout in start() method,
layout.setOnMouseDragged(e -> this.dragStage(e, primaryStage));
layout.setOnMouseMoved(e -> this.calculateGap(e, primaryStage));
Now you can drag your window smoothly :)

Well done that is complete solution of this problem. if you want to drag window of javafx login then you can use this.
package com.systems.auth;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
import javafx.scene.input.MouseEvent;
import javafx.stage.StageStyle;
/**
* JavaFX App
*/
public class App extends Application {
private static Scene scene;
private double gapX = 0, gapY = 0;
#Override
public void start(Stage stage) throws IOException {
Parent root = loadFXML("login");
scene = new Scene(root, 700, 500);
stage.setResizable(false);
stage.initStyle(StageStyle.DECORATED.UNDECORATED);
root.setOnMouseDragged(e -> this.dragStage(e, stage));
root.setOnMouseMoved(e -> this.calculateGap(e, stage));
stage.setScene(scene);
stage.show();
}
private void calculateGap(MouseEvent event, Stage stage) {
gapX = event.getScreenX() - stage.getX();
gapY = event.getScreenY() - stage.getY();
}
private void dragStage(MouseEvent event, Stage stage) {
stage.setX(event.getScreenX() - gapX);
stage.setY(event.getScreenY() - gapY);
}
static void setRoot(String fxml) throws IOException {
scene.setRoot(loadFXML(fxml));
}
private static Parent loadFXML(String fxml) throws IOException {
System.out.print(App.class.getResource(fxml + ".fxml"));
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
return fxmlLoader.load();
}
public static void main(String[] args) {
launch();
}
}

Related

Set divider position of SplitPane

I want to set the divider of a SplitPane to a certain default position. This does not work, the divider stays in the middle:
public void start(Stage primaryStage) throws Exception{
SplitPane splitPane = new SplitPane(new Pane(), new Pane());
// Report changes to the divider position
splitPane.getDividers().get(0).positionProperty().addListener(
o -> System.out.println(splitPane.getDividerPositions()[0])
);
// Doesn't work:
splitPane.setDividerPositions(0.8);
// The docs seem to recommend the following (with floats instead of
// doubles, and with one number more than there are dividers, which is
// weird), but it doesn't work either:
//splitPane.setDividerPositions(0.8f, 0.2f);
primaryStage.setScene(new Scene(splitPane));
primaryStage.setMaximized(true);
primaryStage.show();
}
The output:
0.8
0.5
It suggests that something resets it to the middle.
How can I achieve this?
The issue seems to be that the divider position is reset when the SplitPane width is set during when the Stage is maximized. Set the divider positions afterwards by listening to the window's showing property as follows:
primaryStage.showingProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
splitPane.setDividerPositions(0.8);
observable.removeListener(this);
}
}
});
During Stage initialization, window size changes several times until layout is completed. Every change modifies divider positions. If you want to control divider positions, they have to be set after Stage is fully initialized:
private boolean m_stageShowing = false;
#Override
public void start(Stage primaryStage) throws Exception {
SplitPane splitPane = new SplitPane(new Pane(), new Pane());
ChangeListener<Number> changeListener = new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
splitPane.setDividerPositions(0.8);
if (m_stageShowing) {
observable.removeListener(this);
}
}
};
splitPane.widthProperty().addListener(changeListener);
splitPane.heightProperty().addListener(changeListener);
primaryStage.setScene(new Scene(splitPane));
primaryStage.setMaximized(true);
primaryStage.show();
m_stageShowing = true;
}
I had the same problem and the above solutions where not working reliably for me.
So I created a custom skin for SplitPane:
public class DumbSplitPaneSkin extends SplitPaneSkin {
public DumbSplitPaneSkin(SplitPane splitPane) {
super(splitPane);
}
#Override
protected void layoutChildren(double x, double y, double w, double h) {
double[] dividerPositions = getSkinnable().getDividerPositions();
super.layoutChildren(x, y, w, h);
getSkinnable().setDividerPositions(dividerPositions);
}
}
This skin can be used via css or by overriding SplitPane.createDefaultSkin(). You can also set programatically as splitPane.setSkin(new DumbSplitPaneSkin(splitPane));
As pointed out by others the issue is that the divider position is reset when the Stage is maximized.
You can prevent this by setting ResizableWithParent to false.
Example
Let's say you have a SplitPane with two nested containers inside. Here is the fxml extract:
<SplitPane fx:id="splitPane" dividerPositions="0.25">
<VBox fx:id="leftSplitPaneContainer" />
<FlowPane fx:id="rightSplitPaneContainer"/>
</SplitPane>
And here is the extract from the controller class:
#FXML
private SplitPane splitPane;
#FXML
private VBox leftSplitPaneContainer;
#FXML
private FlowPane rightSplitPaneContainer;
Then you simply can call SplitPane.setResizableWithParent() on both containers to prevent resetting the divider position:
public void initialize(){
SplitPane.setResizableWithParent(leftSplitPaneContainer, false);
SplitPane.setResizableWithParent(rightSplitPaneContainer, false);
}
The divider position will now remain at 0.25 even if you maximize the window.
No complicated listeners or overwriting of SplitPaneSkin involved.
You could just wrap the call setDividerPositions with
Platform.runLater(new Runnable() {
#Override
public void run() {
splitPane.setDividerPositions(0.8);
}
});
This is not 100% reliable solution because run() method is performed in JFX thread at unspecified time but it works properly for simple initialization cases.
Here is my result:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.*;
/**
* SplitPane, Dialogbox example
* #author Pataki István
*/
public class SimpleDocking extends Application {
private double splitPosition = 0;
private SplitPane rootPane = new SplitPane();
private MyDialog dialog;
private BorderPane dockedArea;
#Override
public void start(final Stage stage) throws Exception {
rootPane.setOrientation(Orientation.VERTICAL);
rootPane.setBorder(new Border(new BorderStroke(
Color.GREEN,
BorderStrokeStyle.SOLID,
new CornerRadii(5),
new BorderWidths(3))
));
dockedArea = new BorderPane(new TextArea("Some docked content"));
final FlowPane centerArea = new FlowPane();
final Button undockButton = new Button("Undock");
centerArea.getChildren().add(undockButton);
rootPane.getItems().addAll(centerArea, dockedArea);
stage.setScene(new Scene(rootPane, 300, 300));
stage.show();
dialog = new MyDialog(stage);
undockButton.disableProperty().bind(dialog.showingProperty());
undockButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
handler(stage);
}
});
}
private void handler(Stage stage) {
splitPosition = rootPane.getDividerPositions()[0];
rootPane.getItems().remove(dockedArea);
dialog.setOnHidden(windowEvent -> {
rootPane.getItems().add(dockedArea);
rootPane.setDividerPositions(splitPosition);
});
dialog.setContent(dockedArea);
dialog.show(stage);
}
private class MyDialog extends Popup {
private BorderPane root;
private MyDialog(Window parent) {
root = new BorderPane();
root.setPrefSize(200, 200);
root.setStyle("-fx-border-width: 1; -fx-border-color: gray");
root.setTop(buildTitleBar());
setX(parent.getX() + 50);
setY(parent.getY() + 50);
getContent().add(root);
}
public void setContent(Node content) {
root.setCenter(content);
}
private Node buildTitleBar() {
BorderPane pane = new BorderPane();
pane.setStyle("-fx-background-color: burlywood; -fx-padding: 5");
final Delta dragDelta = new Delta();
pane.setOnMousePressed(mouseEvent -> {
dragDelta.x = getX() - mouseEvent.getScreenX();
dragDelta.y = getY() - mouseEvent.getScreenY();
});
pane.setOnMouseDragged(mouseEvent -> {
setX(mouseEvent.getScreenX() + dragDelta.x);
setY(mouseEvent.getScreenY() + dragDelta.y);
});
Label title = new Label("My Dialog");
title.setStyle("-fx-text-fill: midnightblue;");
pane.setLeft(title);
Button closeButton = new Button("X");
closeButton.setOnAction(actionEvent -> hide());
pane.setRight(closeButton);
return pane;
}
}
private static class Delta {
double x, y;
}
public static void main(String[] args) throws Exception {
launch();
}
}
This is works like you wish.
Since you usually have at least something in splitpane, eg. vbox, just set min and max width and it will automatically set divider.
Platform.runlater(()->splitpane.setDividerPosition(0,0.8));
Absolutely does the trick for me. This sets the first split position of my horizontal splitpane to 80% of the parents width when opening the window.
While runlater() in many cases can lead JavaFX to do a little visible jitter at times, depending on the complexity of your GUI, in my case I haven't seen this happen.
Try
splitPane.setDividerPosition(0, percentage);
The parameters are setDividerPosition(int dividerIndex, double percentage)

I can't get a transparent stage in JavaFX

Here is my code, I'm trying to load a splash screen image with transparent background before my main stage starts. They come almost at the same time, but the big problem is I get a grey rectangle before anything else: .
Here is the code:
public class Menu extends Application {
private Pane splashLayout;
private Stage mainStage;
private ImageView splash;
// Creating a static root to pass to ScreenControl
private static BorderPane root = new BorderPane();
public void start(Stage splashStage) throws IOException {
final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
this.splash = new ImageView(new Image(getClass().getResource("/splash.png").toString()));
splashStage.initStyle(StageStyle.TRANSPARENT);
showSplash(splashStage, screenSize);
// Constructing our scene using the static root
root.setCenter(new ScrollPane);
Scene scene = new Scene(root, screenSize.getWidth(), screenSize.getHeight());
showMainStage(scene);
if (splashStage.isShowing()) {
mainStage.setIconified(false);
splashStage.toFront();
FadeTransition fadeSplash = new FadeTransition(Duration.seconds(1.5), splashLayout);
fadeSplash.setDelay(Duration.seconds(3.5));
fadeSplash.setFromValue(1.0);
fadeSplash.setToValue(0.0);
fadeSplash.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
splashStage.hide();
}
});
fadeSplash.play();
}
}
private void showMainStage(Scene scene) {
mainStage = new Stage(StageStyle.DECORATED);
mainStage.setTitle("book-depot");
mainStage.getIcons().add(new Image(getClass().getResourceAsStream("/icon.png")));
mainStage.setScene(scene);
mainStage.show();
}
private void showSplash(Stage splashStage, Dimension screenSize) {
splashLayout = new StackPane();
splashLayout.setStyle("-fx-background-color: transparent;");
splashLayout.getChildren().add(splash);
Scene splashScene = new Scene(splashLayout, 690, 590);
splashScene.setFill(Color.TRANSPARENT);
splashStage.setScene(splashScene);
splashStage.show();
}
public void mainGui(String[] args) {
launch(args);
}
}
Am I doing something wrong or I really can't get a transparent background?
This is what it looks like when also the other stage loads up, but I'd like it to work like that even before the main stage loads, or at least I'd want to remove the grey rectangle you can see in the other screenshot
The grey background is your "mainStage" since you are showing splash and main stages at the same time. At the beginning while showing the splash stage you can just init (not show) the main stage and show it later when the animation finishes:
public class ModifiedMenu extends Application
{
private Pane splashLayout;
private Stage mainStage;
private ImageView splash;
// Creating a static root to pass to ScreenControl
private static BorderPane root = new BorderPane();
public void start(Stage splashStage) throws IOException {
final Dimension2D screenSize = Toolkit.getDefaultToolkit().getScreenSize();
this.splash = new ImageView(new Image(getClass().getResource("/splash.png").toString()));
splashStage.initStyle(StageStyle.TRANSPARENT);
showSplash(splashStage, screenSize);
// Constructing our scene using the static root
root.setCenter(new ScrollPane());
Scene scene = new Scene(root, screenSize.getWidth(), screenSize.getHeight());
initMainStage(scene);
if (splashStage.isShowing()) {
splashStage.toFront();
FadeTransition fadeSplash = new FadeTransition(Duration.seconds(1.5), splashLayout);
fadeSplash.setDelay(Duration.seconds(3.5));
fadeSplash.setFromValue(1.0);
fadeSplash.setToValue(0.0);
fadeSplash.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
splashStage.hide();
mainStage.show();
}
});
fadeSplash.play();
}
}
private void initMainStage(Scene scene) {
mainStage = new Stage(StageStyle.DECORATED);
mainStage.setTitle("book-depot");
mainStage.getIcons().add(new Image(getClass().getResourceAsStream("/icon.png")));
mainStage.setScene(scene);
}
private void showSplash(Stage splashStage, Dimension2D screenSize) {
splashLayout = new StackPane();
splashLayout.setStyle("-fx-background-color: transparent;");
splashLayout.getChildren().add(splash);
Scene splashScene = new Scene(splashLayout, 690, 590);
splashScene.setFill(Color.TRANSPARENT);
splashStage.setScene(splashScene);
splashStage.show();
}
public void mainGui(String[] args) {
launch(args);
}
}

Change icon on mouse over

I want to create button which changes the default picture when I move mouse over. I made this example but it's not working properly:
public class MainApp extends Application
{
#Override
public void start(Stage stage) throws Exception
{
StackPane bp = new StackPane();
bp.getChildren().add(ReportsIcon());
bp.setPrefSize(600, 600);
Scene scene = new Scene(bp);
scene.setFill(Color.ANTIQUEWHITE);
stage.setTitle("JavaFX and Maven");
stage.setScene(scene);
stage.show();
}
private static final ImageView ReportsFirstIcon;
static
{
ReportsFirstIcon = new ImageView(MainApp.class.getResource("/images/monitoring-colour.png").toExternalForm());
}
private static final ImageView RportsIconsSecond;
static
{
RportsIconsSecond = new ImageView(MainApp.class.getResource("/images/monitoring-green.png").toExternalForm());
}
private HBox ReportsIcon()
{
HBox bpi = new HBox();
bpi.setAlignment(Pos.CENTER);
// Add Label to the Icon
Text inftx = new Text("Reports");
inftx.setFont(Font.font("Verdana", FontWeight.NORMAL, 13)); // Set font and font size
inftx.setFill(Color.BLACK); // Set font color
// Zoom into the picture and display only selected area
Rectangle2D viewportRect = new Rectangle2D(0, 0, 0, 0);
ReportsFirstIcon.setViewport(viewportRect);
BorderPane pp = new BorderPane();
pp.setCenter(ReportsFirstIcon);
bpi.getChildren().addAll(pp, inftx);
bpi.setOnMouseEntered(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent t)
{
pp.setCenter(ReportsFirstIcon);
}
});
bpi.setOnMouseExited(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent t)
{
pp.setCenter(RportsIconsSecond);
}
});
bpi.setOnMouseClicked(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent t)
{
// Open new window
}
});
return bpi;
}
private HBox mouseOver(final HBox bp)
{
bp.setOnMouseEntered(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent t)
{
bp.setStyle("-fx-background-color: linear-gradient(#f2f2f2, #f2f2f2);"
+ " -fx-background-insets: 0 0 -1 0, 0, 1, 2;"
+ " -fx-background-radius: 3px, 3px, 2px, 1px;");
}
});
bp.setOnMouseExited(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent t)
{
bp.setStyle("-fx-background-color: linear-gradient(#f2f2f2, #d4d4d4);"
+ " -fx-background-insets: 0 0 -1 0, 0, 1, 2;"
+ " -fx-background-radius: 3px, 3px, 2px, 1px;");
}
});
return bp;
}
public static void main(String[] args)
{
launch(args);
}
}
Now the code in not working properly the original image is not returned back when I move the mouse outside of the Second BorderPane which is used to hold the picture.
Picture is changed when I move the mouse outside of the stage. Any ideas how to fix this?
I want to show by default the first picture and when I move the mouse over it to replace it with the second. When I move the mouse outside I want to restore the original picture.
Solution Approach
You can bind the button's graphic property to an appropriate ImageView based upon the button's hover property.
button.graphicProperty().bind(
Bindings.when(
button.hoverProperty()
)
.then(meatView)
.otherwise(lambView)
);
Unhovered:
Hovered:
Executable Sample
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.StackPane;
import javafx.scene.text.*;
import javafx.stage.Stage;
public class MuttonMorph extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
ImageView lambView = new ImageView(
new Image(
lambLoc
)
);
ImageView meatView = new ImageView(
new Image(
meatLoc
)
);
Button button = new Button("Lamb,\nit's what's for dinner");
button.setContentDisplay(ContentDisplay.TOP);
button.setTextAlignment(TextAlignment.CENTER);
button.setFont(Font.font(16));
button.graphicProperty().bind(
Bindings.when(
button.hoverProperty()
)
.then(meatView)
.otherwise(lambView)
);
StackPane layout = new StackPane(button);
layout.setPadding(new Insets(30));
stage.setScene(new Scene(layout));
stage.show();
}
// Icons are Linkware (Backlink to http://icons8.com required)
private static final String lambLoc = "http://icons.iconarchive.com/icons/icons8/ios7/96/Animals-Sheep-icon.png";
private static final String meatLoc = "http://icons.iconarchive.com/icons/icons8/ios7/96/Food-Lamb-Rack-icon.png";
}
Alternate Approach
You could probably do a similar thing without a binding by setting by defining appropriate CSS style rules based on the button's :hover CSS pseudo-class and -fx-graphic attribute.

JavaFX - Unique scene per stage

I have a class that extends Application and calls the primary stage. This primary stage has a Next Button, that will call another stage (options stage). The options stage has a Previous Button.
I'd like to get the instance of the primary stage, in the state it was before the user clicked Next Button, for example: a textfield with input data or combobox with selected item.
How can I do that?
Main class:
public class MainClass extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
final FXMLLoader loader = new FXMLLoader(getClass().getResource("interfaceOne.fxml"));
final Parent root = (Parent)loader.load();
final MyController controller = loader.<MyController>getController();
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("icon.png")));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
Platform.setImplicitExit(false);
controller.setStage(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MyController:
public class MyController{
// Some declarations ...
Stage stage = null;
public void setStage(Stage stage) {
this.stage = stage;
}
// Next button's action
#FXML
public void handleNextAction(ActionEvent event) {
try {
Parent root = FXMLLoader.load(getClass().getResource("optionInterface.fxml"));
stage.initStyle(StageStyle.TRANSPARENT);
stage.getIcons().add(new Image(getClass().getResourceAsStream("icon.png")));
stage.setScene(new Scene(root));
stage.show();
// Hide the current screen
((Node)(event.getSource())).getScene().getWindow().hide();
} catch (Exception exc) {
System.out.println("Error: " + exc.getMessage());
}
}
}
Options Controller:
public class OptionsController implements Initializable {
public void handlePreviousAction(ActionEvent event) {
try {
Parent root = FXMLLoader.load(getClass().getResource("interfaceOne.fxml"));;
MyController controller = MyController.getInstance();
stage.initStyle(StageStyle.TRANSPARENT);
stage.getIcons().add(new Image(getClass().getResourceAsStream("icon.png")));
stage.setScene(new Scene(root));
controller.setStage(stage);
controller.isLocationLoaded(false);
stage.show();
// Hide the current screen
((Node)(event.getSource())).getScene().getWindow().hide();
} catch (IOException exc) {
System.out.println("Error: " + exc.getMessage());
}
}
}
Recommended Approach
Don't use multiple stages for this, instead use a single stage and multiple scenes or layered Panes.
Sample References
Angela Caicedo's sophisticated Scene switching tutorial.
A wizard style configuration.
Background
Read over a discussion of the theater metaphor behind JavaFX to help understand the difference between a Stage and a Scene and why you want to probably be changing scenes in and out of your application rather than stages.
Simple Sample
I created a simple sample based upon your application description which just switches back and forth between a main scene and an options scene. As you switch back and forth between the scenes, you can see that the scene state is preserved for both the main scene and the options scene.
For the sample, there is just a single stage reference, which is passed to the application in it's start method and the stage reference is saved in the application. The application creates a scene for the main screen and another for the options screen, saving both scene references switches the currently displayed scene back and forth between these references as required using stage.setScene.
The demo is deliberately simple to make it easy to understand and does not persist any of the data used or make use of a MVC style architecture or FXML as might be done in a more realistic demo.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class RoomReservationNavigator extends Application {
public static void main(String[] args) { Application.launch(args); }
private Scene mainScene;
private Scene optionsScene;
private Stage stage;
#Override public void start(Stage stage) {
this.stage = stage;
mainScene = createMainScene();
optionsScene = createOptionsScene();
stage.setScene(mainScene);
stage.show();
}
private Scene createMainScene() {
VBox layout = new VBox(10);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");
layout.getChildren().setAll(
LabelBuilder.create()
.text("Room Reservation System")
.style("-fx-font-weight: bold;")
.build(),
HBoxBuilder.create()
.spacing(5)
.children(
new Label("First Name:"),
new TextField("Peter")
)
.build(),
HBoxBuilder.create()
.spacing(5)
.children(
new Label("Last Name:"),
new TextField("Parker")
)
.build(),
new Label("Property:"),
ChoiceBoxBuilder.<String>create()
.items(FXCollections.observableArrayList(
"The Waldorf-Astoria",
"The Plaza",
"The Algonquin Hotel"
))
.build(),
ButtonBuilder.create()
.text("Reservation Options >>")
.onAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
stage.setScene(optionsScene);
}
})
.build(),
ButtonBuilder.create()
.text("Reserve")
.defaultButton(true)
.onAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
stage.hide();
}
})
.build()
);
return new Scene(layout);
}
private Scene createOptionsScene() {
VBox layout = new VBox(10);
layout.setStyle("-fx-background-color: azure; -fx-padding: 10;");
layout.getChildren().setAll(
new CheckBox("Breakfast"),
new Label("Paper:"),
ChoiceBoxBuilder.<String>create()
.items(FXCollections.observableArrayList(
"New York Times",
"Wall Street Journal",
"The Daily Bugle"
))
.build(),
ButtonBuilder.create()
.text("Confirm Options")
.defaultButton(true)
.onAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
stage.setScene(mainScene);
}
})
.build()
);
return new Scene(layout);
}
}

Resizing images to fit the parent node

How do I get an image in an ImageView to automatically resize such that it always fits the parent node?
Here is a small code example:
#Override
public void start(Stage stage) throws Exception {
BorderPane pane = new BorderPane();
ImageView img = new ImageView("http://...");
//didn't work for me:
//img.fitWidthProperty().bind(new SimpleDoubleProperty(stage.getWidth()));
pane.setCenter(img);
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.show();
}
#Override
public void start(Stage stage) throws Exception {
BorderPane pane = new BorderPane();
ImageView img = new ImageView("http://...");
img.fitWidthProperty().bind(stage.widthProperty());
pane.setCenter(img);
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.show();
}
This is a better solution than binding the width property (better because often when binding a child to its container, it might not be possible to make the container smaller. At other ocasions the container might even automatically start growing).
The solution below relies on overriding an ImageView so that we can let it behave as 'resizable' and then providing implementations for the minimum ,preferred, and maximum width/heights. Also important is to actually implement the resize() call.
class WrappedImageView extends ImageView
{
WrappedImageView()
{
setPreserveRatio(false);
}
#Override
public double minWidth(double height)
{
return 40;
}
#Override
public double prefWidth(double height)
{
Image I=getImage();
if (I==null) return minWidth(height);
return I.getWidth();
}
#Override
public double maxWidth(double height)
{
return 16384;
}
#Override
public double minHeight(double width)
{
return 40;
}
#Override
public double prefHeight(double width)
{
Image I=getImage();
if (I==null) return minHeight(width);
return I.getHeight();
}
#Override
public double maxHeight(double width)
{
return 16384;
}
#Override
public boolean isResizable()
{
return true;
}
#Override
public void resize(double width, double height)
{
setFitWidth(width);
setFitHeight(height);
}
}
Use ScrollPane or simply Pane to overcome this problem:
Example:
img_view1.fitWidthProperty().bind(scrollpane_imageview1.widthProperty());
img_view1.fitHeightProperty().bind(scrollpane_imageview1.heightProperty());
If you want the ImageView to fit inside a windows frame, use this line of code:
imageView.fitWidthProperty().bind(scene.widthProperty()).
Note that I am using widthProperty of the scene not the stage.
Example:
import java.io.FileNotFoundException;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
public class MapViewer extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) throws FileNotFoundException {
String strTitle = "Titulo de la Ventana";
int w_width = 800;
int w_height = 412;
primaryStage.setTitle(strTitle);
primaryStage.setWidth(w_width);
primaryStage.setHeight(w_height);
Group root = new Group();
Scene scene = new Scene(root);
final ImageView imv = new ImageView("file:C:/Users/utp/Documents/1.2008.png");
imv.fitWidthProperty().bind(scene.widthProperty());
imv.setPreserveRatio(true);
root.getChildren().add(imv);
primaryStage.setScene(scene);
primaryStage.show();
}
}
The aspect radio of the stage (primaryStage) should be similar to that of the image (1.2008.png)
This is a calculated width method that removes the width of the scroll bar.
first:
myImageView.setPreserveRatio(true);
monitor scrollbar width changes:
scrollPane.widthProperty().addListener((observable, oldValue, newValue) -> {
myImageView.setFitWidth(newValue.doubleValue() - (oldValue.doubleValue() - scrollPane.getViewportBounds().getWidth()));
});
scrollBar width:
oldValue.doubleValue() - scrollPane.getViewportBounds().getWidth()
How do I set init width?
after primaryStage.show();
myImageView.setFitWidth(scrollPane.getViewportBounds().getWidth());
Fill the parent whit aspect ration, this fix the problem whit when parent height and width are not in proper ration like the image.
Image image = new Image(getClass().getResource(%path%).toString());
double ratio = image.getWidth() / image.getHeight();
double width = stage.getScene().getWidth();
ImageView imageView.setImage(image);
imageView.setFitWidth(width);
imageView.setFitHeight(width/ratio);
imageView.setPreserveRatio(true);

Resources