Javafx Automatic Resizing and Button Padding

javafx automatic resizing and button padding

Sizing based on font size

For your particular case, rather than trying to resize buttons using padding or additional layout constraints, try adjusting the font size (-fx-font-size) used for the parent layout container for your virtual keyboard. If you make the font size larger, the buttons will automatically change their preferred size to match this larger size, plus all of the text will be automatically rendered and fit within the preferred size and be displayed in the suggested padding layout for that font size (which is probably what you want).

Button Resizing Rules

Basically the rules to get a button resizable are:

  1. Remove the maxSize constraint from the button, button.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE). This is required because the default constraint for a button is that its maximum size is its preferred size, so it doesn't automatically grow to fill available space.
  2. Get the preferred size of the button to be size you want. As you are doing an onscreen keyboard, one way to achieve this is to increase or decrease the font size, then the button will size itself automatically.
  3. If you want additional padding in a button, you can use -fx-padding in CSS or button.setPadding(new Insets(...)) in code.
  4. Put the button in a resizable parent.
  5. Make sure the resizable parent is actually resized (something like a StackPane will be automatically resized to fill available area, I don't use AnchorPane very much so I'm unfamiliar with its resizing behaviour).

To better understand layout management in JavaFX, I recommend viewing a past JavaOne Interface Layout with JavaFX 2.0 presentation.

Resizable Button Grid Sample

If you wish to continue trying to create your own implementation, this sample color chooser might be of assistance. The color choose implementation is based on a resizable grid of resizable buttons, so as your change the area available for color chooser grid, both the grid and the buttons in the grid expand or contract. The code for the color chooser is not FXML based, nor does it directly implement a keyboard which is what you want, but it does demonstrate automated sizing of buttons as you asking about in your question.

chooserlarge choosersmall

Consider using the JavaFX virtual keyboard

JavaFX already has a built-in virtual keyboard. The built-in keyboard doesn't have an officially supported and documented public API and is not guaranteed to be maintained between Java versions. However, using the built-in virtual keyboard might still be a better approach than trying to create your own. Creating a quality, general purpose virtual keyboard is a pretty hard task (IMO).

embedded keyboard

There is some discussion on this topic on the Oracle JavaFX forums.

JavaFX is open source, so even if you don't use the built-in virtual keyboard directly, you can review the JavaFX source to see how it is implemented if you wish.

Sample Virtual Keyboard Code

Sample code demonstrating the use of the built-in JavaFX virtual keyboard on a "desktop" environment.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class EmbeddedSample extends Application {
@Override public void start(Stage stage) {
stage.setScene(new Scene(new StackPane(new TextField("xyzzy")), 200, 100));
stage.getScene().setOnMouseClicked(e -> stage.hide());
stage.show();
}

public static void main(String[] args) { launch(args); }
}

Run the sample code like this:

java -Dcom.sun.javafx.virtualKeyboard=javafx -Dcom.sun.javafx.touch=true EmbeddedSample

Sample Virtual Keyboard Code for Resizing Based on Font Size

keyslarger
keyssmall

keyboard.css

.key {
-fx-base: antiquewhite;
}

.key-row {
-fx-spacing: 0.333333em;
}

.keyboard {
-fx-spacing: 0.333333em;
-fx-padding: 0.333333em;
-fx-font-family: monospace;
}

ResizableKeyboardSample.java

