How to set different height to children of VBox - java

I am trying to create a TreeView where each TreeCell represents a statement in the program. By invoking setGraphics, I want to replace the default cell with a customized Node, like the following figure.
I am using a VBox as all the children are placed vertically. The Header and Footer are 2 tiny areas reserved for Drag-and-Drop operations. e.g. I can drag-and-drop a new statement before the current one by moving the cursor over its header.
I want to use Label for header and footer, and I want to limit their height to 2 pixels, so I have tried:
public VBox getTestContainer() {
VBox vbox = new VBox();
Label header = new Label();
header.setPrefHeight(2);
header.setPrefWidth(200);
... ... ...
vbox.getChildren().add(header, ..., footer);
}
public void start(Stage stage) throws Exception {
Group root = new Group();
root.getChildren().add(getTestBlock());
stage.setTitle("Test");
stage.setScene(new Scene(root, 400, 300));
stage.show();
}
To make sure that the VBox is not resized by other layout pane, I have simply put it in a Group.
The setPrefWidth works, it gives me a 200-pixel wide Label and VBox, but the setPrefHeight doesn't. As you can see the height of the header is much larger than 2 pixels.
My question is how to correctly set the height of Label?

Setting the prefHeight (or maxHeight) to a value smaller than the computed min height of the Label still results in the lable's minimum height being used as smallest possible height for the Label. This minimum height is based on the font size.
header.setFont(Font.font(2));
Would reduce the calculated minimum height.
Since it doesn't seem like you're trying to add any text to those nodes, just use Regions instead of Labels as first and last child of the VBox.

Related

Why does JavaFX Node have wrong sizes in initialize method after css is applied?

I have a VBox that have several AnchorPane. I decided to add css style to these AnchorPane: -fx-border-width. But the VBox is inside another "main" AnchorPane, and main AnchorPane is inside the ScrollPane. If I'm adding css to elements, not all the elements in the ScrollPane will be displayed. For testing, I made border-width 0 0 10 0. I have 20 elements, so the total size of the VBox after applying the style to each element of the VBox should increase by 200. In the controller's intiailize() method, after calling the layout() and applyCss(), getHeight( ) for the VBox returns 1200 (and I know that is the height of VBox if I wouldn't add css). Then for the main anchorPane I'm setting the height setPrefHeight(vbox.getHeight()). But after my application finally runs, I see that the VBox size is 1400 as expected. But the size of the AnchorPane still 1200, and therefore if I will scroll to the end, some elements inside the ScrollPane will not be visible. How can I fix it?
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
for (int i = 0; i < 20; i++) {
addElementsToVBox();
}
vbox.applyCss();
vbox.layout();
mainAnchorPane.applyCss();
mainAnchorPane.layout();
System.out.println(vbox.getHeight()); //in my case will be 1200,
//but after the application will complete the loading, vbox.getHeight() will return 1400
//if I would invoke it from button's onAction() for example
mainAnchorPane.setPrefHeight(vbox.getHeight());
}
private void addElementsToVBox() {
Label label = new Label();
label.setText("Test");
AnchorPane anchorPane = new AnchorPane();
anchorPane.getChildren().add(label);
anchorPane.setPadding( new Insets(2, 0, 2, 0) );
anchorPane.setStyle("-fx-border-width: 0 0 10 0; -fx-border-color: black");
anchorPane.applyCss();
anchorPane.layout();
vbox.getChildren().add(anchorPane);
}
In my case, I was using mainAnchorPane.setPrefHeight(vbox.getHeight()) because after experiments I noticed that without that method mainAnchorPane wouldn't change the height. That is because I set exact pref height value for AnchorPane in SceneBuilder. But after setting USE_COMPUTED_SIZE, my AnchorPane will have the size that I actually want. So all I need is:
set computed size for all fields

Bind layout to screen resolution

