Misalignment of two synced ScrollPanes - java

I try to align the vertical scroll position of two javafx.scene.control.ScrollPanes via
sp1.vvalueProperty().bindBidirectional(sp2.vvalueProperty());
The problem is that one of these ScrollPanes may have a horizontal scroll bar. So the more I scroll down the ScrollPanes, the more they get misaligned (see screenshot). How can I handle this?

It's impossible to do this with 2 ScrollPanes and contents of equal height unless you display the scrollbar in both ScrollPanes:
Consider the case where the content fits the viewport of the left ScrollPane exactly. The viewPort of the right ScrollPane can be scrolled by the ScrollBar height. Modifying the left ScrollPane is not possible.
Since the expected result seems to be some kind of scale, you could simply use a Pane with a child you apply transformY to and a clip. The formula to calculate the pixel to be placed at the top, use
top = vvalue * (contentHeight - viewportHeight)
Example
private static Label createLabel(int num, boolean mark) {
Label label = new Label(Integer.toString(num));
label.setPrefSize(50, 50);
label.setMaxSize(Double.MAX_VALUE, Region.USE_PREF_SIZE);
label.setStyle(mark ? "-fx-background-color: #FFFFFF" : "-fx-background-color: #BBBBBB;");
return label;
}
#Override
public void start(Stage primaryStage) throws Exception {
VBox scale = new VBox();
scale.setMinHeight(Region.USE_PREF_SIZE);
GridPane content = new GridPane();
for (int i = 0; i < 40; i++) {
boolean b = ((i % 2) == 0);
scale.getChildren().add(createLabel(i, !b));
for (int j = 0; j < 10; j++) {
content.add(createLabel(i * 10 + j, b), j, i);
}
}
AnchorPane scaleContainer = new AnchorPane(scale);
scaleContainer.setMinWidth(30);
scaleContainer.setMinHeight(0);
AnchorPane.setLeftAnchor(scale, 0d);
AnchorPane.setRightAnchor(scale, 0d);
Rectangle clip = new Rectangle();
scaleContainer.setClip(clip);
clip.widthProperty().bind(scaleContainer.widthProperty());
clip.heightProperty().bind(scaleContainer.heightProperty());
ScrollPane scroll = new ScrollPane(content);
scale.translateYProperty().bind(Bindings.createDoubleBinding(() -> {
double contentHeight = content.getHeight();
double viewportHeight = scroll.getViewportBounds().getHeight();
if (contentHeight <= viewportHeight) {
return 0d;
} else {
return -scroll.getVvalue() * (contentHeight - viewportHeight);
}
}, scroll.viewportBoundsProperty(), scroll.vvalueProperty(), content.heightProperty()));
HBox root = new HBox(scaleContainer, scroll);
root.setPadding(new Insets(10));
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}

Related

Scrollable gridpane in javafx

