Adjustment of contents in FlowPane - javafx

I have a flowpane in center and i applied a slider effect which gets invoke on a click of button on the right (so slider moves from right to left when expanded). I have followed JewelSea slider tutorial mentioned here Slider
Now i have two different flowpanes in two different nodes. Both the flowpane contains array of labels but the only difference is, One flowpane contains scrollbar and is contained in TitlePane while the other is without scrollbar and no titlepane.
So now if i click on slider the contents in the flowpane(without scrollbar & titlepane) gets automatically adjusted but its not the same case with the flowpane containing scrollbar.
Here is relevant code for flowpane with scrollbar-
public void loadCase() {
ScrollPane s = null;
if (!homeController.mainTabPane.getTabs().contains(testTab)) {
int app = 0;
if (appareaList.size() > 0) {
FlowPane fpTestmoduleContainer = new FlowPane();
FlowPane example = new FlowPane();
for (ApplicationAreas appttribute : appareaList) {
appTestTitledPane[app] = new TitledPane();
appTestTitledPane[app].setText(appttribute.getApplication_name());
appTestTitledPane[app].setPrefSize(Control.USE_COMPUTED_SIZE, Control.USE_COMPUTED_SIZE);
/*Module loop start*/
fpTestmoduleContainer.setHgap(10);
fpTestmoduleContainer.setVgap(10);
// fpTestmoduleContainer.setPrefSize(Control.USE_COMPUTED_SIZE, Control.USE_COMPUTED_SIZE);
List<TestModuleAttribute> testmoduleList = WSData.getTestingModuleList(appttribute.getApplication_id());
ArrayList<Label> listTestlbs = new ArrayList<Label>(testmoduleList.size());
System.out.println("testmoduleList.size()" + testmoduleList.size());
int i = 0;
for (TestModuleAttribute testmattribute : testmoduleList) {
listTestlbs.add(new Label());
listTestlbs.get(i).setText(testmattribute.getModule_name());
listTestlbs.get(i).setAlignment(Pos.CENTER);
listTestlbs.get(i).setTextAlignment(TextAlignment.CENTER);
listTestlbs.get(i).setWrapText(true);
listTestlbs.get(i).setPrefSize(Control.USE_COMPUTED_SIZE, Control.USE_COMPUTED_SIZE);
listTestlbs.get(i).setId(testmattribute.getFxnode_css());
Image imgInstalled = new Image(getClass().getResourceAsStream("/upgradeworkbench/View/Icons/ok.png"));
listTestlbs.get(i).setGraphic(new ImageView(imgInstalled));
listTestlbs.get(i).setContentDisplay(ContentDisplay.BOTTOM);
Tooltip testtp = new Tooltip();
testtp.setText("Total No. Of test Cases :" + testmattribute.getTest_case());
testtp.setWrapText(true);
listTestlbs.get(i).setTooltip(testtp);
addModuleMouseClickListener(listTestlbs.get(i), testmattribute.getModule_name(), testmattribute.getFxnode_css(), testmattribute.getTest_case());
i = i + 1;
}
s = new ScrollPane();
s.setContent(fpTestmoduleContainer);
fpTestmoduleContainer.setPrefWidth(1500);
fpTestmoduleContainer.getChildren().addAll(listTestlbs);
//appTestTitledPane[app].setContent(fpTestmoduleContainer[app]);
listTestlbs.clear();
app = app + 1;
}
appareaTestmoduleContainer.getPanes().addAll(appTestTitledPane);
appareaTestmoduleContainer.setExpandedPane(appTestTitledPane[0]);
testTab.setText("Test Cases Wizard");
testTab.setText("Testing Application Foot Print");
//mainTab.setClosable(true);
// testTab.getContent().setVisible(true);
HBox hb = new HBox();
testTab.setContent(s);
}
}
}
Image of slider working as expected - before sliding
After sliding (without scrollbar) the 4 modules get to the next row as space is occupied by the slider
After adding scrollpane and embedding flowpane inside it. Slider overlaps the flowpane contents as shown
I want to know why the scrollbar causing issue in auto adjustment of contents inside the flowpane and how can i fix it ?

Here the width of your scrollpane is fixed. And then so is the width of the flow pane.You need to change the size of your scrollpane so that its content gets reset.
Use the following code.
scroll[app].setFitToHeight(true);
scroll[app].setFitToWidth(true);
This code will set the size of scrollpane according to the view. The flowpane will also adjust accordingly then.

Related

Region width not refreshing automatically after modifying Grid Pane to which the region width is bound - JavaFX

I'm making a simple Java GUI app using JavaFX that has a Border Pane as the root node.
In the top section of the Border Pane, there is a Grid Pane with three columns (top Grid Pane from now on).
In the first column of the top Grid Pane, there is a Home Button, in the second column, there is an empty Region that only serves as spacer between the first and third column of the top Grid Pane, and in the third column, there is another GridPane (right Grid Pane from now on).
The right Grid Pane contains one Button (Log In Button) on start. However, when a user successfully logs into the app, two other Buttons and a Label are added to the right Grid Pane as part of the Log In Button click event.
The spacer maxWidthProperty and minWidthProperty are bound to the top Grid Pane (tgp) widthProperty and the right Grid Pane(rgp) widthProperty like this:
spacer.minWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
spacer.maxWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
which makes the right Grid Pane move nicely with its buttons staying on the right side of the scene when a user resizes the main stage.
However, a problem occurs when the user logs in and additional buttons are added to the right Grid Pane. The spacer somehow misses this change and its width stays the same, which makes the additional Buttons appear outside of the current stage width. The only way to refresh the spacer width is to interact with the stage somehow, by clicking minimize/maximize/restore or by clicking any button on the scene.
Is there a way to automatically refresh Region width after the nodes to which its width is bound to are modified? Or, is there a better approach to making a top Grid Pane with one button on the left and modifiable number of buttons (nodes) on the right?
Edit: Here is a demonstration of the problem with several screenshots stacked on one another:
Minimal reproducible example:
BorderPane root = new BorderPane();
GridPane tgp = new GridPane();
tgp.minWidthProperty().bind(root.widthProperty());
tgp.maxWidthProperty().bind(root.widthProperty());
tgp.setStyle("-fx-background-color: WHITE; -fx-border-color: LIGHTGREY;");
tgp.setMinHeight(37);
tgp.setMaxHeight(37);
root.setTop(tgp);
Button homeButton = new Button("Home"));
homeButton.setMinHeight(35);
homeButton.setMaxHeight(35);
homeButton.setMinWidth(80);
homeButton.setMaxWidth(80);
tgp.add(homeButton, 0, 0);
GridPane rgp = new GridPane(); // Right Grid Pane - holds User related nodes
rgp.setHgap(5);
tgp.add(rgp, 2, 0);
Label unl = new Label("My Profile");
unl.setFont(new Font("Calibri", 15));
unl.setTextFill(Color.RED);
unl.setMinWidth(Region.USE_PREF_SIZE);
Button wlButton = new Button("Watchlist");
wlButton.setMinHeight(35);
wlButton.setMaxHeight(35);
wlButton.setMinWidth(80);
wlButton.setMaxWidth(80);
Button cartButton = new Button("Cart");
cartButton.setMinHeight(35);
cartButton.setMaxHeight(35);
cartButton.setMinWidth(60);
cartButton.setMaxWidth(60);
Button logInOutButton = new Button("Log In");
logInOutButton.setMinHeight(35);
logInOutButton.setMaxHeight(35);
logInOutButton.setMinWidth(60);
logInOutButton.setMaxWidth(60);
rgp.add(logInOutButton, 3, 0);
logInOutButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (logInOutButton.getText().equals("Log In")) {
LogInStage lis = new LogInStage();
lis.initStage();
if (lis.username != null) {
logInOutButton.setText("Log Out");
rgp.add(unl, 0, 0);
rgp.add(wlButton, 1, 0);
rgp.add(cartButton, 2, 0);
}
} else if (logInOutButton.getText().equals("Log Out")) {
logInOutButton.setText("Log In");
rgp.getChildren().remove(unl);
rgp.getChildren().remove(wlButton);
rgp.getChildren().remove(cartButton);
}
}
});
Region spacer = new Region();
spacer.minWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
spacer.maxWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
tgp.add(spacer, 1, 0)
It's always a bad idea to use bindings, if you can avoid it. Any changes to the size constraints can lead to a new layout pass being scheduled, but during the layout pass they are assumed to be constant. If you now introduce a binding the following sequence of events could happen:
A layout pass is requested for the GridPane, setting a flag to indicate layout is required
A layout pass happens. During the layout pass the children are resized. This triggers an update of the constraints of the children with the bindings.
The flag is cleared, but the changes to the contraints already happened. The layout won't reflect this. The GridPane gets another reason to do a layout.
I don't know, how your scene is set up in detail, but I recommend using column constraints: Set the grow priorities for the outer ones to SOMETIMES and the one for the center to ALWAYS. If you require some spacing around the children, you could use GridPane.setMargin (or the padding of the GridPane itself, if you require the a distance to the edges for all children).
#Override
public void start(Stage primaryStage) {
Button[] rightContent = new Button[3];
for (int i = 0; i < rightContent.length; i++) {
Button btn = new Button(Integer.toString(i));
GridPane.setColumnIndex(btn, i);
rightContent[i] = btn;
}
Button cycle = new Button("cycle");
GridPane rgp = new GridPane(); // I would usually use a HBox here
// don't grow larger than needed
rgp.setMaxWidth(Region.USE_PREF_SIZE);
// cycle though 0 to 3 buttons on the right
cycle.setOnAction(new EventHandler<ActionEvent>() {
int nextIndex = 0;
#Override
public void handle(ActionEvent event) {
if (nextIndex >= rightContent.length) {
rgp.getChildren().clear();
nextIndex = 0;
} else {
rgp.getChildren().add(rightContent[nextIndex]);
nextIndex++;
}
}
});
ColumnConstraints sideConstraints = new ColumnConstraints();
sideConstraints.setHgrow(Priority.SOMETIMES);
ColumnConstraints centerConstraints = new ColumnConstraints();
centerConstraints.setHgrow(Priority.ALWAYS);
//prefer to grow the center part of the GridPane
GridPane root = new GridPane();
root.getColumnConstraints().addAll(sideConstraints, centerConstraints, sideConstraints);
root.add(cycle, 0, 0);
root.add(rgp, 2, 0);
// add something to visualize the center part
// you could simply leave this part out
Region center = new Region();
center.setStyle("-fx-border-radius: 10;-fx-border-width: 1;-fx-border-color:black;");
root.add(center, 1, 0);
Scene scene = new Scene(root, 400, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
As mentioned in the comments, the center region is not actually needed.

JavaFX binding only applied after resizing the window

When I run the following code in the start method of my Main (JavaFX) class I get weird results. The window gets displayed but pane (with a green border) has a width of 0. It is supposed to have the same width as the container's height since I binded prefWidth to the height property.
Then, when I resize the window, the binding comes into effect and the pane becomes a square. Notice that if I maximize the window it also doesn't apply the bindings.
Thank you!
//Create a pane with a min width of 10 and a green border to be able to see it
Pane pane = new Pane();
pane.setStyle("-fx-border-color: green; -fx-border-width: 2");
//Bind the pane's preferred width to the pane's height
pane.prefWidthProperty().bind(pane.heightProperty());
//Put the pane in a vbox that does not fill the stage's width and make the pane grow in the vbox
VBox container = new VBox(pane);
container.setFillWidth(false);
VBox.setVgrow(pane, Priority.SOMETIMES);
//Show the vbox
primaryStage.setScene(new Scene(container, 600, 400));
primaryStage.show();
The problem you are running into here is that when the container is laid out, it has no reasonable information as to the order in which it should compute the width and the height of the pane. So essentially what happens is it computes the width, which (since it's empty), is zero; then computes the height (which fills the container, since you told the VBox to do that). After that, the prefWidth property is changed, but by then the actual width has already been set, so it's essentially too late. The next time a layout pass occurs, the new pref width is taken into account.
I haven't checked the actual layout code, but (since the default content bias is null) most likely the layout code for the vbox is going to do something equivalent to the following pseudocode:
protected void layoutChildren() {
// content bias is null:
double prefWidth = pane.prefWidth(-1);
double prefHeight = pane.prefHeight(-1);
// no fill width:
double paneWidth = Math.max(this.getWidth(), prefWidth);
// vgrow, so ignore preferred height and size to height of the vbox:
double paneHeight = this.getHeight();
pane.resizeRelocate(0, 0, paneWidth, paneHeight);
}
The last call actually causes the height of the pane to change, which then causes the prefWidth to change via the binding. Of course, that's too late for the current layout pass, which has already set the width based on the previous preferred width calculation.
Basically, relying on bindings to manage layout like this is not a reliable way of doing things, because you are changing properties (such as prefWidth in this example) during the layout pass, when it may be already too late to resize the component.
The reliable way to manage layout for a pane like this is to override the appropriate layout methods, which are invoked by the layout pass in order to size the component.
For this example, since the width depends on the height, you should return VERTICAL for the contentBias, and you should override computePrefWidth(double height) to return the height (so the width is set to the height):
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane() {
#Override
public Orientation getContentBias() {
return Orientation.VERTICAL ;
}
#Override
public double computePrefWidth(double height) {
return height ;
}
};
pane.setStyle("-fx-border-color: green; -fx-border-width: 2");
//Bind the pane's preferred width to the pane's height
// pane.prefWidthProperty().bind(pane.heightProperty());
//Put the pane in a vbox that does not fill the stage's width and make the pane grow in the vbox
VBox container = new VBox(pane);
container.setFillWidth(false);
VBox.setVgrow(pane, Priority.SOMETIMES);
//Show the vbox
primaryStage.setScene(new Scene(container, 600, 400));
primaryStage.show();
}

Forcing layout change when Stage is resized in JavaFX

I ran into a possible bug in JavaFX, but having short time, I am looking for a workaround.
I have a bunch of windows containing a chart and a toolbar:
public void createGui() {
root = new VBox();
ToolBar toolbar = new ToolBar();
Button btnConfig = new Button(bundle.getString("chartWindow.menu.configure"));
btnConfig.setOnAction(e -> doChartConfig());
toolbar.getItems().addAll(btnConfig);
chartPane = new VBox();
root.getChildren().setAll(toolbar, chartPane);
VBox.setVgrow(chartPane, Priority.ALWAYS);
scene = new Scene(root);
setScene(scene);
updateChart(); // Creates a chart
}
I have to resize them from code. (Tiling the open charts.) The simplified tiling code is:
// bounds is the user available area of the monitors(s)
double windowWidth = bounds.getWidth() / tileRule.columns;
double windowHeight = bounds.getHeight() / tileRule.rows;
int r = 0;
int c = 0;
for (Window w : sel) {
w.setWidth(windowWidth);
w.setHeight(windowHeight);
w.setX(bounds.getMinX() + c * windowWidth);
w.setY(bounds.getMinY() + r * windowHeight);
c++;
if (c >= tileRule.getColumns()) {
c = 0;
r++;
if (r >= tileRule.rows) {
break;
}
}
}
When I do this, the windows are perfectly arranged:
However, as it is visible, some of the windows contents aren't resized with the window (Now, accidentally, they are the last 3, but it is not always the case. Sometimes there are more, and they are not always the last ones.)
It is clearly visible, that the scene is the one that isn't resized with the window.
As soon as I resize the window manually, the controls are layed out well.
I trled a bunch of things to hack this:
Calling the requestLayout method manually
Removing the root element from the scene and adding again
Removing the scene from the stage and adding again
Adding 100 ms delay between the resize operations
binding the root elements width to the window width (minus border size)
None of the above helped. (Yes, not even adding the scene again! It caused the most spectacular result, because the contents are resized to the window size, but with streaching its content.)
Does anyone have any other idea how to hack this bug?
I'm using Java 8u74.

JavaFX synchronize Scroll pane content changes and its Vvalue

I have a Scrollpane containing 5 Titledpanes(Categories) each containing 2 images. Height of each TitledPane is 200 in expanded state and 25 in collapsed state.
Here is the image :
I have a text field in which I can enter the number of the Image to be selected.
For example, If I enter "4", the scroll pane scrolls to the Image-4 in Category-2 as shown below.
I am able to achieve this by calculating the Vvalue of the scroll pane based on the height to be scrolled and total height of the content inside the scrollpane.
Here is the issue
In the above example, if Category-2 is collapsed, I have to "Expand" it and then move to Image-4.
I am doing this by using the following code algorithm :
// Let the height of the scroll pane content with TitledPane-2 in collapsed state be (ht_ScrollPaneBefore = 825) and ScrollPane view port height be (ht_Viewport = 180). Let the ht of the scrollpane after expansion of TitledPane-2 be ht_ScrollPaneAfter which is calculated in step 3 in the following algorithm.
{
....
1. Expand TitledPane-2
2. Call applyCss() and layout() on the TitledPane to make the Expansion effective.(As addressed in the other query : https://stackoverflow.com/questions/26152642/get-the-height-of-a-node-in-javafx-generate-a-layout-pass)
3. Get the height of the total Content(ht_TotalContent) of the scroll pane with the expanded TitledPane-2.(ht_ScrollPaneAfter = 1000)
4. ****** Calculate the Vvalue based on (ht_TotalContent).(let it be Vvalue = 0.56) *****
5. Set the Vvalue to the scrollpane.
....
}
Issue
Since the expansion of the Titledpane is not yet applied "Visually" on the view, the Vvalue(0.52) is getting applied relative to ht_ScrollpaneBefore(800) which is leading to an incorrect scrolling ht as shown in the "Actual output" below.
Actual Output:
Expected:
Note: The ht of the scrollpane after the expansion of the TitledPane-2 is obtained successfully using the procedure described in the link: Get the height of a node in JavaFX (generate a layout pass). The Vvalue has been calculated based on ht_ScrollPaneAfter only not on ht_ScrollPaneBefore. The only problem here is the application of Vvalue is going wrong as the scroll pane view is not yet updated with the expanded TitledPane-2.
Code Sample :
private void scrollToImage() {
int imageNum = Integer.parseInt(textField.getText()); // Number entered in text field
int categoryNum = imageNum / 2 + imageNum % 2; // Which category the image number belongs to.
Bounds scrollViewBounds = mscr.localToScene(mscr.getBoundsInLocal()); // viewport bounds.
Platform.runLater(() -> {
System.out.println("Before: " + mvbx.getHeight());
titledPaneList[categoryNum - 1].setExpanded(true);
mvbx.applyCss();
mvbx.layout();
});
double scrollHt[] = {0.0};
//ht of the categories before selected image category.
for (int idx = 0; idx < (categoryNum - 1); idx++) {
scrollHt[0] += titledPaneList[idx].getHeight();
}
// check if the image is first/second image in the category.
int imageIdxInCategory = (imageNum % 2 == 0) ? 1 : 0;
// If the selected image is second image in the category,
// add the titlebar and first image ht to the scroll ht.
if (imageIdxInCategory > 0) {
scrollHt[0] += 25 //Title bar ht.
+ titledPaneList[categoryNum - 1].getInsets().getTop() // titled pane top insets
+ ((VBox) titledPaneList[categoryNum - 1].getContent()).getChildren().get(0).getBoundsInLocal().getHeight(); // ht of the first image
// Note: Since, my titledpane contains a vbox which contains the
// image number label and imageview,
// I am calculating the ht of the first vbox her in the above code.
}
Platform.runLater(() -> {
System.out.println("After: " + mvbx.getHeight());
double d_ScrollHtPerPix = 1
/ (mvbx.getHeight()
- (scrollViewBounds.getHeight() - mscr.getInsets().getTop() - mscr.getInsets().getBottom()));
mscr.setVvalue(scrollHt[0] * d_ScrollHtPerPix);
});
}
}
Output :
When the category of the selected image is collapsed,
Before : 825
After : 825
As u see, the height of the scrollpane content is same even after expanding the collapsed category.

JavaFX ScrollPane - How to use one scrollbar to scroll 2 ScrollPanes?

I have BorderPane with left and center part, both are ScrollPanes. How to scroll them with one scrollbar ( vertical ). Or how to get access to one of the ScrollBars ?
The answer of #invariant did not work for me. But the code written below worked out.
ScrollPane sp1 = new ScrollPane();
ScrollPane sp2 = new ScrollPane();
sp1.hvalueProperty().bindBidirectional(sp2.hvalueProperty());
With the binding two components to each other, they scroll together horizontally. It would work for vertical case.
you can bind scrollpane1(sp1) vScrollBar property and set the changed value to other scrollpane vScrollbar property.
Sample code : this code automatically changes SP2 vScrollbar position when Sp1 vScrollbar position chnaged.
DoubleProperty vPosition = new SimpleDoubleProperty();
vPosition.bind(sp1.vvalueProperty());
vPosition.addListener(new ChangeListener() {
#Override
public void changed(ObservableValue arg0, Object arg1, Object arg2) {
sp2.setVvalue((double) arg2);
}
});
Hint to get one scrolll bar to scroll two Scroll panes : Define a vertical scroll bar and then hide ( may be set opcaity to Zero or something ..) vscrollbars for two Scrollpanes. and then bind to defined scrollbar changes and set that chnaged values to both scrollpanes vscrollbars like above.

Resources