Set group of objects as background of a scene - java

I am trying to set the background of a scene. What I want as the background is a group called stars. My scene contains a VBox that contains a Label and two buttons. When I add the group to the scene, the VBox is moved, and some of the objects are cut off. I need a way to add the group to the scene without adding it to the VBox. I have tried adding the VBox and the group to another group, but the program does not run at all.
I define the objects to be set as the background in:
public static void background()
{
Group stars = new Group();
starsLink = stars;
for (int starInt = 0; starInt < 480; starInt++)
{
Circle star = new Circle(Math.random() * 1024, Math.random() * 600, 1, Color.web("white", 1));
stars.getChildren().add(star);
}
}
and I add the objects to the VBox and set the scene of the stage in:
VBox helpVBox = new VBox();
helpVBox.getChildren().addAll(plotLabel, controlsLabel, instructionsLabel, menuButton);
helpVBox.setAlignment(Pos.CENTER);
helpVBox.setSpacing(10);
Scene helpScene = new Scene(helpVBox);
helpScene.getStylesheets().add(stellarClass.class.getResource("/CSS/style.css").toExternalForm());
helpSceneLink = helpScene;

I would recommand something like this:
Stage
--Scene
----root (Stackpane or something like that)
-------yourBackgroundGroup
-------yourForeGround (your Vbox with content)
Make sure to add your VBox after the BackgroundGroup or call vbox.toFront() otherwise the background will cover the view :P

Related

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.

How to set different height to children of VBox

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.

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 to center a TilePane but place the TilePane children from left-to-right?

Like what the title suggest, I'm trying to center a TilePane in its parent container, but at the same time, I want the child nodes of the TilePane to be placed from left-to-right.
#Override
public void start(final Stage primaryStage) throws Exception {
final VBox root = new VBox();
final Scene sc = new Scene(root);
primaryStage.setScene(sc);
final TilePane tp = new TilePane();
root.getChildren().add(tp);
tp.setPrefColumns(3);
tp.setAlignment(Pos.TOP_LEFT);
tp.setStyle("-fx-background-color: green;");
root.setAlignment(Pos.CENTER);
root.setFillWidth(true);
Stream.iterate(0, i -> i + 1).limit(5).forEach(i -> {
Region r = new Region();
r.setPrefSize(200, 200);
r.setStyle("-fx-background-color: red; -fx-border-color: blue; -fx-border-width: 1;");
tp.getChildren().add(r);
});
primaryStage.show();
}
This creates a window like this:
Increasing window width causes this:
Continuing to increase window height causes this:
Setting root.setFillWidth(false) causes:
What can I do to make sure that the whole TilePane remains centered while the Region child nodes continues to be placed from left on each row?
Update
To make it more clear, the TilePane should, during a resize, tries to fit in as many tiles (regions in this example) in all rows except the top row. In other words, there should not be any green space at the right side of the first row. Alternatively, the green space should be equal at both the left and right side. Additionally, the tiles must be placed from left to right. Setting TilePane.setAlignment(Pos.CENTER) would place any "leftover" tiles at the center of the last row, which is not acceptable.
Set the preferred size of the tiles via TilePane. Prevent the parent from resizing the TilePane beyond it's prefered size by setting fillWidth to false and use a listener to the width property of the VBox to set the prefColumns property:
#Override
public void start(Stage primaryStage) {
final VBox root = new VBox();
final Scene sc = new Scene(root);
primaryStage.setScene(sc);
final TilePane tp = new TilePane();
tp.setPrefTileWidth(200);
tp.setPrefTileHeight(200);
root.getChildren().add(tp);
tp.setPrefColumns(3);
tp.setAlignment(Pos.TOP_LEFT);
tp.setStyle("-fx-background-color: green;");
root.setAlignment(Pos.CENTER);
root.setFillWidth(false);
// set prefColumns from a listener instead of a binding
// to prevent the initial value from being set to 0
root.widthProperty().addListener((o, oldValue, newValue) -> {
// allow as many columns as fit the parent but keep it in
// range [1, childCount]
tp.setPrefColumns(Math.min(tp.getChildren().size(),
Math.max(1, (int) (newValue.doubleValue() / tp.getPrefTileWidth()))));
});
Stream.iterate(0, i -> i + 1).limit(5).forEach(i -> {
Region r = new Region();
r.setStyle("-fx-background-color: red; -fx-border-color: blue; -fx-border-width: 1;");
tp.getChildren().add(r);
});
primaryStage.show();
}
Update by OP
I was amazed by this approach, and I tried something slightly more dynamic.
root.widthProperty().addListener((o, oldValue, newValue) -> {
double maxWidth = tp.getChildren()
.stream()
.filter(n -> n instanceof Region)
.map(n -> ((Region) n).getWidth())
.max(Double::compareTo)
.orElse(0d);
tp.setPrefColumns(Math.min(tp.getChildren().size(),
Math.max(1, (int) (newValue.doubleValue() / maxWidth))));
});
Stream.iterate(0, i -> i + 1).limit(5).forEach(i -> {
Region r = new Region();
Random random = new Random();
r.setPrefSize(random.nextInt(150) + 50, random.nextInt(150) + 50);
System.out.println(r.getPrefWidth());
System.out.println(r.getPrefHeight());
r.setStyle("-fx-background-color: red; -fx-border-color: blue; -fx-border-width: 1;");
tp.getChildren().add(r);
});
This removes the need to set a static width/height for each tile.
While this works in this basic example, I have yet tried this on more complex tile children.

