I'm trying to create an app that used JavaFX for GUI. The main windows has a set of tabs, one of which has a canvas that is used for visualization. I have noticed that resizing a canvas is not an easy task and there are lots of the tips and question ralated to this.
I took this code as the example. It works fine, until I try to put it into a TabsPane. When I enlarge the window the image enlarges too, but when I shirk the window, canvas is not shinked! Moreover, the panel on the top of the tab is not shrinked too.
Here is a screenhot of the window that demostrates the issue:
Here is the code:
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
* 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();
}
}
#Override
public void start(Stage stage) throws Exception {
TabPane tabs = new TabPane();
Tab tab = new Tab("tab", createNewTaskArea());
tabs.getTabs().add(tab);
stage.setScene(new Scene(tabs, 500, 500));
stage.setTitle("Tip 1: Resizable Canvas");
stage.show();
}
private Pane createNewTaskArea() {
Label newTaskNameQuery = new Label("Task name:");
TextField newTaskName = new TextField("Untitled task");
newTaskNameQuery.setLabelFor(newTaskName);
GridPane topPane = new GridPane();
topPane.setPadding(new Insets(5));
topPane.setHgap(5);
topPane.setVgap(10);
topPane.add(newTaskNameQuery, 0, 0);
topPane.add(newTaskName, 1, 0);
GridPane.setHalignment(newTaskNameQuery, HPos.RIGHT);
GridPane.setHgrow(newTaskName, Priority.ALWAYS);
ResizableCanvas canvas = new ResizableCanvas();
VBox panel = new VBox();
// Bind canvas size to stack pane size.
panel.widthProperty().addListener(observable -> canvas.setWidth(panel.getWidth()));
panel.heightProperty().addListener(
observable -> canvas.setHeight(panel.getHeight() - topPane.getHeight())
);
panel.getChildren().addAll(topPane, canvas);
return panel;
}
public static void main(String[] args) {
launch(args);
}
}
Is it possible to make it work properly?
It seems that the TabPane causes the problems, becasue if I remove it and make contents of the tab to be contents of the entire window everything works just like as I expect.
This does not work since for the VBox to reduce it's size, the Canvas's size would need to be reduced which does not happen, since the Canvas size is only decreased when the VBox size is reduced...
It's much easier to simply put the Canvas inside a resizeable parent, set the appropriate layout parameters for it and resize the Canvas based on the size of this parent:
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);
}
}
VBox panel = new VBox();
// parent to be resized
Pane pane = new Pane(canvas);
// grow/shrink pane when VBox height is increased/decreased
VBox.setVgrow(pane, Priority.ALWAYS);
// bind canvas size to parent size
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
panel.getChildren().addAll(topPane, pane);
Related
I'm looking for a Java method that will me screen dimensions in full-screen.
When using,
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
double width = screenSize.getWidth();
double height = screenSize.getHeight();
or
double width = GraphicsEnvironment.getLocalGraphicsEnvironment().
getMaximumWindowBounds().getWidth();
The width and height values will change whenever the screen changes resolution. I do not understand this measurement, since the width and height values are of the primitive type double (this means that they reside in the stack).
After an exhaustive search I had to resort to this forum because I did not find any methods like availSize
You can use the following method to get all content in full screen and also can call the static method to change scenes just passing the FXML name (another.fxml). Hope you are looking for this:
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import java.awt.*;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Main extends Application {
private static Stage mainStage;
public static Stage getStage() {
return mainStage;
}
#Override
public void start(Stage stage) throws Exception {
mainStage = stage;
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Region contentRootRegion = (Region) loader.load();
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
double origW = d.width;
double origH = d.height;
// Set a default "standard" or "100%" resolution
// origW = 1360;
// origH = 700;
// If the Region containing the GUI does not already have a preferred width and height, set it.
// But, if it does, we can use that setting as the "standard" resolution.
if (contentRootRegion.getPrefWidth() == Region.USE_COMPUTED_SIZE)
contentRootRegion.setPrefWidth(origW);
else
origW = contentRootRegion.getPrefWidth();
if (contentRootRegion.getPrefHeight() == Region.USE_COMPUTED_SIZE)
contentRootRegion.setPrefHeight(origH);
else
origH = contentRootRegion.getPrefHeight();
//Wrap the resizable content in a non-resizable container (Group)
Group group = new Group(contentRootRegion);
//Place the Group in a StackPane, which will keep it centered
StackPane rootPane = new StackPane();
rootPane.getChildren().add(group);
//Create the scene iniitally at the "100%" size
Scene scene = new Scene(rootPane, origW, origH);
//Bind the scene's width and height to the scaling parameters on the group
group.scaleXProperty().bind(scene.widthProperty().divide(origW));
group.scaleYProperty().bind(scene.heightProperty().divide(origH));
//Set the scene to the window (stage) and show it
stage.setTitle("My App Name");
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
stage.setX(bounds.getMinX());
stage.setY(bounds.getMinY());
stage.setWidth(bounds.getWidth());
stage.setHeight(bounds.getHeight());
stage.setMaximized(true);
//stage.setFullScreen(true);
stage.setScene(scene);
stage.show();
}
public static void ChangeScene(String FXML) {
try {
FXMLLoader loader = new FXMLLoader(Main.class.getResource(FXML));
Region contentRootRegion = (Region) loader.load();
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
double origW = d.width;
double origH = d.height;
if (contentRootRegion.getPrefWidth() == Region.USE_COMPUTED_SIZE)
contentRootRegion.setPrefWidth(origW);
else
origW = contentRootRegion.getPrefWidth();
if (contentRootRegion.getPrefHeight() == Region.USE_COMPUTED_SIZE)
contentRootRegion.setPrefHeight(origH);
else
origH = contentRootRegion.getPrefHeight();
//Wrap the resizable content in a non-resizable container (Group)
Group group = new Group(contentRootRegion);
//Place the Group in a StackPane, which will keep it centered
StackPane rootPane = new StackPane();
rootPane.getChildren().add(group);
//Create the scene initially at the "100%" size
Scene scene = new Scene(rootPane, origW, origH);
//Bind the scene's width and height to the scaling parameters on the group
group.scaleXProperty().bind(scene.widthProperty().divide(origW));
group.scaleYProperty().bind(scene.heightProperty().divide(origH));
//Set the scene to the window (stage) and show it
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
Main.getStage().setX(bounds.getMinX());
Main.getStage().setY(bounds.getMinY());
Main.getStage().setWidth(bounds.getWidth());
Main.getStage().setHeight(bounds.getHeight());
//Main.getStage().setMaximized(true);
Main.getStage().setScene(scene);
Main.getStage().show();
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static void main(String[] args) {
launch(args);
}
}
I currently have a ScrollPane with a FlowPane as content. The FlowPane currently initializes with no children nodes, a fixed width and a pref/min height (but no max height).
While adding items to the FlowPane at runtime (I click some UI element and something is added to the FlowPane), the ScrollPane should adjust its height in the case that the addition to the FlowPane no longer fits.
I don't understand how to set the height of the flowPane and ScrollPane so that this works - if that's the problem to begin with. At the moment, whenever the addition to the FlowPane doesn't fit its initial height, the content is added, but not visible. The scrollbar belonging to the ScrollPane never adjusts its height - if it did, I could just scroll further down and see the content.
Let's say I have a ScrollPane with some width and height, some viewport width/height, and a FlowPane with some width/height - What should my settings be for the min/pref/max sizes? How can I make a scrollPane adjust its scrollbar behaviour or make the content visible?
The ScrollPane's setFitToHeight is already set to true, which didn't seem to change anything.
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class FlowPaneTest extends Application
{
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception
{
// borderPane rootPane
BorderPane borderPane = new BorderPane();
borderPane.setMinSize(600, 600);
borderPane.setPrefSize(600, 600);
borderPane.setMaxSize(600, 600);
// container for the two scrollPanes below
FlowPane flow = new FlowPane();
borderPane.setRight(flow);
// two scrollPanes, each should resize it's height (width should be fixed) if
// children are added beyond it's current height
ScrollPane top = new ScrollPane();
ScrollPane bottom = new ScrollPane();
FlowPane scrollPaneContent = new FlowPane();
top.setContent(scrollPaneContent);
bottom.setContent(scrollPaneContent);
flow.getChildren().add(top);
flow.getChildren().add(bottom);
borderPane.setOnMouseClicked(new EventHandler<Event>()
{
#Override
public void handle(Event event)
{
Label l = new Label("test");
l.setMinSize(100, 100);
l.setPrefSize(100, 100);
l.setMaxSize(100, 100);
scrollPaneContent.getChildren().add(l);
}
});
// size settings
int width = 300, height = 300;
top.setHvalue(0.5);
top.setMinViewportHeight(height);
top.setPrefViewportHeight(height);
top.setMinViewportWidth(width);
top.setPrefViewportWidth(width);
top.setHbarPolicy(ScrollBarPolicy.ALWAYS);
top.setFitToHeight(true);
top.setMinSize(width, height);
top.setPrefSize(width, height);
top.setMaxWidth(width);
scrollPaneContent.setMinSize(width, height);
scrollPaneContent.setPrefSize(width, height);
scrollPaneContent.setMaxWidth(width);
scrollPaneContent.setPrefHeight(height);
bottom.setMinSize(width, height);
bottom.setPrefSize(width, height);
bottom.setMaxWidth(width);
bottom.setHvalue(0.5);
bottom.setMinViewportHeight(height);
bottom.setPrefViewportHeight(height);
bottom.setMinViewportWidth(width);
bottom.setPrefViewportWidth(width);
bottom.setHbarPolicy(ScrollBarPolicy.ALWAYS);
top.setFitToHeight(true);
bottom.setFitToHeight(true);
// stage
Scene scene = new Scene(borderPane, 600.0, 600.0);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Try to give the ScrollPane pref height and width and add this line
scrollPane.setFitToWidth(true);
Ending up with something similar to this ugly bit of code - It listens to the number of children in the pane and increases the size every time something is added to the list of children:
topSubPane.getChildren().addListener(new ListChangeListener()
{
#Override
public void onChanged(Change c)
{
c.next();
topSubPane.setPrefHeight(topSubPane.getHeight() + 50);
}
});
Works, but feels like an unorthodox hack. Is there really no regular way of doing this?
This is my code
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extends Application {
public void start(Stage primaryStage) throws Exception {
HBox OuterHBox = new HBox();
Image image1 = new Image("file:resources/redseven.png", 200, 200, true, true);
ImageView imageView1 = new ImageView(image1);
imageView1.setFitWidth(200);
imageView1.setFitHeight(200);
imageView1.setPreserveRatio(true);
imageView1.fitWidthProperty().bind(OuterHBox.widthProperty());
imageView1.fitHeightProperty().bind(OuterHBox.heightProperty());
Image image2 = new Image("file:resources/redseven.png", 200, 200, true, true);
ImageView imageView2 = new ImageView(image2);
imageView2.setFitWidth(200);
imageView2.setFitHeight(200);
imageView2.setPreserveRatio(true);
imageView2.fitWidthProperty().bind(OuterHBox.widthProperty());
imageView2.fitHeightProperty().bind(OuterHBox.heightProperty());
OuterHBox.getChildren().addAll(imageView1, imageView2);
Scene scene = new Scene(OuterHBox, 600, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public static void loadFontStuff() {
Font.loadFont(Main.class.getResource("TRON.TTF").toExternalForm(), 10);
System.out.println(Main.class.getResource("TRON.TTF"));
}
}
Output
What I want is when I make the window smaller by reducing it's width or reducing it's height, both the images inside should resize and be visible.
When I reduce the height, I see both images resizing to fit the screen.
But when I reduce the width, both images don't resize. Second image disappears out of view.
[
When I reach the first image, it resizes itself.
You can achieve this by setting width to image when outerbox's width get changed.
You can add a width change listener to the outerbox and distribute the updated width to the images equally. You need to remove fitWidthProperty because each image will use complete outerbox width.
Please update your code like this:
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class Main extends Application {
public void start(Stage primaryStage) throws Exception {
HBox OuterHBox = new HBox();
Image image1 = new Image("file:resources/redseven.png", 200, 200, true, true);
ImageView imageView1 = new ImageView(image1);
imageView1.setFitWidth(200);
imageView1.setFitHeight(200);
imageView1.setPreserveRatio(true);
// imageView1.fitWidthProperty().bind(OuterHBox.widthProperty());
imageView1.fitHeightProperty().bind(OuterHBox.heightProperty());
Image image2 = new Image("file:resources/redseven.png", 200, 200, true, true);
ImageView imageView2 = new ImageView(image2);
imageView2.setFitWidth(200);
imageView2.setFitHeight(200);
imageView2.setPreserveRatio(true);
// imageView1.fitWidthProperty().bind(OuterHBox.widthProperty());
imageView2.fitHeightProperty().bind(OuterHBox.heightProperty());
// Updated Code============
OuterHBox.widthProperty().addListener((observable, oldValue, newValue) -> {
imageView1.setFitWidth(newValue.doubleValue() / 2);
imageView2.setFitWidth(newValue.doubleValue() / 2);
});
OuterHBox.getChildren().addAll(imageView1, imageView2);
Scene scene = new Scene(OuterHBox, 600, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public static void loadFontStuff() {
Font.loadFont(Main.class.getResource("TRON.TTF").toExternalForm(), 10);
System.out.println(Main.class.getResource("TRON.TTF"));
}
}
The problem with your approach is with the combination of setPreserveRatio(true); and the binding. When you trying to increase the image height, because it has to keep its dimension ratio ( cause of setPreserveRatio(true); ), the ImageView will scale the width as well, forcing the HBox to increase its size and when it finally reaches more than the actual stage width it will make a part of the images to be hidden. You could set the setPreserveRatio to false unfortunately in that case the first Image will always try to get all the available space of the HBox and you will not be able to see the second Image.
In my opinion, I believe it's for the best to manually set the fitWidth and fitHeight of each ImageView by calculating the available space inside your pane. Here is an example
import java.util.ArrayList;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application {
private ArrayList<ImageView> allImages = null;
public void start(Stage primaryStage) {
HBox mainPane = new HBox();
try {
allImages = createImages(10, "icon.png");
} catch (Exception e1) {
e1.printStackTrace();
}
if (allImages == null || allImages.isEmpty()) {
Platform.exit();
}
mainPane.getChildren().addAll(allImages);
mainPane.widthProperty().addListener(e -> {
double fitWidth = mainPane.widthProperty().get() / allImages.size();
for (ImageView iv : allImages) {
iv.setFitWidth(fitWidth);
}
});
mainPane.heightProperty().addListener(e -> {
double fitHeight = mainPane.heightProperty().get();
for (ImageView iv : allImages) {
iv.setFitHeight(fitHeight);
}
});
Scene scene = new Scene(mainPane, 600, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
private ArrayList<ImageView> createImages(int count, String string) throws Exception {
ArrayList<ImageView> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
Image image = new Image(getClass().getResource("icon.png").toURI().toURL().toString());
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
list.add(imageView);
}
return list;
}
public static void main(String[] args) {
launch(args);
}
}
I am loading ten Images and display them in single row. Each time the user resize the Stage it will trigger an event which mainPane (your HBox) handles. Then finds out how many Images there are and how much space we have to fill, all you have to do then is to set the appropriate size for each ImageView and your are done. The Example above is working fine if you set the setPreserveRatio to false too.
I'm having some difficulty with ScrollPane in JavaFX 8 showing the scrollbar as needed. What I'm currently doing is simply creating a FlowPane with x number of elements, and setting that as the content of the ScrollPane.
The problem happens when I shrink down perpendicular to the orientation of the FlowPane. When elements begin to wrap and go out of bounds, the scrollbar does not appear. This does not happen when I shrink parallel to the orientation. I have a small Java program to exemplify the issue.
Start
Shrinking Parallel
Shrinking Perpendicular
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
FlowPane flow = new FlowPane();
flow.setStyle("-fx-border-color: red");
addPanes(flow, 16);
ScrollPane scroll = new ScrollPane(flow);
scroll.setStyle("-fx-border-color: green");
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
Scene scene = new Scene(scroll, 450, 450);
primaryStage.setScene(scene);
primaryStage.show();
}
public void addPanes(FlowPane root, int panes) {
for(int i = 0; i < panes; i++) {
StackPane filler = new StackPane();
filler.setStyle("-fx-border-color: black");
filler.setPrefSize(100, 100);
root.getChildren().add(filler);
}
}
}
Have a look at the code below and tell me if that's what you want to achieve. I am still not sure what cause the problem, I will have to look the documentation of ScrollPane to find out. My suspicion is at setFitToWidth & setFitToHeight methods. Although I still believe it's not a bug.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
FlowPane flow = new FlowPane();
flow.setStyle("-fx-border-color: red");
addPanes(flow, 16);
ScrollPane scroll = new ScrollPane(flow);
scroll.setStyle("-fx-border-color: green");
// Apparently this cause the issue here.
// scroll.setFitToHeight(true);
// scroll.setFitToWidth(true);
// Instead just make the flow pane take the dimensions of the ScrollPane
// the -5 is to not show the Bars when both of panes have the same dimensions
flow.prefWidthProperty().bind(Bindings.add(-5, scroll.widthProperty()));
flow.prefHeightProperty().bind(Bindings.add(-5, scroll.heightProperty()));
Scene scene = new Scene(scroll, 450, 450);
primaryStage.setScene(scene);
primaryStage.show();
}
public void addPanes(FlowPane root, int panes) {
for (int i = 0; i < panes; i++) {
HBox filler = new HBox();
filler.setStyle("-fx-border-color: black");
filler.setPrefSize(100, 100);
root.getChildren().add(filler);
}
}
}
Looking documentation of the ScrollPane, and in specific the setFitToHeight you will find that :
Property description:
If true and if the contained node is a
Resizable, then the node will be kept resized to match the height of
the ScrollPane's viewport. If the contained node is not a Resizable,
this value is ignored.
And because the node inside the ScrollPane will be kept resized to match the width and height of the ScrollPane's viewport thats why the Vertical ScrollBar will never appear.
You can add the code below to always show your vertical scrollbar.
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
When the required height of the FlowPane inside the ScrollPane is calculated a width value of -1 is passed. The flow pane will then report the height required when all its content fits into a single line.
As a workaround you could pass the width from the last layout calculation in this case.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
FlowPane flow = new FlowPane() {
#Override protected double computeMinHeight(double width) {
double minHeight = super.computeMinHeight(width != -1 ? width :
/* When no width is specified, use the current contol size*/
getWidth());
return minHeight;
}
};
flow.setStyle("-fx-border-color: red");
addPanes(flow, 16);
ScrollPane scroll = new ScrollPane(flow);
flow.maxWidthProperty().bind(scroll.widthProperty());
scroll.widthProperty().addListener((observable, oldValue, newValue)->{
/* clearSizeCache */
flow.requestLayout();
});
scroll.setStyle("-fx-border-color: green");
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
Scene scene = new Scene(scroll, 450, 450);
primaryStage.setScene(scene);
primaryStage.show();
}
public void addPanes(FlowPane root, int panes) {
for(int i = 0; i < panes; i++) {
StackPane filler = new StackPane();
filler.setStyle("-fx-border-color: black");
filler.setPrefSize(100, 100);
root.getChildren().add(filler);
}
}
}
I have started programming this app for ploting temperature data, when the screen gets resized, the canvas should resize as well. It initializes correctly but when I want to resize the window, the draw() method will only resize the height, according to the stackpanes height value it is bound to but it will ignore the width entirely. The Listener won't even fire. This I find very strange. Also I have to set the minSize for the StackPane, otherwise nothing will be draw at all. I'm not using FXML.
Does anybody have any ideas?
Edit I changed line 58 from setRight() to setCenter, as mentioned in the solution from InternetUnexplorer. But I was still curious about why it is so, so I did some research. I found this in the internet:
Top/Bottom area: Can shrink/expand horizontally and keep the height
unchanged.
Left/Right area: Can shrink/expand vertically and keep the length
unchanged.
Center area: Can shrink/expand in both directions.
http://o7planning.org/en/10629/javafx-borderpane-layout-tutorial
Here my main class:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class main extends Application {
#Override
public void start(Stage mainStage) throws Exception {
TempViewerWindow view = new TempViewerWindow();
ReadData controller = new ReadData(view);
controller.selectAll();
mainStage.setScene(new Scene(view));
//mainStage.setMinWidth(500);
//mainStage.setMinHeight(400);
mainStage.setTitle("Temperature Viewer");
mainStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
And here my view class:
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
public class TempViewerWindow extends BorderPane {
public TableView<TempData> tableView;
public Canvas canvas;
public GraphicsContext gc;
public StackPane holder;
public TempViewerWindow() {
tableView = new TableView<>();
TableColumn<TempData, String> col_date = new TableColumn<>("Date");
TableColumn<TempData, Float> col_temperature = new TableColumn<>("Temperature");
col_date.setCellValueFactory(e -> e.getValue()
.dateProperty());
col_temperature.setCellValueFactory(e -> e.getValue()
.temperatureProperty()
.asObject());
tableView.getColumns()
.addAll(col_date, col_temperature);
setLeft(tableView);
holder = new StackPane();
holder.setMinSize(400, 400); // must be here for some reason
canvas = new Canvas();
gc = canvas.getGraphicsContext2D();
canvas.widthProperty()
.bind(holder.widthProperty()
.subtract(10));
canvas.heightProperty()
.bind(holder.heightProperty()
.subtract(10));
canvas.widthProperty()
.addListener(observable -> redraw(gc));
canvas.heightProperty()
.addListener(observable -> redraw(gc));
holder.getChildren()
.add(canvas);
setCenter(holder); //here was the bug
}
private void redraw(GraphicsContext gc) {
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
System.out.println("redraw!");
}
}
Your canvas is being placed in the right of your BorderPane, when you should be placing it in the center.
If you change line 54 of TempViewerWindow from setRight(holder) to setCenter(holder), the scaling works properly.
You're using a BorderPane as parent for holder. The holder is used as right child of the BorderPane. However the right/left children are resized to their preferred widths and are resized to fill the height between the top and bottom nodes. (See javadoc)
The left and right children will be resized to their preferred widths and extend the length between the top and bottom nodes.
If you'd use holder as center, it's width would be adjusted the way you expect it.
Alternatively you could use a HBox as base class for TempViewerWindow to achieve this kind of behavior.
public TempViewerWindow() {
...
this.getChildren().addAll(tableView, holder);
HBox.setHgrow(holder, Priority.ALWAYS);
HBox.setHgrow(tableView, Priority.NEVER);
this.setFillHeight(true);
}