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

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.

Related

Get visible elements from AnchorPane

I'm developing a JavaFX application where the user is able to zoom and drag the elements (all contained in a AnchorPane). Some of those elements are simple lines and I need to have something like a ruler that has different parent to stick on the screen on the same position even if the user zooms or drags the mentioned AnchorPane. I got almost everything working but I have one problem, I need to know which lines from the AnchorPane are visible to the user (as if the user zooms and drags the AnchorPane, some of the lines are not visible anymore). Here's what I tried (not working...)
private List<Double> getVisibleVerticalLinesXCoordonate() {
List<Double> xCoordonatesOfVisibleVerticalLines = new ArrayList<>();
List<Node> visibleNodes = new ArrayList<>();
Bounds bounds = rulerParent.getBoundsInLocal();
Bounds paneBounds = rulerParent.localToScene(bounds);
for (Node n : gridVerticalLines) {
Bounds nodeBounds = n.getBoundsInParent();
if (paneBounds.intersects(nodeBounds)) {
visibleNodes.add(n);
}
}
for (Node node : visibleNodes) {
Bounds newBounds = getRelative(node);
xCoordonatesOfVisibleVerticalLines.add(newBounds.getMinX());
}
System.out.println(Arrays.asList(xCoordonatesOfVisibleVerticalLines));
return xCoordonatesOfVisibleVerticalLines;
}
private Bounds getRelative(Node node) {
return rulerParent.sceneToLocal(node.localToScene(node.getBoundsInLocal()));
}
So, the rulerParent is what is fixed on the screen (is not zooming or dragging at all). After I have the visible lines from the AnchorPane I get the x coordinates of the lines relative to rulerParent - so I can align the ruler lines with the lines in the AnchorPane.
The problem is that this is not returning the actual visible lines...
I don't need to be able to see the whole line to consider it visible, that's why I'm using intersect...if any part of a line is visible, I need it.
It's about how you handle the zoom and dragging actions try to use ViewPort instead of scaling as with ViewPort you can know the translation X and Y coordinates of the ViewPort which is the visible X and Y coordinates of the AnchorPane

Gluon ScrollPane refresh issue

I have a messaging application running in Android which has the setup like in setup of the screen
the order is as below
<View>
<BorderPane>
<center>
<ScrollPane>
<content>
<VBox> //issue is here
</content>
<ScrollPane>
<center>
<bottom>
<TextField>
<bottom>
</BorderPane>
</View>
When I add children to VBox with
VBox.getChildren().add(TextLabel);
The ScrollPane gets new VBox and shows that on the screen.
However when i add more children that what current screen can fit i scroll to end of the ScrollPane by setting vvalueProperty();
ScrollPane.vvalueProperty().bind(VBox.heightProperty());
(Above code is essential to recreate the issue)
This works perfectly fine when running it on computer but on mobile i have this weird issue where scrollPane drops VBox when i add more children than what can be fit on the screen. And when i click on the VBox area the screen refreshes and i get the desired content on the screen
Video demonstrating ScrollBar issue in gluon
For convenience i have set following color code
ScrollBar - Red
VBox - Blue
As an alternative to binding I also tried
ScrollBar.setVvalue(1.0);
setVvalue() did not have same issue but this on the other hand was not showing the last message in the view.
Right now i have tried all possible combinations including replacing VBox with FlowPane and observed same behavior.
I can reproduce your issue on an Android device. Somehow, as discussed in the comments above, the binding of the vertical scroll position is causing some race condition.
Instead of trying to find out the cause of that issue, I'd rather remove the binding and propose a different approach to get the desired result: the binding is a very strong condition in this case.
When you try to do in the same pass this:
vBox.getChildren().add(label);
scrollPane.setVvalue(vBox.getHeight());
you already noticed and mentioned that the scroll position wasn't properly updated and you were missing the last item added to the vBox.
This can be explained easily: when you add a new item to the box, there is a call to layoutChildren() that will take some time to be performed. At least it will take another pulse to get the correct value.
But since you try to set immediately the vertical scroll position, the value vBox.getHeight() will still return the old value. In other words, you have to wait a little bit to get the new value.
There are several ways to do it. The most straightforward is with a listener to the box's height property:
vBox.heightProperty().addListener((obs, ov, nv) ->
scrollPane.setVvalue(nv.doubleValue()));
As an alternative, after adding the item to the box, you could use:
vBox.getChildren().add(label);
Platform.runLater(() -> scrollPane.setVvalue(vBox.getHeight()));
But this doesn't guarantee that the call won't be done immediately. So it is better to do a PauseTransition instead, where you can control the timing:
vBox.getChildren().add(label);
PauseTransition pause = new PauseTransition(Duration.millis(30));
pause.setOnFinished(f -> scrollPane.setVvalue(vBox.getHeight()));
pause.play();
As a suggestion, you could also do a nice transition to slide in the new item.
Alternative solution
So far, you are using an ScrollPane combined with a VBox to add a number of items, allowing scrolling to the first item on the list but keeping the scroll position always at the bottom so the last item added is fully visible. While this works fine (with my proposal above to avoid the binding), you are adding many nodes to a non virtualized container.
I think there is a better alternative, with a ListView (or better a CharmListView that will allow headers). With the proper CellFactory you can have exactly the same visuals, and you can directly scroll to the last item. But the main advantage of this control is its virtualFlow, that will manage for you a limited number of nodes while you have many more items added to a list.
This is just a short code snippet to use a ListView control for your chat application:
ListView<String> listView = new ListView<>();
listView.setCellFactory(p -> new ListCell<String>() {
private final Label label;
{
label = new Label(null, MaterialDesignIcon.CHAT_BUBBLE.graphic());
label.setMaxWidth(Double.MAX_VALUE);
label.setPrefWidth(this.getWidth() - 60);
label.setPrefHeight(30);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null && ! empty) {
label.setText(item);
label.setAlignment(getIndex() % 2 == 0 ? Pos.CENTER_LEFT : Pos.CENTER_RIGHT);
setGraphic(label);
} else {
setGraphic(null);
}
}
});
setCenter(listView);
and to add a new item and scroll to it, you just need:
listView.getItems().add("Text " + (listView.getItems().size() + 1));
listView.scrollTo(listView.getItems().size() - 1);
Of course, with the proper styling you can remove the lines between rows, and create the same visuals as with the scrollPane.

