JavaFX: Button(s) in HBox with same width - java

I have a HBox and two (or more) Button's in it. And I want all the buttons be of the same width. I can't set width of the buttons in pixels because texts are taken from resource bundle for every language (so length of the text is variable). This is the code I tried, but didn't succeed:
Button but1=new Button("Long text");
Button but2=new Button ("Text");
HBox.setHgrow(but1, Priority.ALWAYS);
HBox.setHgrow(but2, Priority.ALWAYS);
HBox hbox=new HBox();
hbox.getChildren().addAll(but1,but2);
Scene scene=new Scene(hbox, 1000, 600);
stage.setScene(scene);
stage.show();
What is my mistake?

You need to set the maxWidth of the Button to Double.MAX_VALUE and also set HBox.setHgrow() to Priority.ALWAYS to make the Button fill the available width in the HBox.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class Test extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Button but1 = new Button("Long text");
Button but2 = new Button ("Text");
HBox hbox=new HBox();
hbox.getChildren().addAll(but1,but2);
but1.setMaxWidth(Double.MAX_VALUE);
but2.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(but1, Priority.ALWAYS);
HBox.setHgrow(but2, Priority.ALWAYS);
Scene scene=new Scene(hbox, 1000, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch();
}
}

Set button maximum width to maximum value:
but1.setMaxWidth(Double.MAX_VALUE);
But it will only resize buttons to fill hbox width. If you need buttons with the same width, you should find the longest button and then set its width to other buttons.
but1.setPrefWidth(width);

Related

JavaFX split space evenly between two side panels in an HBox