My layout looks like that:
Panel is a VBox and Content is a HBox. Both are contained in an HBox.
I need to make panel fixed size, so right now I'm doing it like that:
VBox panel = new VBox();
double fixedWidth = Screen.getPrimary().getBounds().getWidth() / 10;
panel.setMinWidth(fixedWidth);
panel.setMaxHeight(fixedWidth);
... but what if user's screen resolution change? That code doesn't handle that and I'm afraid the Screen class doesn't provide any type of callback.
Your title and most of your question indicate you want panel to use 10% of the screen’s width, and content to use 90% of the screen’s width. However, you also use the term “fixed width” which is something different; namely, that panel would have the same width at all times, regardless of screen size.
I shall assume you meant the first concept: that you want panel to use 10% of the width and content to use 90% of the width.
Instead of an HBox, use a GridPane. Set its column constraints to 10% and 90%.
ColumnConstraints panelWidth = new ColumnConstraints();
panelWidth.setPercentWidth(10);
panelWidth.setFillWidth(true);
ColumnConstraints contentWidth = new ColumnConstraints();
contentWidth.setPercentWidth(90);
contentWidth.setFillWidth(true);
RowConstraints rowConstraints = new RowConstraints();
rowConstraints.setFillHeight(true);
rowConstraints.setVgrow(Priority.ALWAYS);
GridPane pane = new GridPane();
pane.addRow(0, panel, content);
pane.getColumnConstraints().setAll(panelWidth, contentWidth);
pane.getRowConstraints().setAll(rowConstraints);
Based on your current solution, you seem to be giving 10% size to your panel which means 90% is the content panel. You can directly bind screen size to your scene object and initialize your scene with screen size. It will make the whole application reactive to the screen resolution changes. Here is a sample from my code on how I do it:
final Scene scene = new Scene(root,width,height);
logger.debug("Scene Created");
stage.centerOnScreen();
stage.setScene(scene);
final ChangeListener<Number> listener = new ChangeListener<Number>()
{
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, final Number newValue)
{
logger.debug("Scene Resize Started");
Scale scale = new Scale();
scale.xProperty().bind(scene.widthProperty().divide(width));
scale.yProperty().bind(scene.heightProperty().divide(height));
scale.setPivotX(0); scale.setPivotY(0);
root.getTransforms().clear();
root.getTransforms().addAll(scale);
logger.debug("Scene Resize Ended");
}
};
scene.widthProperty().addListener(listener);
scene.heightProperty().addListener(listener);
This takes care of resize as well as resolution changes. The width and height variable in the start are width and height captured from Screen of the user.

ScrollPane javafx auto scrolling (setting vvalue to 1.0) only scrolls to the item before the last

So, the situation is... I have a vbox inside a scrollpane. I am adding hbox's into the vbox and then calling vbox.setVvalue(1.0) after every insert.
However say, there are 5 hbox's, the scroller only makes it so that the last visible item is the 4th hbox - with one hbox below what is currently seen(needing to be scrolled down to be visible).
I've found a solution which is to bind the scrollpane's vvalue property to the vbox's heightproperty like so: scrollPane.vvalueProperty().bind(vbox.heightProperty()) which i assume changes the vvalue to the max every time the vbox height is changed (i.e when a new hbox is added).
However, i still would like to improve my knowledge and why the first (setting the vvalue of the scrollpane after every insert) is different from binding the properties. Thanks!
Setting the new vvalue happens before the layout pass caused by modifying the VBox, but the result applied before the layout pass. Since the formula for the y coordinate of the top that are shown in the viewport is
top = max(0, vvalue * (contentHeight - viewportHeight))
and during the layout pass the content's top left is kept in place, you see the bottom of the old content at the bottom of the viewport.
To fix this you could manually trigger a layout pass on the ScrollPane using
scrollPane.applyCss();
scrollPane.layout();
Example
#Override
public void start(Stage primaryStage) {
VBox content = new VBox();
ScrollPane scrollPane = new ScrollPane(content);
VBox.setVgrow(scrollPane, Priority.ALWAYS);
Button button = new Button("fill");
button.setOnAction(evt -> {
for (int i = 0; i < 20; i++) {
content.getChildren().add(new Text(Integer.toString(i)));
}
System.out.println("content size before layout: " + content.getHeight());
// manually layout scrollPane
scrollPane.applyCss();
scrollPane.layout();
System.out.println("content size after layout: " + content.getHeight());
scrollPane.setVvalue(1d);
});
Scene scene = new Scene(new VBox(button, scrollPane), 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}

JavaFX - How do you add multiple elements to a StackPane?

I'm new to JavaFX. I'm trying to create a simple centred menu that contains text with buttons below.
I've created two elements, Text title and Button testButton. Then I created StackPane stackPane. I'm then trying to add the two elements to the stackPanes children and adding that to a new Scene. However, only the last element shows up.
How can I add multiple elements to the StackPane?
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Test Title");
Text title = new Text("hey!");
StackPane.setAlignment(title, Pos.TOP_CENTER);
Button testButton = new Button("Testing");
StackPane.setAlignment(testButton, Pos.TOP_CENTER);
StackPane stackPane = new StackPane();
stackPane.setPrefSize(300, 300);
stackPane.setPadding(new Insets(25, 0, 0, 0));
stackPane.getChildren().add(title);
stackPane.getChildren().add(testButton);
Scene scene = new Scene(stackPane);
primaryStage.setScene(scene);
primaryStage.show();
}
I want to reference the official documentation here: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/StackPane.html, especially:
StackPane lays out its children in a back-to-front stack.
The z-order of the children is defined by the order of the children list with the 0th child being the bottom and last child on top. If a border and/or padding have been set, the children will be layed out within those insets.
Now, to answer your question: You do it as you did, but you probably want an offset as both the children are at the same position, hence the one later added is overlaying all the previous ones.
You can check that by changing e.g.
Text title = new Text("Adding a very, very, very, very, very, very long text here... now that vile button should not overlap me anymore!");
or setting the alignment differently.
If you don't want to bother with the optimal layout by manually positioning, it's probably better to use another Pane that does that for you, e.g. one of the direct known subclasses here: https://docs.oracle.com/javafx/2/api/javafx/scene/layout/Pane.html