When adding a second item to my stackpane, the first item loses its Event/MouseOn. Why? How can I fix? JavaFX

I have a stackpane. When I add a second item to my stack pane, both show up, but I can't click on my first item anymore. It becomes 'unclickable'.
what ever I defined in my .setonmouse does not work. It works for my second item. If I switch the order they are in the stack pane, the other one works, but not both.
is there a fix for this? This is what my program looks like:
I want my 'grid' centered ALWAYS. There are buttons to the left centered in a column, there will be buttons on the right later on, and there will be buttons/Text on top of the grid and buttom in the margins later on too.
I want everything to be clickable.
http://img688.imageshack.us/img688/6025/examplerg.png
StackPane orders items in Z-order: latter above the former. So, your second item gots all mouse clicks and first one (being covered by second) doesn't get anything.
For layout you've described you can use BorderPane:
public void start(Stage stage) throws Exception {
BorderPane root = new BorderPane();
root.setCenter(new Rectangle(100,100, Color.RED));
root.setLeft(new Rectangle(10,10, Color.BLUE));
root.setRight(new Rectangle(10,10, Color.CYAN));
stage.setScene(new Scene(root,300,300));
stage.show();
}
You can make any Pane "mouse transparent", so that it doesn't consume any click events, and lets them pass through to the stack under it.
Here's some example code... this example sets up 4 panes in a stack, with just the mainPane accepting clicks to begin with.
StackPane rootPane = new StackPane();
VBox mainPane = new VBox(80);
BorderPane helpOverlayPane = new BorderPane();
helpOverlayPane.setMouseTransparent(true);
Canvas fullScreenOverlayCanvas = new Canvas();
fullScreenOverlayCanvas.setMouseTransparent(true);
VBox debugPane = new VBox();
debugPane.setAlignment(Pos.BASELINE_RIGHT);
AnchorPane debugOverlay = new AnchorPane();
debugOverlay.setMouseTransparent(true);
debugOverlay.getChildren().add(debugPane);
AnchorPane.setBottomAnchor(debugPane, 80.0);
AnchorPane.setRightAnchor(debugPane, 20.0);
rootPane.getChildren().addAll(mainPane, fullScreenOverlayCanvas, debugOverlay, helpOverlayPane);
Now, when you want to use your canvas to draw on top, make sure you change mouse transparent to false for just that stack, and keep all panes on top of it mouse transparent.
fullScreenOverlayCanvas.setMouseTransparent(false);
debugOverlay.setMouseTransparent(true);
fullScreenOverlayCanvas.setVisible(true);
doSomethingWithCanvasThatNeedsMouseClicks();
P.S. I did some editing of the code I had, so it may not run as-is. Also, see discussion of making only parts of panes transparent here:
JavaFX Pass MouseEvents through Transparent Node to Children

Categories