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);
Related
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();
}
}
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)
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);
}
}
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.
In javaFX to resize a canvas there is no such method to do that, the only solution is to extends from Canvas.
class ResizableCanvas extends Canvas {
public ResizableCanvas() {
// Redraw canvas when size changes.
widthProperty().addListener(evt -> draw());
heightProperty().addListener(evt -> draw());
}
private void draw() {
double width = getWidth();
double height = getHeight();
GraphicsContext gc = getGraphicsContext2D();
gc.clearRect(0, 0, width, height);
}
#Override
public boolean isResizable() {
return true;
}
}
is extends from Canvas is the only solution to make canvas Resizable ?
because this solution work only if we don't want to use FXML, if we declare in fxml a canvas how can we make it resizable?
this is my code :
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
Controller controller;
#Override
public void start(Stage primaryStage) throws Exception{
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
AnchorPane root = loader.load(); // controller initialized
controller = loader.getController();
GraphicsContext gc = controller.canvas.getGraphicsContext2D();
gc.setFill(Color.AQUA);
gc.fillRect(0, 0, root.getPrefWidth(), root.getPrefHeight());
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(controller.Pane, controller.Pane.getPrefWidth(), controller.Pane.getPrefHeight()));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Of all the answers given, none of them actually worked for me in terms of making the canvas automatically resize with its parent. I decided to take a crack at this and this is what I came up with:
import javafx.scene.canvas.Canvas;
public class ResizableCanvas extends Canvas {
#Override
public boolean isResizable() {
return true;
}
#Override
public double maxHeight(double width) {
return Double.POSITIVE_INFINITY;
}
#Override
public double maxWidth(double height) {
return Double.POSITIVE_INFINITY;
}
#Override
public double minWidth(double height) {
return 1D;
}
#Override
public double minHeight(double width) {
return 1D;
}
#Override
public void resize(double width, double height) {
this.setWidth(width);
this.setHeight(height);
}
}
This was the only one that actually made the canvas truly resizable.
My reasons for going with this approach is as follows:
I didn't want to break encapsulation by forcing the parent component to send us a width and height in the constructor which would also mean that the canvas cannot be used in FXML.
I also did not want to depend on the parent's width and height properties thus making the canvas the only child in it's parent, by taking up all the space.
Finally, the canvas needed to have it's drawing done in another class, which meant I could not use the current accepted answer which also included drawing to canvas via a draw method.
With this canvas, I do not need to bind to its parent width/height properties to make the canvas resize. It just resizes with whatever size the parent chooses. In addition, anyone using the canvas can just bind to its width/height properties and manage their own drawing when these properties change.
There's a guide that I think that you may find useful for setting up a resizable canvas:
JavaFx tip - resizable canvas
Piece of code from the guide:
/**
* Tip 1: A canvas resizing itself to the size of
* the parent pane.
*/
public class Tip1ResizableCanvas extends Application {
class ResizableCanvas extends Canvas {
public ResizableCanvas() {
// Redraw canvas when size changes.
widthProperty().addListener(evt -> draw());
heightProperty().addListener(evt -> draw());
}
private void draw() {
double width = getWidth();
double height = getHeight();
GraphicsContext gc = getGraphicsContext2D();
gc.clearRect(0, 0, width, height);
gc.setStroke(Color.RED);
gc.strokeLine(0, 0, width, height);
gc.strokeLine(0, height, width, 0);
}
#Override
public boolean isResizable() {
return true;
}
#Override
public double prefWidth(double height) {
return getWidth();
}
#Override
public double prefHeight(double width) {
return getHeight();
}
}
Taken from http://werner.yellowcouch.org/log/resizable-javafx-canvas/: To make a JavaFx canvas resizable all that needs to be done is override the min/pref/max methods. Make it resizable and implement the resize method.
With this method no width/height listeners are necessary to trigger a redraw. It is also no longer necessary to bind the size of the width and height to the container.
public class ResizableCanvas extends Canvas {
#Override
public double minHeight(double width)
{
return 64;
}
#Override
public double maxHeight(double width)
{
return 1000;
}
#Override
public double prefHeight(double width)
{
return minHeight(width);
}
#Override
public double minWidth(double height)
{
return 0;
}
#Override
public double maxWidth(double height)
{
return 10000;
}
#Override
public boolean isResizable()
{
return true;
}
#Override
public void resize(double width, double height)
{
super.setWidth(width);
super.setHeight(height);
<paint>
}
The canvas class just needs to override isResizable() (everything else, which is suggested in other examples, is actually not necessary) :
public class ResizableCanvas extends Canvas
{
public boolean isResizable()
{
return true;
}
}
And in the Application the width and height properties of the canvas have to be bound to the canvas' parent:
#Override
public void start(Stage primaryStage) throws Exception
{
...
StackPane pane = new StackPane();
ResizableCanvas canvas = new ResizableCanvas(width, height);
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
pane.getChildren().add(_canvas);
...
}
Listeners can be added to the width in height properties, in order to redraw the canvas, when it is resized (but if you need that and where to place it, depends on your application):
widthProperty().addListener(this::paint);
heightProperty().addListener(this::paint);
I found that the above solutions did not work when the canvas is contained in a HBox, as the HBox would not shrink when window is resized because it would clip the canvas. Thus the HBox would expand, but never grow any smaller.
I used the following code to make the canvas fit the container:
public class ResizableCanvas extends Canvas {
#Override
public double prefWidth(double height) {
return 0;
}
#Override
public double prefHeight(double width) {
return 0;
}
}
And in my controller class:
#FXML
private HBox canvasContainer;
private Canvas canvas = new ResizableCanvas();
...
#Override
public void start(Stage primaryStage) throws Exception {
...
canvas.widthProperty().bind(canvasContainer.widthProperty());
canvas.heightProperty().bind(canvasContainer.
pane.getChildren().add(canvas);
...
}
this solution work only if we don't want to use FXML
I am not sure about the merits of a resizable canvas itself, neither with/without FXML. Generally you want to redraw something on it, and then you do not have a canvas (which has no content on its own), but you are back to application-specific code, just as like the question iself and most answers around do contain some re/draw() method.
Then you could throw away the separate class, do four bindings in FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="test.TestController">
<children>
<Pane fx:id="pane" VBox.vgrow="ALWAYS">
<children>
<Canvas fx:id="canvas" height="${pane.height}" width="${pane.width}"
onWidthChange="#redraw" onHeightChange="#redraw" />
</children>
</Pane>
</children>
</VBox>
and implement only redraw() in Java:
package test;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
public class TestController {
#FXML
private Canvas canvas;
#FXML
private void redraw() {
double w=canvas.getWidth();
double h=canvas.getHeight();
GraphicsContext gc=canvas.getGraphicsContext2D();
gc.clearRect(0, 0, w, h);
gc.beginPath();
gc.rect(10, 10, w-20, h-20);
gc.stroke();
}
}
(If needed, find a suitable main class and module-info in https://stackoverflow.com/a/58915071/7916438)
In order to achieve a resizable Canvas, I have placed my Canvas inside a Pane:
<Pane fx:id="canvasPane" >
<Canvas fx:id="canvas" />
</Pane>
I then bound the Canvas height and width properties to those of the Pane inside the initialize method of my controller:
canvas.heightProperty().bind(canvasPane.heightProperty());
canvas.widthProperty().bind(canvasPane.widthProperty());
This allows the Pane to interact with the LayoutManager while causing the Canvas to fill the Pane.