I have an HBox that contains a square VBox in the center. The HBox width can be made larger than it's height, leaving extra space on the sides. I want to fill this extra space on the left and right with separate VBoxes where I will put buttons, info, etc. I want these two VBoxes, the "side panels," to always be of equal width. However, as you can see from the image below, when the right panel has a button and the left does not, it is wider than the left panel:
I thought
rightPanel.minWidthProperty().bindBidirectional(leftPanel.minWidthProperty());
rightPanel.prefWidthProperty().bindBidirectional(leftPanel.prefWidthProperty());
would do the trick, but it didn't.
Here's my code:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Control;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class FX010 extends Application{
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
GamePanel gp = new GamePanel();
Scene scene = new Scene(gp, 800, 600);
primaryStage.setTitle("testing");
primaryStage.minWidthProperty().bind(primaryStage.heightProperty());
primaryStage.setScene(scene);
primaryStage.sizeToScene();
primaryStage.show();
}
public class GamePanel extends HBox{
public GamePanel() {
this.setMinHeight(400);
final VBox boardBox = new VBox();
boardBox.alignmentProperty().set(Pos.CENTER);
this.alignmentProperty().set(Pos.CENTER);
VBox leftPanel, rightPanel;
leftPanel = new VBox();
rightPanel = new VBox();
leftPanel.setBorder(new Border(new BorderStroke(Color.DARKRED, BorderStrokeStyle.SOLID,
CornerRadii.EMPTY,new BorderWidths(1))));
rightPanel.setBorder(new Border(new BorderStroke(Color.DARKRED, BorderStrokeStyle.SOLID,
CornerRadii.EMPTY,new BorderWidths(1))));
rightPanel.minWidthProperty().bindBidirectional(leftPanel.minWidthProperty());
rightPanel.prefWidthProperty().bindBidirectional(leftPanel.prefWidthProperty());
rightPanel.getChildren().add(new Button("testtesttest"));
HBox.setHgrow(leftPanel, Priority.ALWAYS);
HBox.setHgrow(rightPanel, Priority.ALWAYS);
StackPane board = new StackPane(new Rectangle(100,100,Color.RED));
board.setBorder(new Border(new BorderStroke(Color.DARKBLUE, BorderStrokeStyle.DASHED,
CornerRadii.EMPTY,new BorderWidths(1))));
final NumberBinding binding = Bindings.min(widthProperty(), heightProperty());
boardBox.prefWidthProperty().bind(binding);
boardBox.prefHeightProperty().bind(binding);
boardBox.setMaxSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
VBox.setVgrow(board, Priority.ALWAYS);
boardBox.getChildren().add(board);
getChildren().addAll(leftPanel, boardBox, rightPanel);
//HBox.setHgrow(this, Priority.ALWAYS);
}
}
}
How can I force leftPanel to always have the same width as rightPanel, while still ensuring that boardBox is always a square?
Thanks!
It works when you add e. g.:
leftPanel.setPrefWidth(100d);
rightPanel.setPrefWidth(100d);
Edit: Maybe it would be better if you use a GridPane as a root layout where you can have three columns with a fixed percentage width like e. g.: 15 % (left-hand side), 70 % (center) and 15 % (right-hand side). But only you can know and decide what fits best for your project. :-P
After a lot of testing, I got this to work:
public class GamePanel extends HBox{
Pane leftPanel, rightPanel, iLeft, iRight;
VBox boardBox;
public GamePanel() {
this.setMinHeight(400);
iLeft = new Pane();
boardBox = new VBox();
iRight = new Pane();
boardBox.alignmentProperty().set(Pos.CENTER);
this.alignmentProperty().set(Pos.CENTER);
leftPanel = new Pane();
rightPanel = new Pane();
leftPanel.setBorder(new Border(new BorderStroke(Color.DARKRED, BorderStrokeStyle.SOLID,
CornerRadii.EMPTY,new BorderWidths(1))));
rightPanel.setBorder(new Border(new BorderStroke(Color.DARKRED, BorderStrokeStyle.SOLID,
CornerRadii.EMPTY,new BorderWidths(1))));
rightPanel.setPrefWidth(0);
leftPanel.setPrefWidth(0);
iRight.getChildren().add(new Button("testtesttest"));
rightPanel.getChildren().add(iRight);
HBox.setHgrow(leftPanel, Priority.ALWAYS);
HBox.setHgrow(rightPanel, Priority.ALWAYS);
StackPane board = new StackPane(new Rectangle(100,100,Color.RED));
board.setBorder(new Border(new BorderStroke(Color.DARKBLUE, BorderStrokeStyle.DASHED,
CornerRadii.EMPTY,new BorderWidths(1))));
final NumberBinding binding = Bindings.min(widthProperty(), heightProperty());
boardBox.prefWidthProperty().bind(binding);
boardBox.prefHeightProperty().bind(binding);
boardBox.setMaxSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
VBox.setVgrow(board, Priority.ALWAYS);
boardBox.getChildren().add(board);
getChildren().addAll(leftPanel, boardBox, rightPanel);
//HBox.setHgrow(this, Priority.ALWAYS);
}
}
This solution involves setting the left and right panel's prefWidth to zero, then adding internal panes into each panel, which the content is then added to. I think the reason that this works is because space is allocated for the prefWidth before the panel's size is expanded by the Hgrow constraint. Since equal amounts of space are added to each panel by the HBox, the panel with a greater prefWidth ends up bigger. By setting both to zero, they both start with no prefWidth and thus end up at the same size. I believe that's also why anko's answer seemed to work - because when they start with equal prefWidths and the same amount of space is added to each, they end up with the same size. However, setting a prefWidth of 100 messes with the boardBox's size (makes it not a square) when the stage's width is shrunk as small as possible.
I'm sure there is a better way to do this, and I'd be happy to accept a better answer.

JavaFX FlowPane within ScrollPane, dynamically adjust scrollpane's content and size

