javafx automatic resizing and button padding


Question

I'm trying to make an on screen keyboard with Javafx for the layout. I'm using the Scene Builder to make the FXML file.

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

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.text.*?>

<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="186.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2">
  <children>
    <VBox layoutX="0.0" layoutY="0.0" prefHeight="186.0" prefWidth="600.0" rotate="0.0" spacing="2.0">
      <children>
        <HBox minHeight="33.0" prefHeight="33.0" prefWidth="600.0" spacing="2.0">
          <children>
            <Label maxWidth="-Infinity" prefHeight="33.0" prefWidth="35.0" text="Milk" textAlignment="CENTER" />
            <Label maxWidth="-Infinity" prefHeight="33.0" text="Mister" />
            <Label maxWidth="-Infinity" prefHeight="35.0" text="Minimum" />
          </children>
          <padding>
            <Insets left="5.0" />
          </padding>
        </HBox>
        <HBox prefHeight="29.0" prefWidth="600.0">
          <children>
            <Button minWidth="29.0" mnemonicParsing="false" prefHeight="27.0" prefWidth="29.0" text="Esc" textAlignment="CENTER" underline="false">
              <font>
                <Font size="9.0" />
              </font>
            </Button>
            <Button mnemonicParsing="false" text="`" textAlignment="CENTER" />
            <Button mnemonicParsing="false" text="1" />
            <Button mnemonicParsing="false" text="2" />
            <Button mnemonicParsing="false" text="3" />
            <Button mnemonicParsing="false" text="4" />
            <Button mnemonicParsing="false" text="5" />
            <Button mnemonicParsing="false" text="6" />
            <Button mnemonicParsing="false" text="7" />
            <Button mnemonicParsing="false" text="9" />
            <Button mnemonicParsing="false" text="0" />
            <Button mnemonicParsing="false" text="-" />
            <Button mnemonicParsing="false" text="=" />
            <Button mnemonicParsing="false" text="Backspace" />
          </children>
        </HBox>
        <HBox prefHeight="57.0" prefWidth="600.0">
          <children>
            <VBox minHeight="50.0" prefHeight="78.0" prefWidth="505.0" HBox.hgrow="ALWAYS">
              <children>
                <HBox prefHeight="29.0" prefWidth="-1.0">
                  <children>
                    <Button mnemonicParsing="false" prefHeight="27.0" text="Tab" />
                    <Button mnemonicParsing="false" text="q" />
                    <Button mnemonicParsing="false" text="w" />
                    <Button mnemonicParsing="false" text="e" />
                    <Button mnemonicParsing="false" text="r" />
                    <Button mnemonicParsing="false" text="t" />
                    <Button mnemonicParsing="false" text="y" />
                    <Button mnemonicParsing="false" text="u" />
                    <Button mnemonicParsing="false" text="i" />
                    <Button mnemonicParsing="false" text="o" />
                    <Button mnemonicParsing="false" text="p" />
                    <Button mnemonicParsing="false" text="[" />
                    <Button mnemonicParsing="false" text="]" />
                  </children>
                </HBox>
                <HBox minHeight="-1.0" prefHeight="28.0" prefWidth="-1.0">
                  <children>
                    <Button mnemonicParsing="false" prefWidth="71.0" text="Caps" />
                    <Button mnemonicParsing="false" text="a" />
                    <Button mnemonicParsing="false" text="s" />
                    <Button mnemonicParsing="false" text="d" />
                    <Button mnemonicParsing="false" text="f" />
                    <Button mnemonicParsing="false" text="g" />
                    <Button mnemonicParsing="false" text="h" />
                    <Button mnemonicParsing="false" text="j" />
                    <Button mnemonicParsing="false" text="k" />
                    <Button mnemonicParsing="false" text="l" />
                    <Button mnemonicParsing="false" text=";" />
                    <Button mnemonicParsing="false" text="'" />
                    <Button mnemonicParsing="false" text="\\" />
                  </children>
                </HBox>
              </children>
            </VBox>
            <Button mnemonicParsing="false" prefHeight="57.0" prefWidth="132.0" text="Enter" />
          </children>
        </HBox>
        <HBox prefHeight="27.000099999997474" prefWidth="600.0">
          <children>
            <Button mnemonicParsing="false" prefWidth="85.0" text="Shift" />
            <Button mnemonicParsing="false" text="z" />
            <Button mnemonicParsing="false" text="x" />
            <Button mnemonicParsing="false" text="c" />
            <Button mnemonicParsing="false" text="v" />
            <Button mnemonicParsing="false" text="b" />
            <Button mnemonicParsing="false" text="n" />
            <Button mnemonicParsing="false" text="m" />
            <Button mnemonicParsing="false" text="," />
            <Button mnemonicParsing="false" text="." />
            <Button mnemonicParsing="false" text="/" />
            <Button mnemonicParsing="false" text="^" />
            <Button mnemonicParsing="false" prefWidth="56.0" text="Shift" />
            <Button mnemonicParsing="false" text="Del" />
          </children>
        </HBox>
        <HBox prefHeight="29.0" prefWidth="600.0" VBox.vgrow="ALWAYS">
          <children>
            <Button mnemonicParsing="false" text="Fn" />
            <Button mnemonicParsing="false" text="Ctrl">
              <HBox.margin>
                <Insets />
              </HBox.margin>
            </Button>
            <Button mnemonicParsing="false" text="Win" />
            <Button mnemonicParsing="false" text="Alt" />
            <Button mnemonicParsing="false" prefWidth="105.0" text="Space" HBox.hgrow="ALWAYS" />
            <Button mnemonicParsing="false" text="Alt" />
            <Button mnemonicParsing="false" prefWidth="-1.0" text="Ctrl" textAlignment="LEFT" />
            <Button mnemonicParsing="false" text="&lt;" />
            <Button mnemonicParsing="false" text="v" />
            <Button mnemonicParsing="false" text="&gt;" />
            <Button mnemonicParsing="false" text="Menu" />
          </children>
        </HBox>
      </children>
    </VBox>
  </children>
</AnchorPane>

Which looks like this:

enter image description here

But when the window is resized, the contents don't. I want the buttons to gain/lose size until it fits the window. Turning on Hgrow and Vgrow didn't work.

And I can't find how to set the padding on buttons. I want to make buttons like Ctrl smaller without losing the text.

1
5
1/15/2016 2:00:18 PM

Accepted Answer

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

}
24
4/23/2014 12:22:06 AM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon