I am relatively new to JavaFx and I need help in getting a Vbox to grow as nodes are added to it.
I've place a VBox in a ScrollPane. The VBox gets filled with TitledPanes as they come in. But once the TitledPanes fill the space allotted the Vbox, the TitledPanes begin to overlap. Ideally I would want to Vbox to resize itself and use the ScrollPane to navigate.
I have the Vbox Max Height set to USE_COMPUTED_SIZE. I've added a listener to the ScrollPane to listen to changes in size of the VBox but no luck. Any Suggestions?
scrollPane.vvalueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) {
vBox.setLayoutY(-new_val.doubleValue());
}
});
Is there any reason you are using a VBox rather than an Accordion?
With an Accordion you could use a ListChangeListener to check for changes in TitledPane number and then adjust the size of the Accordion if an item was added or removed:
accordion.getPanes().addListener(new ListChangeListener<Item>() {
public void onChanged(Change<tem> c) {
while (c.next()) {
if(c.wasAdded() || c.wasRemoved){
//resize Accordion in dependency of the vvalue of your scrollpane
}
}
});
For a VBox, the principle should still be valid:
vbox.getChildren().addListener(new ListChangeListener<Item>() {
public void onChanged(Change<tem> c) {
while (c.next()) {
if(c.wasAdded() || c.wasRemoved){
//rezise VBox in dependency of the vvalue of your scrollpane
}
}
});
based on
Related
I have a small problem. I'm building an interface with JavaFX like this:
I wonder, how can I do to block those "lines" of the ScrollPane I indicated in the image? Practically it is not to be resizable but among its properties the ScrollPane does not allow me to put the check on that property:
How can I do to solve?
thanks to all in advance!
I think your problem is that you have not added a minimum value to your ScrollPanes here's an example :
SplitPane split = new SplitPane();
split.setPrefSize(400, 400);
//First ScrollPane
ScrollPane spA = new ScrollPane();
spA.setMinWidth(100); //Block the scrollPane width to 100
spA.setFitToHeight(true);
spA.setFitToWidth(true);
Pane paneA = new Pane();
paneA.setStyle("-fx-background-color:red;");
spA.setContent(paneA);
//Second ScrollPane
ScrollPane spB = new ScrollPane();
spB.setMinWidth(100); //Block the scrollPane width to 100
spB.setFitToHeight(true);
spB.setFitToWidth(true);
Pane paneB = new Pane();
paneB.setStyle("-fx-background-color:blue;");
spB.setContent(paneB);
split.getItems().addAll(spA,spB);
To be able to use your scrollPane as it grows, you can use the binding and bind the content's (width/height properties) of your ScrollPane to their parents (ScrollPane) example :
//set the (FitToHeight/FitToWidth) properties to false before !
spA.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
paneB.setMinWidth((double)newValue);
}
});
Good luck !
I am trying to wrap my head around Scroll- and Tilepanes atm, and I have come upon an issue I just cant solve without a dirty hack.
I have a horizontal TilePane that has 8 Tiles, and I set it to have 4 columns, resulting in 2 rows with 4 tiles.
That TilePane I put in an HBox, since if I put it in a StackPane it would stretch the size of the tilepane making my colum setting void. A bit weird that setting the prefColumns/Rows recalculates the size of the TilePane, rather than trying to set the actual amounts of columns/rows, feels more like a dirty hack.
Anyway, putting the HBox directly into the ScrollPane would not work either, since the Scrollbars would not appear even after the 2nd row of tiles would get cut off. Setting that HBox again in a Stackpane which I then put in a ScrollPane does the trick. Atleast until I resize the width of the window to be so small the tilepane has to align the tiles anew and a 3rd or more rows appear.
Here is the basic programm:
public class Main extends Application {
#Override
public void start(Stage stage) {
TilePane tilePane = new TilePane();
tilePane.setPadding(new Insets(5));
tilePane.setVgap(4);
tilePane.setHgap(4);
tilePane.setPrefColumns(4);
tilePane.setStyle("-fx-background-color: lightblue;");
HBox tiles[] = new HBox[8];
for (int i = 0; i < 8; i++) {
tiles[i] = new HBox(new Label("This is node #" + i));
tiles[i].setStyle("-fx-border-color: black;");
tiles[i].setPadding(new Insets(50));
tilePane.getChildren().add(tiles[i]);
}
HBox hbox = new HBox();
hbox.setAlignment(Pos.CENTER);
hbox.setStyle("-fx-background-color: blue;");
hbox.getChildren().add(tilePane);
StackPane stack = new StackPane();
stack.getChildren().add(hbox);
ScrollPane sp = new ScrollPane();
sp.setFitToHeight(true);
sp.setFitToWidth(true);
sp.setContent(stack);
stage.setScene(new Scene(sp, 800, 600));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I managed to achieve my wanted behaviour, but its more of a really dirty hack. I added a listener to the height and width of my HBox containing the TilePane and assumed that when the height changes its because the width got so small that a column was removed and a new row added. To be able to do that I put the HBox in a VBox so that it would not grow withe the height of the ScrollPane. For the width I simply calculated if there is space to display another colum (up to 4), to do it.
Here are the changes:
public class Main extends Application {
private boolean notFirstPassHeight;
private boolean notFirstPassWidth;
#Override
public void start(Stage stage) {
TilePane tilePane = new TilePane();
tilePane.setPadding(new Insets(5));
tilePane.setVgap(4);
tilePane.setHgap(4);
tilePane.setPrefColumns(4);
tilePane.setStyle("-fx-background-color: lightblue;");
// I took the value from ScenicView
tilePane.prefTileWidthProperty().set(182);
HBox tiles[] = new HBox[8];
for (int i = 0; i < 8; i++) {
tiles[i] = new HBox(new Label("This is node #" + i));
tiles[i].setStyle("-fx-border-color: black;");
tiles[i].setPadding(new Insets(50));
tilePane.getChildren().add(tiles[i]);
}
ScrollPane sp = new ScrollPane();
sp.setFitToHeight(true);
sp.setFitToWidth(true);
StackPane stack = new StackPane();
VBox vbox = new VBox();
vbox.setStyle("-fx-background-color: red");
HBox hbox = new HBox();
hbox.setAlignment(Pos.CENTER);
hbox.setStyle("-fx-background-color: blue;");
hbox.getChildren().add(tilePane);
notFirstPassHeight = false;
notFirstPassWidth = false;
hbox.heightProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue.doubleValue() < newValue.doubleValue() && notFirstPassHeight) {
tilePane.setPrefColumns(tilePane.getPrefColumns() - 1);
stack.requestLayout();
}
notFirstPassHeight = true;
});
hbox.widthProperty().addListener((observable, oldValue, newValue) -> {
if (oldValue.doubleValue() < newValue.doubleValue() && notFirstPassWidth && tilePane.getPrefColumns() <= 3
&& (newValue.doubleValue() / (tilePane.getPrefColumns() + 1)) > tilePane.getPrefTileWidth()) {
tilePane.setPrefColumns(tilePane.getPrefColumns() + 1);
stack.requestLayout();
}
notFirstPassWidth = true;
});
vbox.getChildren().add(hbox);
stack.getChildren().add(vbox);
sp.setContent(stack);
stage.setScene(new Scene(sp, 800, 600));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
However this approach requires me to
1.Know the Width of the Tiles in the Tilepane.
2.Consider Padding and Gap between tiles for my calculation to be accurate, which I dont do in my example.
And its just not a good approach at any rate if you ask me. Too complicated a process for such a basic thing. There has to be a way better and simple way to accomplish complete resizability and the wanted behaviour with TilePanes in a ScrollPane.
Setting the preferred number of columns and/or rows in the TilePane determines the calculation for the prefWidth and prefHeight values for that tile pane. If you want to force a maximum number of columns, you just need to make the maxWidth equal to the computed prefWidth: you can do this with
tilePane.setMaxWidth(Region.USE_PREF_SIZE);
This means that (as long as the tile pane is placed in something that manages layout), it will never be wider than the pref width, which is computed to allow the preferred number of columns. It may, of course, be smaller than that. (Note you could use the same trick with setMinWidth if you needed a minimum number of columns, rather than a maximum number of columns.)
The scroll pane's fitToHeight and fitToWidth properties will, when true, attempt to resize the height (respectively width) of the content to be equal to the height (width) of the scroll pane's viewport. These operations will take precedence over the preferred height (width) of the content, but will attempt to respect the minimum height (width).
Consequently, it's usually a mistake to call both setFitToWidth(true) and setFitToHeight(true), as this will almost always turn off scrolling completely (just forcing the content to be the same size as the scroll pane's viewport).
So here you want to make the max width of the tile pane respect the pref width, and fix the width of the tile pane to be the width of the scroll pane's viewport (so that when you shrink the width of the window, it shrinks the width of the viewport and creates more columns). This will add a vertical scrollbar if the number of rows grows large enough, and only add a horizontal scrollbar if the viewport shrinks horizontally below the minimum width of the tile pane (which is computed as the minimum of the preferred widths of all the nodes it contains).
I think the following version of your original code does essentially what you are looking for:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;
public class ScrollingTilePane extends Application {
#Override
public void start(Stage stage) {
TilePane tilePane = new TilePane();
tilePane.setPadding(new Insets(5));
tilePane.setVgap(4);
tilePane.setHgap(4);
tilePane.setPrefColumns(4);
tilePane.setStyle("-fx-background-color: lightblue;");
// dont grow more than the preferred number of columns:
tilePane.setMaxWidth(Region.USE_PREF_SIZE);
HBox tiles[] = new HBox[8];
for (int i = 0; i < 8; i++) {
tiles[i] = new HBox(new Label("This is node #" + i));
tiles[i].setStyle("-fx-border-color: black;");
tiles[i].setPadding(new Insets(50));
tilePane.getChildren().add(tiles[i]);
}
HBox hbox = new HBox();
hbox.setAlignment(Pos.CENTER);
hbox.setStyle("-fx-background-color: blue;");
hbox.getChildren().add(tilePane);
// StackPane stack = new StackPane();
// stack.getChildren().add(tilePane);
// stack.setStyle("-fx-background-color: blue;");
ScrollPane sp = new ScrollPane();
// sp.setFitToHeight(true);
sp.setFitToWidth(true);
sp.setContent(hbox);
stage.setScene(new Scene(sp, 800, 600));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Note that if you need to change the background color of the space outside the scroll pane's content, you can use the following in an external style sheet:
.scroll-pane .viewport {
-fx-background-color: red ;
}
Above, I have added a screen shot - now that I can add images. The bottom of the screen shot shows the edge of the scene (with no scrollbar on outer container 2).
This scene may become too busy - but I am attempting to use nested scroll panes which contain either a FlowPane or VBox. I am defining "inner containers" which is a VBox and includes a textfield and scroll pane which has a flow pane as it's content. The flow pane loads a number of "status blocks". The scroll pane for the inner containers seems to be working okay. Below is a code snippet from the inner container:
public class InnerContainer extends VBox
{
// Declare the various parts of the inner container
private TextField m_icName = null; // Name of the inner container
private ScrollPane m_icScroll = null;
private FlowPane m_icFlow = null; // Holds the status blocks
// List of status blocks in this inner container
private ArrayList<StatusBlock> m_statBlockList = null;
/******************************************************************
* Create the containers and controls used by the inner container *
******************************************************************/
public InnerContainer()
{
m_statBlockList = new ArrayList<>(); // Set up list of status blocks
setMinSize(200.0, 170.0);
setPrefSize(200, 170);
setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
setPadding(new Insets(5, 5, 5, 5));
m_icName = new TextField();
m_icName.setMaxWidth(Double.MAX_VALUE);
m_icScroll = new ScrollPane();
m_icScroll.setFitToWidth(true);
m_icScroll.setFitToHeight(true);
m_icScroll.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
VBox.setVgrow(m_icScroll, Priority.ALWAYS);
m_icFlow = new FlowPane();
m_icFlow.setPrefWrapLength(650.0); // This is the "wrap" point
m_icFlow.setVgap(5);
m_icFlow.setHgap(5);
m_icFlow.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
m_icScroll.setContent(m_icFlow);
// Add the elements to the vbox
getChildren().addAll(m_icName, m_icScroll);
VBox.setVgrow(this, Priority.ALWAYS);
I've been unable to get the scroll bars to work properly for the outer containers. If I add inner containers the outer container just keeps growing and I can never get the scroll bar to show up (even if it exceeds the size of the screen). I suspect I am having trouble correctly computing the size of the content of the outer container. The outer container is a VBox which contains a text field and a scrollpane which has content of a VBox. The final VBox can consist of one to many inner containers.
Here is a code snippet from the outer container:
public class OuterContainer extends VBox
{
// Declare the various parts of the outer container
private TextField m_ocName = null; // Name of the outer container
private ScrollPane m_ocScroll = null;
private VBox m_ocMainVBox = null;
private ArrayList<InnerContainer> m_innerContList = null;
public OuterContainer()
{
// Setup the inner container list
m_innerContList = new ArrayList<>();
setSpacing(8);
setPrefSize(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE);
setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
setPadding(new Insets(5, 5, 5, 5));
m_ocName = new TextField();
m_ocName.setMaxWidth(Double.MAX_VALUE);
m_ocScroll = new ScrollPane();
m_ocScroll.setFitToWidth(true);
m_ocScroll.setFitToHeight(true);
m_ocScroll.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
VBox.setVgrow(m_ocScroll, Priority.ALWAYS);
m_ocMainVBox = new VBox();
m_ocMainVBox.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
VBox.setVgrow(m_ocMainVBox, Priority.ALWAYS);
m_ocMainVBox.setSpacing(5);
m_ocMainVBox.setMinSize(1.0, 1.0);
m_ocScroll.setContent(m_ocMainVBox);
// Add the elements to the top vbox
getChildren().addAll(m_ocName, m_ocScroll);
VBox.setVgrow(this, Priority.ALWAYS);
There is a final outer class which also should potentially have a scrollbar. It also isn't working correctly, but I suspect the issue is similar to what I am asking for help with here.
Thanks in advance for any help.
I've abandoned trying to use three scrollbars and will instead use a single scrollbar. However, I believe the root issue was addressed in:
Java FX scales the parent based on the content size
The size of the FlowPane or VBox used as the content of the ScrollPane keeps growing as the size of the content grows. This also causes the height of the scrollpane to grow. To resolve this, I am now basing the pref height of the scroll pane on the height of the stage. I've also added a change listener that looks for changes to the stage height property. It is similar to the following code snippet:
m_dashScroll.setPrefHeight(TopClass.getStage().getHeight() - DECORATION_OFFSET);
TopClass.getStage().heightProperty().addListener(new ChangeListener<Number>()
{
#Override
public void changed(ObservableValue<? extends Number> ov,
Number oldVal, Number newVal)
{
m_dashScroll.setPrefHeight(newVal.doubleValue() - DECORATION_OFFSET);
}
});
This required a static reference to the stage, so I would be interested if there are better ways to do this. [Also, I could use a lambda here if desired.]
I using this code to fir FlowPane into ScrollPane
FlowPane flowPane = new FlowPane(Orientation.HORIZONTAL);
ScrollPane scroll = new ScrollPane();
scroll.setContent(flowPane);
scroll.viewportBoundsProperty().addListener(new ChangeListener<Bounds>()
{
#Override
public void changed(ObservableValue<? extends Bounds> ov, Bounds oldBounds, Bounds bounds)
{
flowPane.setPrefWidth(bounds.getWidth());
flowPane.setPrefHeight(bounds.getHeight());
}
});
Unfortunately this maybe is not the best solution. Is there another more simple way to fit FlowPane into ScrollPane in Java 8?
There are methods of ScrollPane meant to do this:
scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true);
Try setting maxWidth and maxHeoght properties of your flowPane to Double.MAX_VALUE either via CSS or via API so as to provide its maximal expansion whithin container:
flowPane.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
I'm writing an application with JavaFX 2.2.7-b01.
Here is an example of the code I currently have. How can I allow the application window to be resized but maintain the aspect ratio it is initially configured with? In other words, if the user resizes the window, the window width should always stay double the window height.
...
public void showScene(Stage stage, String fxmlPath) {
try {
FXMLLoader loader = new FXMLLoader();
loader.setBuilderFactory(new JavaFXBuilderFactory());
loader.setLocation(fxmlPath);
Parent page;
try (InputStream in = Main.class.getResourceAsStream(fxmlPath)) {
page = (Parent) loader.load(in);
}
Scene scene = new Scene(page, 400, 200);
stage.setScene(scene);
stage.sizeToScene();
stage.show();
} catch (Exception ex) {
...
}
}
...
It seems JavaFX allows a user to specify the width and height of a scene in the constructor but does not allow programmatic access to update the width or height. There are no setWidth or setHeight methods. I know I can add property listeners to get the read only width/height of the scene while it is being resized, but I haven't been able to figure out how to change the scene dimensions dynamically so I can force the aspect ratio to be maintained.
I imagine this would be possible if I were to subclass the Scene object (if I have to I will) but is there any other simple way to do this?
> How to make javafx.scene.Scene resize while maintaining an aspect ratio?
By manipulating the stage size instead of scene:
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Play by resizing the window");
VBox root = new VBox();
root.getChildren().add(btn);
root.setStyle("-fx-background-color: gray");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.minWidthProperty().bind(scene.heightProperty().multiply(2));
primaryStage.minHeightProperty().bind(scene.widthProperty().divide(2));
primaryStage.show();
}
Use removeListener and addListener when changing value to prevent listeners from chaining.
private ChangeListener<? super Number> widthChangeListener;
private ChangeListener<? super Number> heightChangeListener;
#Override
public void start(Stage stage) throws Exception {
widthChangeListener = (observable, oldValue, newValue) -> {
stage.heightProperty().removeListener(heightChangeListener);
stage.setHeight(newValue.doubleValue() / 2.0);
stage.heightProperty().addListener(heightChangeListener);
};
heightChangeListener = (observable, oldValue, newValue) -> {
stage.widthProperty().removeListener(widthChangeListener);
stage.setWidth(newValue.doubleValue() * 2.0);
stage.widthProperty().addListener(widthChangeListener);
};
stage.widthProperty().addListener(widthChangeListener);
stage.heightProperty().addListener(heightChangeListener);
}
In netbeans using Java Fx Scene Builder provide you can use AnchorPane Constraints to set
Resizing of stage.