import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.*;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class ResizableKeyboardSample extends Application {
public static void main(String[] args) throws Exception {
launch(args);
}

String[] chars = {
"qwertyuiop",
"asdfghjkl",
"zxcvbnm"
};

public void start(final Stage stage) throws Exception {
Keyboard keyboard = new Keyboard();

VBox layout = new VBox(20);
layout.setPadding(new Insets(10));
layout.getChildren().setAll(
createControls(keyboard),
keyboard
);

Scene scene = new Scene(layout, 1000, 400);
scene.getStylesheets().add(
getClass().getResource(
"keyboard.css"
).toExternalForm()
);

stage.setScene(scene);
stage.show();
}

private Node createControls(Keyboard keyboard) {
Slider fontSize = new Slider(8, 40, Font.getDefault().getSize());
keyboard.fontSizeProperty().bind(fontSize.valueProperty());
fontSize.setShowTickLabels(true);
fontSize.setShowTickMarks(true);
fontSize.setMajorTickUnit(2);
fontSize.setMinorTickCount(0);

Label typedData = new Label();
keyboard.lastKeyTextProperty().addListener((observable, oldText, newText) ->
typedData.setText(typedData.getText() + newText)
);

VBox layout = new VBox(10);
layout.getChildren().setAll(
new Label("Keyboard Size"),
fontSize,
typedData
);
layout.setMinSize(VBox.USE_PREF_SIZE, VBox.USE_PREF_SIZE);

return layout;
}

class Keyboard extends VBox {
private DoubleProperty fontSize = new SimpleDoubleProperty(Font.getDefault().getSize());

public double getFontSize() {
return fontSize.get();
}

public DoubleProperty fontSizeProperty() {
return fontSize;
}

public void setFontSize(double fontSize) {
this.fontSize.set(fontSize);
}

private ReadOnlyStringWrapper lastKeyText = new ReadOnlyStringWrapper();

public String getLastKeyText() {
return lastKeyText.get();
}

public ReadOnlyStringProperty lastKeyTextProperty() {
return lastKeyText.getReadOnlyProperty();
}

public Keyboard() {
setAlignment(Pos.BOTTOM_CENTER);
setMinSize(VBox.USE_PREF_SIZE, VBox.USE_PREF_SIZE);
getStyleClass().add("keyboard");

onFontSizeChange(fontSize.getValue());
fontSize.addListener((observable, oldValue, newValue) ->
onFontSizeChange(newValue)
);

for (String row: chars) {
HBox keyRow = new HBox();
keyRow.getStyleClass().add("key-row");

keyRow.setAlignment(Pos.CENTER);
for (char c: row.toCharArray()) {
KeyButton key = new KeyButton(Character.toString(c));
keyRow.getChildren().add(key);
}
getChildren().add(keyRow);
}
}

private void onFontSizeChange(Number newValue) {
setStyle("-fx-font-size: " + newValue + "px;");
}

class KeyButton extends Button {
public KeyButton(String text) {
super(text);
getStyleClass().add("key");

setMinSize(Button.USE_PREF_SIZE, Button.USE_PREF_SIZE);
setMaxSize(Button.USE_PREF_SIZE, Button.USE_PREF_SIZE);

setOnAction(event -> lastKeyText.set(text));
}
}
}

}

Resizable Buttons in 4*6 GridPane

I altered the second example examined here to make the button grow; resize the stage to see the effect. Noting the GridPane optional layout constraints, I allowed each Button to always grow arbitrarily in the center of its grid cell.

button.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);

GridPane.setHalignment(gb, HPos.CENTER);
GridPane.setHgrow(gb, Priority.ALWAYS);
GridPane.setValignment(gb, VPos.CENTER);
GridPane.setVgrow(gb, Priority.ALWAYS);

image

import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;

/** @see https://stackoverflow.com/a/69429741/230513 */
public class GridButtonTest extends Application {

private static final int N = 5;
private final List<List<Button>> list = new ArrayList<>();

private Button getGridButton(int r, int c) {
return list.get(r).get(c);
}

private Button createGridButton(int row, int col) {
Button button = new Button("r" + row + ",c" + col);
button.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
button.setOnAction((ActionEvent event) -> {
System.out.println(event.getSource() == getGridButton(row, col));
});
return button;
}

@Override
public void start(Stage stage) {
stage.setTitle("GridButtonTest");
GridPane root = new GridPane();
for (int row = 0; row < N - 1; row++) {
list.add(new ArrayList<>());
for (int col = 0; col < N + 1; col++) {
Button gb = createGridButton(row, col);
list.get(row).add(gb);
root.add(gb, row, col);
//GridPane.setMargin(gb, new Insets(N));
GridPane.setHalignment(gb, HPos.CENTER);
GridPane.setHgrow(gb, Priority.ALWAYS);
GridPane.setValignment(gb, VPos.CENTER);
GridPane.setVgrow(gb, Priority.ALWAYS);
}
}
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}

public static void main(String[] args) {
launch(args);
}
}

JavaFx, FXML, how to auto resize content: Button, StackPane, Field, Scrollpane according to their parents

I'm not sure if you're intentionally wrapping your content into multiple panes, but it is unnecessary. Panes will not autoresize its content and AnchorPanes will only resize content if you call AnchorPane.setTopAnchor(Node), AnchorPane.setRightAnchor(Node), AnchorPane.setBottomAnchor(Node), or AnchorPane.setLeftAnchor(Node).

First, the GridPane. You can set the VGrow and HGrow properties of the RowConstraints and ColumnConstraints, respectively, to resize the content in relation to the GridPane's size. Setting the percentHeight and percentWidth of the RowConstraints and ColumnConstraints will also aid in keeping the rows and columns at their preferred proportions.

