Difficulty with nested ScrollPanes and FlowPanes/VBox'es - javafx

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.]

Related

the size of sub node changes causing changes of parent node's size in javafx

For example, I use an imageview as the sub node of a vbox, when I change the size property(fitWidthProperty and fitHeightProperty), the size of box also changes. How can I stop this? There just do it like the code follows.But actually Vbox is the child of GridPane which I don't list it.
public int DEFAULT_WIDTH = 200;
//onAction means a click on button
public void onAction(){
changeDefaultWidth();
}
//initial another control ,just like pagination.
public void configureCellFactory(){
VBox box = new VBox();
ImageView imageView = new ImageView();
imageView.fitWidthProperty().bind(default_width);
box.getChildren().add(imageView);
}

How to block a ScrollPane panel JavaFX

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 !

Responsive UI-Design with a TilePane in a ScrollPane

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 ;
}

JavaFx Resize VBox as contents are added with ScrollPane

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

Aligning a Path within a Container, StackPane, VBox etc

If I try to align a Path in a StackPane, somehow it is aligned based on its internal structure rather than using the layout bounds. Is there anyway of changing this? I've tried all sorts of combinations of with/without Group wrapper, VBox, HBox, autosizeChildren property etc.
For example, I want the line to be inset by (50, 50), but it gets shown at (0, 0)
#Override
public void start(Stage primaryStage) {
MoveTo moveTo = new MoveTo();
moveTo.setX(50);
moveTo.setY(50);
LineTo lineTo = new LineTo();
lineTo.setX(100);
lineTo.setY(100);
Path path = new Path(moveTo, lineTo);
StackPane stack = new StackPane(new Group(path));
stack.setAlignment(Pos.TOP_LEFT);
primaryStage.setScene(new Scene(stack, 100, 100));
primaryStage.show();
}
Without using a StackPane it appears as I expect. the line starts at (50, 50)
#Override
public void start(Stage primaryStage) {
MoveTo moveTo = new MoveTo();
moveTo.setX(50);
moveTo.setY(50);
LineTo lineTo = new LineTo();
lineTo.setX(100);
lineTo.setY(100);
Path path = new Path(moveTo, lineTo);
primaryStage.setScene(new Scene(new Group(path), 100, 100));
primaryStage.show();
}
According to the StackPane Javadocs:
The stackpane will attempt to resize each child to fill its content
area. If the child could not be sized to fill the stackpane (either
because it was not resizable or its max size prevented it) then it
will be aligned within the area using the alignment property, which
defaults to Pos.CENTER.
Since the Path is not resizable, is doesn't get resized, and just gets positioned according to the alignment you set. Wrapping in a Group doesn't help because the Group takes on the bounds of its children.
Either use a regular Pane instead of the StackPane (probably the most convenient solution):
Pane stack = new Pane(path);
or wrap the path in a Pane (which gets resized, etc):
StackPane stack = new StackPane(new Pane(path));

Resources