I currently have a ScrollPane with a FlowPane as content. The FlowPane currently initializes with no children nodes, a fixed width and a pref/min height (but no max height).
While adding items to the FlowPane at runtime (I click some UI element and something is added to the FlowPane), the ScrollPane should adjust its height in the case that the addition to the FlowPane no longer fits.
I don't understand how to set the height of the flowPane and ScrollPane so that this works - if that's the problem to begin with. At the moment, whenever the addition to the FlowPane doesn't fit its initial height, the content is added, but not visible. The scrollbar belonging to the ScrollPane never adjusts its height - if it did, I could just scroll further down and see the content.
Let's say I have a ScrollPane with some width and height, some viewport width/height, and a FlowPane with some width/height - What should my settings be for the min/pref/max sizes? How can I make a scrollPane adjust its scrollbar behaviour or make the content visible?
The ScrollPane's setFitToHeight is already set to true, which didn't seem to change anything.
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class FlowPaneTest extends Application
{
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception
{
// borderPane rootPane
BorderPane borderPane = new BorderPane();
borderPane.setMinSize(600, 600);
borderPane.setPrefSize(600, 600);
borderPane.setMaxSize(600, 600);
// container for the two scrollPanes below
FlowPane flow = new FlowPane();
borderPane.setRight(flow);
// two scrollPanes, each should resize it's height (width should be fixed) if
// children are added beyond it's current height
ScrollPane top = new ScrollPane();
ScrollPane bottom = new ScrollPane();
FlowPane scrollPaneContent = new FlowPane();
top.setContent(scrollPaneContent);
bottom.setContent(scrollPaneContent);
flow.getChildren().add(top);
flow.getChildren().add(bottom);
borderPane.setOnMouseClicked(new EventHandler<Event>()
{
#Override
public void handle(Event event)
{
Label l = new Label("test");
l.setMinSize(100, 100);
l.setPrefSize(100, 100);
l.setMaxSize(100, 100);
scrollPaneContent.getChildren().add(l);
}
});
// size settings
int width = 300, height = 300;
top.setHvalue(0.5);
top.setMinViewportHeight(height);
top.setPrefViewportHeight(height);
top.setMinViewportWidth(width);
top.setPrefViewportWidth(width);
top.setHbarPolicy(ScrollBarPolicy.ALWAYS);
top.setFitToHeight(true);
top.setMinSize(width, height);
top.setPrefSize(width, height);
top.setMaxWidth(width);
scrollPaneContent.setMinSize(width, height);
scrollPaneContent.setPrefSize(width, height);
scrollPaneContent.setMaxWidth(width);
scrollPaneContent.setPrefHeight(height);
bottom.setMinSize(width, height);
bottom.setPrefSize(width, height);
bottom.setMaxWidth(width);
bottom.setHvalue(0.5);
bottom.setMinViewportHeight(height);
bottom.setPrefViewportHeight(height);
bottom.setMinViewportWidth(width);
bottom.setPrefViewportWidth(width);
bottom.setHbarPolicy(ScrollBarPolicy.ALWAYS);
top.setFitToHeight(true);
bottom.setFitToHeight(true);
// stage
Scene scene = new Scene(borderPane, 600.0, 600.0);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Try to give the ScrollPane pref height and width and add this line
scrollPane.setFitToWidth(true);
Ending up with something similar to this ugly bit of code - It listens to the number of children in the pane and increases the size every time something is added to the list of children:
topSubPane.getChildren().addListener(new ListChangeListener()
{
#Override
public void onChanged(Change c)
{
c.next();
topSubPane.setPrefHeight(topSubPane.getHeight() + 50);
}
});
Works, but feels like an unorthodox hack. Is there really no regular way of doing this?

Divide stage into 2 gridpanes JavaFX

So Im trying to have text on the left and buttons on the right, text should have constant size and buttons should resize to fill the rest of the window.
Here is my result so far:
I dont want my text over buttons, I want them to share the whole window.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
GridPane buttons = new GridPane();
GridPane textGrid = new GridPane();
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
Button button1 = new Button();
Button button2 = new Button();
Button button3 = new Button();
Button button4 = new Button();
Button button5 = new Button();
button1.setText("Button1");
button2.setText("Button4");
button3.setText("Button3");
button4.setText("Button4");
button5.setText("Button5");
TextArea text1 = new TextArea();
text1.setText("Test");
text1.setPrefSize(100, 100);
button1.prefWidthProperty().bind(buttons.widthProperty());
button2.prefWidthProperty().bind(buttons.widthProperty());
button3.prefWidthProperty().bind(buttons.widthProperty());
button4.prefWidthProperty().bind(buttons.widthProperty());
button5.prefWidthProperty().bind(buttons.widthProperty());
button1.prefHeightProperty().bind(buttons.heightProperty());
button2.prefHeightProperty().bind(buttons.heightProperty());
button3.prefHeightProperty().bind(buttons.heightProperty());
button4.prefHeightProperty().bind(buttons.heightProperty());
button5.prefHeightProperty().bind(buttons.heightProperty());
buttons.addColumn(0, button1, button2, button3, button4, button5);
textGrid.addColumn(0, text1);
Scene scene = new Scene(root, 280, 180);
root.getChildren().addAll(buttons, textGrid);
buttons.setAlignment(Pos.TOP_RIGHT);
textGrid.setAlignment(Pos.TOP_LEFT);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
It is usually better to let the layout panes handle the layout management rather than trying to manage the layout through bindings.
Here is a sample:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.stream.IntStream;
public class Main extends Application {
private static final int N_BUTTONS = 5;
#Override
public void start(Stage stage) {
VBox buttonLayout = new VBox(
10,
IntStream.range(0, N_BUTTONS)
.mapToObj(this::createButton)
.toArray(Button[]::new)
);
HBox.setHgrow(buttonLayout, Priority.ALWAYS);
TextArea textArea = new TextArea("Test");
textArea.setPrefWidth(100);
textArea.setMaxWidth(TextArea.USE_PREF_SIZE);
textArea.setMinWidth(TextArea.USE_PREF_SIZE);
HBox layout = new HBox(10, textArea, buttonLayout);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
private Button createButton(int i) {
Button button = new Button("Button " + i);
// button.setMaxWidth(Double.MAX_VALUE);
button.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
VBox.setVgrow(button, Priority.ALWAYS);
return button;
}
public static void main(String[] args) {
launch(args);
}
}
Here are a couple of things I would point out based upon the sample:
As the buttons are so similar, create the buttons in a loop rather than individually in code. I use an IntStream range with a map and a toArray, but you could do the same thing with a standard for loop (which may be easier to understand).
Use combinations of standard layout panes to achieve your layout. For example the buttons are vertically spaced, so put them in a VBox, the text and the buttons are horizontal to each other, so use a HBox.
Use constraints on the layouts to massage them into performing the layout you like, for example, HBox.setHgrow(buttonLayout, Priority.ALWAYS); tells the Box to always assign any extra additional space in the Box to the buttonLayout so that the buttons will fill any remaining area.
Set constraints on the individual nodes to size them how you wish, for example the following code establishes a fixed width for the textArea, which will not vary (you could similar code to establish a fixed height if you wished):
textArea.setPrefWidth(100);
textArea.setMaxWidth(TextArea.USE_PREF_SIZE);
textArea.setMinWidth(TextArea.USE_PREF_SIZE);
Some controls will automatically expand themselves beyond their max size, buttons do not by default, to enable this behavior use the following code (if you only wanted the width to expand and not the height then you would only set the maxWidth rather than the maxSize):
button.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
Rather than defining layouts in code as in this example, instead use a tool such as SceneBuilder to create the scene visually and save the layout as an FXML file, so that the layout is separated from your code (similarly place any styling in an external CSS file).

Dynamically add elements to window in JavaFX

I would like to have a window that shows an image. That is the main purpose of the window. However it should be possible to also have controls on the top. The number is not known beforehand. Could be 3 or 15. They should just pile up there for now. So the upper part grows and the image below is just being pushed down.
void createNewWindow() {
Stage stage = new Stage();
BorderPane pane = new BorderPane();
ImageView imageView = new ImageView("path");
pane.setCenter(imageView);
HBox controlBox = new HBox(10);
pane.setTop(controlBox);
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.setResizable(true);
stage.show();
}
This code barely works. I have to add the width and height manually because the scene or the stage doesn't look for anything to fit to. And when I am adding buttons later to the HBox on the top the window doesn't increase in size, neither does the HBox (height stays at 0). Only the image gets pushed down it is not fully visible anymore.
How would I go about this instead?
You should use the HBox.setHgrow method on each of the children of controlBox.
// for each button
HBox.setHgrow(child, Priority.ALWAYS);
This will align the buttons next to each other, reducing the sizes so that all of them fit in one line and they fill the available space.
JavaFX nodes are dynamically re-sizable i.e. the child will fill the space provided by parent and Parent will expand in accordance to the minimum space the child needs.
I don't face issue that have been raised by you while trying to add a HBox to a BorderPane. The BorderPane height increased (more than the image height) after the HBox got added to it. In case you want to see if the Image gets pushed down, try replacing a VBox to the HBox.
A simple example where I use an image of 365 and a HBox of 26, which result in a BorderPane of 391 height
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class BorderPaneHeight extends Application{
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane borderPane = new BorderPane();
HBox box = new HBox(10);
ImageView imageView = new ImageView(new Image("file:///home/itachi/Pictures/aaa.png")); // Replace with your image path
Button button1 = new Button("Add");
Button button2 = new Button("Add");
box.getChildren().addAll(button1, button2);
borderPane.setTop(box);
borderPane.setCenter(imageView);
Scene scene = new Scene(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
System.out.println("Image height : " + imageView.getImage().getHeight());
System.out.println("Hbox height : " + box.getHeight());
System.out.println("BorderPane Height : " + borderPane.getHeight());
}
public static void main(String[] args) {
launch(args);
}
}
Output on Console
Image height : 365.0
Hbox height : 26.0
BorderPane Height : 391.0
For the window to grow as the content inside the window expands I did like this(looked but haven't found another solution)
In this case a new window is open from MainController and this window contents can grow and I want this new window to grow with it, so in the controller for the new window I add a listener...
containerPane.heightProperty().addListener((observable, oldValue, newValue) -> {
MainController.theStage.setHeight(MainController.theStage.getHeight() + (newValue.doubleValue() - oldValue.doubleValue()));
});

JavaFX 8: Stage insets (window decoration thickness)?

How can i determine the stage/window insets in JavaFX? In Swing i could simple write:
JFrame frame = new JFrame();
Insets insets = frame.getInsets();
What would be the equivalent in JavaFX to get the size of the border and the titlebar of the window?
You can determine these by looking at the bounds of the scene relative to the width and height of the window.
Given a Scene scene;, scene.getX() and scene.getY() give the x and y coordinates of the Scene within the window. These are equivalent to the left and top insets, respectively.
The right and bottom are slightly trickier, but
scene.getWindow().getWidth()-scene.getWidth()-scene.getX()
gives the right insets, and similarly
scene.getWindow().getHeight()-scene.getHeight()-scene.getY()
gives the bottom insets.
These values will of course only make sense once the scene is placed in a window and the window is visible on the screen.
If you really want an Insets object you can do something like the following (which would even stay valid if the border or title bar changed size after the window was displayed):
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class WindowInsetsDemo extends Application {
#Override
public void start(Stage primaryStage) {
Label topLabel = new Label();
Label leftLabel = new Label();
Label rightLabel = new Label();
Label bottomLabel = new Label();
VBox root = new VBox(10, topLabel, leftLabel, bottomLabel, rightLabel);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 600, 400);
ObjectBinding<Insets> insets = Bindings.createObjectBinding(() ->
new Insets(scene.getY(),
primaryStage.getWidth()-scene.getWidth() - scene.getX(),
primaryStage.getHeight()-scene.getHeight() - scene.getY(),
scene.getX()),
scene.xProperty(),
scene.yProperty(),
scene.widthProperty(),
scene.heightProperty(),
primaryStage.widthProperty(),
primaryStage.heightProperty()
);
topLabel.textProperty().bind(Bindings.createStringBinding(() -> "Top: "+insets.get().getTop(), insets));
leftLabel.textProperty().bind(Bindings.createStringBinding(() -> "Left: "+insets.get().getLeft(), insets));
rightLabel.textProperty().bind(Bindings.createStringBinding(() -> "Right: "+insets.get().getRight(), insets));
bottomLabel.textProperty().bind(Bindings.createStringBinding(() -> "Bottom: "+insets.get().getBottom(), insets));
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Categories