On the ScrollPane, set the fitToWidth and fitToHeight properties to keep its children resized to match the size of the ScrollPane.

For your input area, an HBox would be much better suited than a Pane so that you can more easily resize the textfields and buttons. Setting each Label's graphic to its corresponding textfield will also help keep the two together.

Here is your fxml code cleaned up to give you a better idea.

Sample Image

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="ClientItem_gridPane" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.65"
xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" percentHeight="73.0" prefHeight="435.0"
vgrow="ALWAYS" />
<RowConstraints fillHeight="false" maxHeight="1.7976931348623157E308" minHeight="10.0" percentHeight="10.0"
prefHeight="65.0" />
<RowConstraints fillHeight="false" maxHeight="1.7976931348623157E308" minHeight="10.0" percentHeight="17.0"
prefHeight="100.0" />
</rowConstraints>
<children>
<ScrollPane fx:id="ClientItem_scrollPane" fitToHeight="true" fitToWidth="true" prefHeight="417.0"
prefWidth="800.0" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.valignment="CENTER"
GridPane.vgrow="ALWAYS">
<content>
<TableView fx:id="ClientItem_tableView" prefHeight="409.0" prefWidth="778.0">
<columns>
<TableColumn fx:id="ClientItem_tableCol_ClientNo" prefWidth="103.0" text="Client No." />
<TableColumn fx:id="ClientItem_tableCol_Name" prefWidth="151.0" text="Name" />
<TableColumn fx:id="ClientItem_tableCol_Address" minWidth="0.0" prefWidth="220.0"
text="Address" />
<TableColumn fx:id="ClientItem_tableCol_Phone" minWidth="0.0" prefWidth="108.0" text="Phone" />
<TableColumn fx:id="ClientItem_tableCol_Remaing" minWidth="0.0" prefWidth="95.0"
text="Remaining" />
<TableColumn fx:id="ClientItem_tableCol_Type" prefWidth="102.0" text="Type" />
</columns>
</TableView>
</content>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</ScrollPane>
<HBox alignment="CENTER" prefHeight="77.0" prefWidth="800.0" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1"
GridPane.vgrow="ALWAYS">
<children>
<Label alignment="CENTER" contentDisplay="BOTTOM" text="Client No.">
<graphic>
<TextField fx:id="ClientItem_textField_ClientNo" prefHeight="25.0" prefWidth="69.0" />
</graphic>
</Label>
<Label alignment="CENTER" contentDisplay="BOTTOM" text="Name">
<graphic>
<TextField fx:id="ClientItem_textField_Name" prefHeight="25.0" prefWidth="188.0" />
</graphic>
</Label>
<Label alignment="CENTER" contentDisplay="BOTTOM" text="Address">
<graphic>
<TextField fx:id="ClientItem_textField_Address" prefHeight="25.0" prefWidth="220.0" />
</graphic>
</Label>
<Label alignment="CENTER" contentDisplay="BOTTOM" text="Phone">
<graphic>
<TextField fx:id="ClientItem_textField_Phone" prefHeight="25.0" prefWidth="108.0" />
</graphic>
</Label>
<Label alignment="CENTER" contentDisplay="BOTTOM" text="Remaining">
<graphic>
<TextField fx:id="ClientItem_textField_Remaining" prefHeight="25.0" prefWidth="95.0" />
</graphic>
</Label>
<Label alignment="CENTER" contentDisplay="BOTTOM" text="Type">
<graphic>
<TextField fx:id="ClientItem_textField_Type" prefHeight="25.0" prefWidth="87.0" />
</graphic>
</Label>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</HBox>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="20.0" GridPane.hgrow="ALWAYS"
GridPane.rowIndex="2" GridPane.vgrow="ALWAYS">
<children>
<Button fx:id="ClientItem_btn_Add" mnemonicParsing="false" prefHeight="50.0" prefWidth="100.0"
text="Add" />
<Button fx:id="ClientItem_btn_Delete" mnemonicParsing="false" prefHeight="50.0" prefWidth="100.0"
text="Delete" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</HBox>
</children>
</GridPane>

How to automatically resize buttons on BorderPane as the window is resized?

To align the buttons at the center of the bottom section of the BorderPane, an easy and convenient way to do it is to use a HBox as the parent containers of the two buttons.

HBox box = new HBox(10, button1, button2); // 10 is spacing
box.setAlignment(Pos.CENTER);
borderPane.setBottom(box);