.setLayoutX() not affecting position in FlowPane

For some reason or another textExampleTwo.setLayoutX(40) does not actually result in the Text moving at all to the right. Is this a bug or have I missed something important here?
public void start(Stage stage) throws Exception {
FlowPane flowPane = new FlowPane();
flowPane.setOrientation(Orientation.VERTICAL);
Text textExampleOne = new Text("An example - 1");
Text textExampleTwo = new Text("An example - 2");
textExampleTwo.setLayoutX(40.0);
flowPane.getChildren().addAll(textExampleOne, textExampleTwo);
Scene applicationScene = new Scene(flowPane);
stage.setHeight(400.0);
stage.setWidth(400.0);
stage.setScene(applicationScene);
stage.show();
}
You've missed something important here:
Many Panes including FlowPane determine the position of their children on their own. For positioning the layoutX and layoutY properties are used. If you assign a new value to one of them and the Node is a child of a layout that positions it's children itself, this just leads to the position to be changed back during the next layout pass.
The exception to this are Nodes with the managed property set to false. This leads to neither layoutX nor layoutY being assigned however.
In your case you seem to want a combination of the two.
In this case the desired effect can be achieved by setting a margin.
// set all insets except the left one to 0
FlowPane.setMargin(textExampleOne, new Insets(0, 0, 0, 40));
Note however that this does not set the x position to 40, but it keeps a space of size 40 at the left of the Node. If you add enough children before this node to move it to the second column, this spacing would be used to calculate the distance to the beginning of the column.

JavaFX: How can I horizontally center an ImageView inside a ScrollPane?

viewScroll.setContent(new ImageView(bigimg));
double w = viewScroll.getContent().getBoundsInLocal().getWidth();
double vw = viewScroll.getViewportBounds().getWidth();
viewScroll.getContent().setTranslateX((vw/2)-(w/2));
viewScroll.toFront();
I set an ImageView with some Image inside the ScrollPane but the ImageView always goes to the far left corner. Here I'm trying to manually offset the difference, but it doesn't work well. The ImageView goes too far to the right plus it only updates once because it's inside the eventhandler for a button.
Here is an example using a label without the need for listeners:
public void start(Stage primaryStage) throws Exception {
ScrollPane scrollPane = new ScrollPane();
Label label = new Label("Hello!");
label.translateXProperty().bind(scrollPane.widthProperty().subtract(label.widthProperty()).divide(2));
scrollPane.setContent(label);
Scene scene = new Scene(scrollPane);
primaryStage.setScene(scene);
primaryStage.setWidth(200);
primaryStage.setHeight(200);
primaryStage.show();
}
I am not sure if you are familiar with properties or not, but in the code above, when I bind the translateXProperty to the formula, every time one of the dependencies changes, such as the ScrollPane's widthProperty or the Label's widthProperty, the formula is recalculated and translateXProperty is set to the result of the formula.
I am not sure, but in your previous code, it appears that the calculation code would be in a resize listener. This is not required when dealing with properties as they update whenever dependencies changed (note the bind() and not set()).

Adjustment of contents in FlowPane

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.

Resources