im trying to add multiply VBox to a gridpane (called refPane in the following codesnippet) which is inside a scrollpane.
int columnIndex = 0;
int rowIndex = 0;
int boxWidth = windowWidth/ITEMS_PER_ROW;
int boxHeight = windowHeight/ITEMS_PER_COLUMN;
for(int i=0; i<items.size(); i++){
VBox vBox = new VBox();
vBox.setPrefWidth(boxWidth);
vBox.setPrefHeight(boxHeight);
Label label1 = new Label();
label1.setText("ImgPlaceholder");
label1.setPrefWidth(boxWidth);
label1.setPrefHeight(boxHeight / 100 * 70);
vBox.getChildren().add(label1);
Label label2 = new Label();
label2.setText("Description");
label2.setPrefWidth(boxWidth);
label2.setPrefHeight(boxHeight / 100 * 30);
label2.setPadding(new Insets(0,0,0, 10));
vBox.getChildren().add(label2);
refPane.add(vBox, columnIndex, rowIndex);
if(columnIndex != 0 && columnIndex % GAMES_PER_ROW == 0){
rowIndex++;
columnIndex = 0;
}else {
columnIndex++;
}
It adds no more then ITEMS_PER_ROW Vboxes in one row and continues in the next row. Also there should be no more rows then ITEMS_PER_COLUM visible.
The problem is, if I add more then ITEMS_PER_ROW * ITEMS_PER_COLUMN to the grid, instead ob beeing scrollable, the vboxes just get smaller in size.
Any Ideas? Thanks in advance.
Chances are javafx is prioritizing shrinking the VBox's over expanding your grid pane. Try setting the minHeight of each VBox, to be equal to its prefHeight to keep them from shrinking vertically.

JavaFX dynamic gridpane row height adjustment

I am currently working on a project, and I cannot find just any solution. I want a Gridpane row height to be dynamically counted from the width of one specific column inside that row. The whole Gridpane must be resizable and the rest of the available space should take another row below so the aspect ratio of that one cell is preserved according to the element inside that cell. Any ideas boys?
click for a pic
public class GridPaneControl extends GridPane {
private int index;
private Label startLabel;
private Label endLabel;
private HBox inputCellsContainer;
private HBox outputCellsContainer;
private Button inputOutputNextButton;
private Button inputOutputPreviousButton;
public GridPaneControl() {
setGridLinesVisible(true);
initialization();
ColumnConstraints cc0 = new ColumnConstraints();
cc0.setMaxWidth(Double.MAX_VALUE);
cc0.setHgrow(Priority.NEVER);
ColumnConstraints cc1 = new ColumnConstraints();
cc1.setMaxWidth(Double.MAX_VALUE);
ColumnConstraints cc2 = new ColumnConstraints();
cc2.setMaxWidth(Double.MAX_VALUE);
cc2.setHgrow(Priority.NEVER);
RowConstraints rc0 = new RowConstraints();
rc0.setVgrow(Priority.ALWAYS);
rc0.setFillHeight(false);
rc0.setMaxHeight(Double.MAX_VALUE);
RowConstraints rc1 = new RowConstraints();
rc1.setVgrow(Priority.ALWAYS);
rc1.setFillHeight(false);
rc1.setMaxHeight(Double.MAX_VALUE);
RowConstraints rc2 = new RowConstraints();
rc2.setVgrow(Priority.NEVER);
RowConstraints rc3 = new RowConstraints();
rc3.setVgrow(Priority.ALWAYS);
getColumnConstraints().addAll(cc0, cc1, cc2);
getRowConstraints().addAll(rc0, rc1, rc2, rc3);
}
private void initialization() {
inputCellsContainer = new HBox(0);
outputCellsContainer = new HBox(0);
GridPane.setValignment(inputCellsContainer, VPos.BOTTOM);
GridPane.setValignment(outputCellsContainer, VPos.TOP);
inputOutputPreviousButton = new Button("<<");
inputOutputPreviousButton.maxWidth(Double.MAX_VALUE);
GridPane.setHgrow(inputOutputPreviousButton, Priority.NEVER);
GridPane.setVgrow(inputOutputPreviousButton, Priority.NEVER);
GridPane.setMargin(inputOutputPreviousButton, new Insets(5));
inputOutputNextButton = new Button(">>");
inputOutputNextButton.maxWidth(Double.MAX_VALUE);
GridPane.setHgrow(inputOutputNextButton, Priority.NEVER);
GridPane.setVgrow(inputOutputNextButton, Priority.NEVER);
GridPane.setMargin(inputOutputNextButton, new Insets(5));
for (int i = 0; i < 32; i++) {
InputOutputCell cellIn = new InputOutputCell(String.format("%02X", i), Color.AQUA, 0);
InputOutputCell cellOut = new InputOutputCell(String.format("%02X", i), Color.BEIGE, 1);
HBox.setHgrow(cellIn, Priority.ALWAYS);
HBox.setHgrow(cellOut, Priority.ALWAYS);
inputCellsContainer.getChildren().add(cellIn);
outputCellsContainer.getChildren().add(cellOut);
}
GridPane.setHgrow(inputCellsContainer, Priority.ALWAYS);
GridPane.setHgrow(outputCellsContainer, Priority.ALWAYS);
GridPane.setVgrow(inputCellsContainer, Priority.ALWAYS);
GridPane.setVgrow(outputCellsContainer, Priority.ALWAYS);
startLabel = new Label("0");
endLabel = new Label("31");
GridPane.setHalignment(startLabel, HPos.LEFT);
GridPane.setHalignment(endLabel, HPos.RIGHT);
this.add(inputOutputPreviousButton, 0, 0, 1, 2);
this.add(inputCellsContainer, 1, 0);
this.add(outputCellsContainer, 1, 1);
this.add(inputOutputNextButton, 2, 0, 1, 2);
this.add(startLabel, 1, 2);
this.add(endLabel, 1, 2);
}
public int getIndex() {
return index;
}
private class InputOutputCell extends StackPane {
#FXML
Text text;
#FXML
Rectangle rectangle;
public InputOutputCell(String text, Color color, int type) {
setMinSize(0, 0);
this.text = new Text(text);
rectangle = new Rectangle();
if (type == 0) {
rectangle.widthProperty().bind(inputCellsContainer.widthProperty().divide(32));
rectangle.heightProperty().bind(inputCellsContainer.widthProperty().divide(32));
} else {
rectangle.widthProperty().bind(outputCellsContainer.widthProperty().divide(32));
rectangle.heightProperty().bind(outputCellsContainer.widthProperty().divide(32));
}
rectangle.maxWidth(Double.MAX_VALUE);
rectangle.maxHeight(Double.MAX_VALUE);
rectangle.setArcHeight(10);
rectangle.setArcWidth(10);
rectangle.setFill(color);
rectangle.setStroke(Color.BLACK);
getChildren().addAll(rectangle, this.text);
}
public void setText(String text) {
this.text.setText(text);
}
public String getText() {
return this.text.getText();
}
}
}
I want cells 1,0 and 1,1 to be resizable and clearly by increasing their width the height of row 0 and 1 should not be increasing equally. If there should be any height left I want the row 3 to take it because row 2 should not grow to height at all.
You did a fair bit of explanation (which is good) but it is complex enough to make me read 10 times, and I think I understood 70% of it. The most puzzling part is this line:
the rest of the available space should take another row below so the aspect ratio of that one cell is preserved according to the element inside that cell.
Not sure which "cell" you are referrring to, and what you mean by keeping aspect ratio.
Now for the actual answer, I think the most obvious one that I can see is that you have given rows 0 and 1 ALWAYS priority for VGrow.
You have 3 rows that has ALWAYS VGrow, and what the GridPane will do is to give all children to whatever space that they preferred to have, then distribute all the "leftover" spaces to rows with ALWAYS. That is why the 3 gaps in your image has the same height.

JavaFX: Coordinates of nodes are wrong for no obvious reason

I'm trying to visualize a graph in JavaFX. The nodes shall be interactive. At the moment I have a VBox and place as many HBoxes into it as I need levels in my graph. All the HBoxes are set to position their childrens centered. The childrens represent the individual nodes and are Buttons.
The VBox itself is placed in a StackPane with a Canvas as another children of the StackPane.
This StackPane is then placed into the Scene.
I want to use the Canvas to draw the joining Edges between the nodes. To get The coordinates of the nodes i use:
System.out.println(button.localToScene(button.getBoundsInLocal().getMinX(),
button.getBoundsInLocal().getMinY()));
But no matter where the button is positioned, the resulting coordinate remains:
Point2D [x = 0.0, y = 108.0]
for all the buttons. Every single Button has apparently the same coordinate though it's displayed correctly!
Please help me, im quite desperate right now...
Edit:
Here is the minimal example that works:
Main.java
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
VBox root = new VBox();
HBox buttons = new HBox();
TabPane tabs = new TabPane();
buttons.getChildren().addAll(
new Button("1"),
new Button("2"),
new Button("3"));
tabs.getTabs().addAll(
new TabBoxes("____A____"),
new TabBoxes("____B____"),
new TabBoxes("____C____")
);
root.getChildren().addAll(buttons, tabs);
primaryStage.setScene(new Scene(root, 500, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
TabBoxes.java
public class TabBoxes extends Tab {
private VBox vBox = new VBox();
private ArrayList<HBox> hBoxes;
private Canvas canvas = new Canvas();
private StackPane stack = new StackPane();
private static final int V_INIT_SPACING = 60;
private static final int V_SPACING = 60;
private static final int H_SPACING = 60;
TabBoxes(String s){
super(s);
this.setContent(stack);
setClosable(false);
stack.getChildren().add(canvas);
stack.getChildren().add(vBox);
canvas.setWidth(2000);
canvas.setHeight(1080);
hBoxes = new ArrayList<>();
vBox.setSpacing(V_SPACING);
vBox.translateYProperty().set(V_INIT_SPACING);
for (int i = 0; i < 5; i++) {
hBoxes.add(new HBox() {
{
setSpacing(H_SPACING);
setAlignment(Pos.CENTER);
}
});
}
for (int i = 0; i < 5; i++) {
for (int j = 0; j <= i; j++) {
hBoxes.get(i).getChildren().add(new Button(i + "||" + j){
{
setPrefSize(100,60);
setOnAction(e-> System.out.println("My coordinates are: " + localToScene(getBoundsInLocal().getMinX(),getBoundsInLocal().getMinY())));
}
});
}
}
vBox.getChildren().addAll(hBoxes);
}
}
boundsInLocal are coordinates in the Button's own coordinate system. It's not surprising, that these values are the same.
If you want to calculate the coordinates in the Canvas coordinate system, transform the coordinates to the StackPane coordinates and then transform the coordinates to the Canvas coordinates:
Node n = button;
Point2D p = new Point2D(button.getBoundsInLocal().getMinX(), button.getBoundsInLocal().getMinY());
while (n != stackPane) {
p = n.localToParent(p);
n = n.getParent();
}
p = canvas.parentToLocal(p);

JavaFx path transition in gridPane

I can not understand how moveTo method work in javafx. here is my example
I have a GridPane that consist of 4 columns and 1 row. All the columns h has a StackPane and the last Stackpane also has an empty label.
public class PathTransitionTExample extends Application {
StackPane pane;
GridPane grid;
Label label;
Scene scene;
#Override
public void start(Stage primaryStage) {
label = new Label();
label.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);
grid = new GridPane();
grid.getStyleClass().add("gridPane");
for (int x = 0; x < 4; x++) {
grid.add(pane = new StackPane(), x, 0);
pane.setPrefSize(50, 50);
pane.getStyleClass().add("stackPane");
}
pane = (StackPane) grid.getChildren().get(3);
pane.getChildren().add(label);
scene = new Scene(grid, 260, 50);
scene.getStylesheets().add(PathTransitionTest.class.getResource("pathCSS.css").toExternalForm());
scene.setOnMouseClicked(me -> pathTransition());
primaryStage.setTitle("Path Transition");
primaryStage.setScene(scene);
primaryStage.show();
}
//pathCSS.css
.gridPane{
-fx-background-color: red;
-fx-hGap: 20;
}
.stackPane{
-fx-background-color: orange;
}
.label {
-fx-background-color: blue;
}
I want to move the label in the grid pane from 0.3 to 0.0 however when I am trying to do that the whole transition slips.
private void pathTransition() {
//the Coordinates of the label
double oldMinX = grid.getChildren().get(3).getLayoutX();
double oldMinY = grid.getChildren().get(3).getLayoutY();
//the coordinates of the stack pane where i want the label move
double newMinX = grid.getChildren().get(1).getLayoutX();
double newMinY = grid.getChildren().get(1).getLayoutY();
Path path = new Path();
path.getElements().add(new MoveTo(oldMinX, oldMinY ));
path.getElements().add(new LineTo(newMinX,newMinY ));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(new Duration(500));
pathTransition.setPath(path);
pathTransition.setNode(label);
pathTransition.play();
}
If I change the arguments of MoveTo and LineTo by the following I can achieve the animation I want but I cant understand why. :\
double oldMinX = grid.getChildren().get(3).getLayoutX() -185;
double oldMinY = grid.getChildren().get(3).getLayoutY()+ grid.getChildren().get(3).getBoundsInLocal().getHeight()/2 ;
double newMinX = grid.getChildren().get(1).getLayoutX()-255 ;
double newMinY = grid.getChildren().get(1).getLayoutY() + grid.getChildren().get(0).getBoundsInLocal().getHeight()/2 ;
I guess It is because transitions use different coordinate systems than scenes but I cant really find anything that explains well :( Could someone give me some hints how It is working?
Thank you so much in advance.
I realized that i shouldn't use GridPane. If i do it without using containers the transition is working fine.

Centering ImageView using JavaFX

I am trying to center an ImageView in a JavaFX GUI. However, the image just appears in the top left corner of the window.
My code:
FlowPane picPane = new FlowPane();
Image photo = new Image(url, 300, 300, false, false);
ImageView myView = new ImageView(photo);
picPane.getChildren().add(myView);
picPane.setColumnHalignment(HPos.CENTER);
The property "columnHalignment" is ignored for horizontal flowpanes (default settings).
Take a look at the HBox Layout. There you can specify the alignment properly.
EDIT: some code:
public HBox addHBox(URL url) {
HBox hbox = new HBox();
hbox.setPadding(new Insets(15, 12, 15, 12));
hbox.setSpacing(10);
hbox.setStyle("-fx-background-color: #336699;");
hbox.setAlignment(Pos.TOP_CENTER);
Image photo = new Image(url, 300, 300, false, false);
ImageView myView = new ImageView(photo);
hbox.getChildren().add(myView);
return hbox;
}
I am very sorry. I cannot test it at the moment. But it should work this way.
This is the simplest solution I found.
Image img = new Image( "file:"+ imgURL );
imgViewer.setImage(img);
centerImage(imgViewer); // this does the centering..
public static void centerImage(ImageView imgView) {
Image img = imgView.getImage();
if (img != null) {
double w = 0;
double h = 0;
double ratioX = imgView.getFitWidth() / img.getWidth();
double ratioY = imgView.getFitHeight() / img.getHeight();
double reducCoeff = 0;
if(ratioX >= ratioY) {
reducCoeff = ratioY;
} else {
reducCoeff = ratioX;
}
w = img.getWidth() * reducCoeff;
h = img.getHeight() * reducCoeff;
imgView.setX((imgView.getFitWidth() - w) / 2);
imgView.setY((imgView.getFitHeight() - h) / 2);
}
}
Just modified from this link's solution a little: Centering an image in an ImageView
Your Image object sits in myview,
myview sits in picPain.
picPain is centered, however its' contents are not centered in terms of "drawing" with the image holder Graphics object!
The component drawing is myview.
Second, the size of the container myview requires measuring on each side from the container picPain.
While containers have alignments of their internal contents "they have defaults for their sizing and position they draw from the top left corner".
Set them and calculate them!

Categories

Resources