Since, you want the buttons to expand when you expand the screen, you can make the HGROW for these buttons to Priority.ALWAYS.

HBox.setHgrow(button1, Priority.ALWAYS);
HBox.setHgrow(button2, Priority.ALWAYS);

You would also have to remove the maxSize constraint from the buttons by calling :

button1.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
button2.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);

There is one small issue with this approach. The buttons will capture the whole available area and we do not want that. An easy way to get rid of it is to add two fixed length, transparent rectangles at the beginning and end of the HBox.


MCVE

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class Main extends Application {

@Override
public void start(Stage primaryStage) {

Button button1 = new Button("Button 1");
Button button2 = new Button("Button 2");

button1.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
button2.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);

Rectangle rect1 = new Rectangle(60, 20);
rect1.setFill(Color.TRANSPARENT);
Rectangle rect2 = new Rectangle(60, 20);
rect2.setFill(Color.TRANSPARENT);

HBox box = new HBox(10, rect1, button1, button2, rect2);
box.setAlignment(Pos.CENTER);
HBox.setHgrow(button1, Priority.ALWAYS);
HBox.setHgrow(button2, Priority.ALWAYS);

BorderPane root = new BorderPane();
root.setBottom(box);

Scene scene = new Scene(root, 300, 250);

primaryStage.setTitle("Main Stage");
primaryStage.setScene(scene);
primaryStage.show();
}

public static void main(String[] args) {
launch(args);
}
}

JavaFX - button size in percents

Use It:

    button.prefHeightProperty().bind(Bindings.divide(primaryStage.widthProperty(), 10.0));
button.prefWidthProperty().bind(Bindings.divide(primaryStage.widthProperty(), 10.0));

Resize a Button JavaFX

I added your Code, it works very fine. I made it a little bit different:

        btnTriangle.setStyle("-fx-background-color:transparent;-fx-padding:0;-fx-background-size:0;");

JavaFX Scene Builder Button Bar Spacing

If you have a reasons to save your Button Bar and you don't want to use other layouts directly (Toolbar or HBox) ,you can add a Hbox to your Button Bar and add your buttons into your hbox like this :

<ButtonBar buttonOrder="L_HE+U+FBIX_NCYOA_T" layoutX="130.0" layoutY="24.0" prefHeight="0.0" stylesheets="@fx.css" ButtonBar.buttonData="APPLY">
<buttons>
<HBox>
<children>
<Button mnemonicParsing="false" text="Button" HBox.hgrow="ALWAYS" />
<Button mnemonicParsing="false" text="Button" HBox.hgrow="ALWAYS" />
<Button mnemonicParsing="false" text="Button" />
<Button mnemonicParsing="false" text="Button" />
<Button mnemonicParsing="false" text="Button" />
</children>
</HBox>
</buttons>
</ButtonBar>

And the result is Sample Image

How to resize a button depending on the text in JavaFX?

I don't know why, so I hope somebody of you does....

Solution:

Just replace the AnchorPane with a BorderPane like this
public class ButtonTest extends Application
{

public static void main(String[] args)
{
Application.launch(args);
}

@Override
public void start(Stage primaryStage) throws Exception
{
Scene scene = new Scene(getBottomPanel(), 600, 50);
primaryStage.setScene(scene);
primaryStage.show();
}

private BorderPane getBottomPanel()
{
HBox infraBox = new HBox(5);
infraBox.setAlignment(Pos.CENTER);
infraBox.getChildren().add(new Label("Input (manuell):"));
infraBox.getChildren().add(new TextField());

Button shortButton = new Button("OK");
Button longButton = new Button("\u00dcberspringen");
ButtonBar buttonBar = new ButtonBar();
buttonBar.getButtons().addAll(shortButton, longButton);
/*using BorderPane instead of AnchorPane*/
BorderPane bottomPanel = new BorderPane();
bottomPanel.setPadding(new Insets(5));
bottomPanel.setLeft(infraBox);
bottomPanel.setCenter(buttonBar);
return bottomPanel;
}
}

Automatically resizing the window based on dynamic content

I would say this is not a graceful solution (it's more like a hack), but at least it has worked using your example:

VBox root = new VBox() {
private boolean needsResize = false;

@Override
protected void layoutChildren()
{
super.layoutChildren();

if (!needsResize) {
needsResize = true;

Platform.runLater(() -> {
if (needsResize) {
window.sizeToScene();
needsResize = false;
}
});
}
}
};

Of course, you should add in the sizeToScene.isSelected() part, and you could also make this an actual subclass.



Related Topics



Leave a reply



Submit