Bind layout to screen resolution - java

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.

Related

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();
}

ImageView always scaling beyond scene size, can't seem to force it to fit

I am new to JavaFX (been working with swing for a long time) and am trying to work with BorderPane. One would assume BorderPane is similar to BorderLayout but the big difference is the center of BorderPane will expand to fit its contents while BorderLayout will shrink to fit the window.
I am using JFXPanel in a JFrame and have a 3 part interface: A panel on the left (some text), some buttons on the bottom (flow control), and in the center want to have a dynamic panel/pane, that for the most part will be just an imageview. I set it all up and it works fine, but I'm working with camera images here which are way bigger than my monitor. I've tried scaling the images down by binding the imageview width to different things (such as anchor pane, scene size (works, but not properly), etc. The issue I am having is that since borderpane's center panel expands to fit its content, it will expand and never have a proper value I can bind to. I need the image to be fully visible in the window at any size.
Here's the code I've been working with.
protected void setupFXWindow(JFXPanel mainPanel) {
butNext = new Button("Next Step");
butBack = new Button("PreviousStep Step");
butQuit = new Button("Cancel Signature Generation");
butNext.setTooltip(new Tooltip("Next step..."));
butBack.setTooltip(new Tooltip("Previous Step..."));
butQuit.setTooltip(new Tooltip("Quit generating a signature"));
Group root = new Group();
Scene scene = new Scene(root, javafx.scene.paint.Color.ALICEBLUE);
javafx.scene.image.Image fximage = new javafx.scene.image.Image(new File(image.getSourceFilePath()).toURI().toString());
ImageView iv1 = new ImageView();
iv1.setImage(fximage);
iv1.setPreserveRatio(true);
VBox directionsPanel = new VBox();
HBox authorflowPanel = new HBox(); //bottom buttons for next, back, etc.
mainPanel.setScene(scene);
//INSTRUCTIONS
directionsStepLabel = new Text();
directionsLabel = new Text();
setDirectionsText("Directions will be placed here.");
exampleLabel = new Text("Example");
exampleIconLabel = new Text("An example image will be shown here.");
directionsPanel.setPadding(new javafx.geometry.Insets(5, 5, 5, 5));
directionsPanel.getChildren().addAll(directionsStepLabel, directionsLabel, exampleLabel);
authorflowPanel.getChildren().addAll(butBack, butQuit, butNext);
BorderPane bp = new BorderPane();
bp.prefHeightProperty().bind(scene.heightProperty());
bp.prefWidthProperty().bind(scene.widthProperty());
bp.setLeft(directionsPanel);
bp.setBottom(authorflowPanel);
bp.setCenter(iv1);
root.getChildren().add(bp);
}
This code sample doesn't have iv1 (imageview) binded to anything, cause at this point I have no idea what I can bind to that will give me the remaining space in the scene. Since I cannot use the full width or height of the scene, I'm at a loss of what I am supposed to do here.
The code above makes it look like this:
Wrap the ImageView in some kind of Pane (e.g. a StackPane). Then the pane will fill the center region of the border pane and you can bind to its width and height:
ImageView iv1 = new ImageView();
iv1.setImage(fximage);
iv1.setPreserveRatio(true);
StackPane imageContainer = new StackPane(iv1);
iv1.fitWidthProperty().bind(imageContainer.widthProperty());
iv1.fitHeightProperty().bind(imageContainer.heightProperty());
// ...
bp.setCenter(imageContainer);

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

Disable automatic resizing, without disabling manual resizing

Coding a GUI in a Java project I've encountered a problem with JavaFX.
I've not found any soulutions for my specific problem, so here I am:)
Is it possible to let a JavaFX scene be resizeable by the user and at the same time have it not resized by child nodes, that are bigger, than the window?
Here is some sample code:
#Override
public void startGUI(int width, int height) {
this.main = new MainWindow(this, this.logic);
this.scene = new Scene(this.main.getRoot());
this.main.setScene(this.scene);
this.primaryStage.setScene(this.scene);
this.primaryStage.setMinHeight(height);
this.primaryStage.setMinWidth(width);
}
The 'MainWindow' has got a childnode, that can be very big (>1024x768).
I want the window not to be resized by this node, but at the same time, the user should be able to resize the window by dragging its borders.
Use a Scene constructor which specifies initial size constraints.
For example:
Scene scene = new Scene(root, 600, 400);
That way the initial size of the Scene will be taken from these constraints rather than calculated from the preferred size of the root node.

Categories