Getting the absolute coordinates of a node in a BorderPane with some null regions

I have a set of custom made "buttons" for the menu screen of my game. It's basically a StackPane with a Rectangle and a Text node stacked. Basically this is similar to how my buttons are structured.
Pane button1 = new StackPane(new Rectangle(100, 50), new Text("Play!"));
Pane button2 = [....];
Then I insert buttons into a VBox and into my menu along with a header text:
VBox buttons = new VBox(button1, button2, button3...);
Pane menuScreen = new BorderPane(buttons, new Text("The Game"), null, null, null);
However, for my custom detection for mouse position compared to the buttons I need to know the buttons' positions...
int x = button1.getLayoutX(); //returns 0
int x = button1.getTranslateX(); //returns 0
int x = button1.localToScene(0, 0).getX(); //returns 0
int x = button1.localToScene(buttons.get(0).getBoundsInLocal()).getMinX(); //returns 0
int x = button1.localToScene(buttons.get(0).getBoundsInLocal()).getMaxX(); //returns the width of the entire scene
int x = buttons.get(0).getTranslateX(); //returns 0
int y = button1.localToScene(buttons.get(0).getBoundsInLocal()).getMinY();
The last case returns 234.0 if I have the VBox set with .setAlignment(Pos.CENTER) and 64.0 if I don't. But for .getMinX() it stays at 0.0 in either case. I believe it's related to the BorderPane's left/right/bot regions being set to null while the top region has the title text.
I cannot find any way of getting the x coordinate when the buttons are in a layout pane other than the Pane class itself. I tried StackPane as well. My suspicion is that there is no fixed coordinate and that properties are involved, but I only get confused from reading about it when I don't know what I'm looking for.
I have tried solutions from this quesion and this seems to be the same but as I mentioned I'm afraid my minX() doesn't have a set value since the VBox is the only thing filling the center row of my BorderPane.
Edit: StackPane seems to give the right values for .getMinX() when I use .setAlignment(Pos.CENTER), but I am not allowed to do that with the text, only with the VBox, so then the text gets stuck on top of the buttons.
My issue was cause by the VBox and my StackPanes taking up all the possible space, not just the area of the visible Rectangle. Problem was solved by calling setMaxWidth(100) on the StackPane which contained the Rectangle and the Text.
Also, the only problem seems to be that you have a zero x-coordinate. Maybe this is "real". Set a background on button1 to see where it is in the layout. – James_D

Categories