1

I designed a simple virtual keyboard in JavaFX, for a Hangman-style game. The only problem is that I can't get the right alignment for the second and third row, as a real keyboard design has (for example, the A key is almost in the middle between the Q and W ones).
The keyboard is simply implemented using a GridPane filled with a button for each character.
Is there a way to insert a little blank space? I honestly thought of stacking three GridPane, one for each row, and then adding the right offsets as blank texts, but this seemed to me a little bit forced, so I wanted to know if there was a "cleaner" way of doing it.
Here's my code:

package gui;

import game.GameManager;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;

public class VirtualKeyboard extends HBox {
    private final GridPane pane;
    private final GameManager gameManager;
    private static final char[] orderedKeys = {'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P',
                                                'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L',
                                                'Z', 'X', 'C', 'V', 'B', 'N', 'M'};

    public VirtualKeyboard(GameManager gameManager) {
        this.pane = new GridPane();
        this.gameManager = gameManager;
        this.getChildren().add(pane);
        setup();
    }

    @Override
    public void setPrefSize(double v, double v1) {
        super.setPrefSize(v, v1);
        pane.setPrefSize(v, v1);
    }

    private void setup() {
        var qButton = new Button("Q");
        var wButton = new Button("W");
        var eButton = new Button("E");
        var rButton = new Button("R");
        var tButton = new Button("T");
        var yButton = new Button("Y");
        var uButton = new Button("U");
        var iButton = new Button("I");
        var oButton = new Button("O");
        var pButton = new Button("P");
        var aButton = new Button("A");
        var sButton = new Button("S");
        var dButton = new Button("D");
        var fButton = new Button("F");
        var gButton = new Button("G");
        var hButton = new Button("H");
        var jButton = new Button("J");
        var kButton = new Button("K");
        var lButton = new Button("L");
        var zButton = new Button("Z");
        var xButton = new Button("X");
        var cButton = new Button("C");
        var vButton = new Button("V");
        var bButton = new Button("B");
        var nButton = new Button("N");
        var mButton = new Button("M ");

        setRowIndexes(0, qButton, wButton, eButton, rButton, tButton, yButton, uButton, iButton, oButton, pButton);
        setRowIndexes(1, aButton, sButton, dButton, fButton, gButton, hButton, jButton, kButton, lButton);
        setRowIndexes(2, zButton, xButton, cButton, vButton, bButton, nButton, mButton);

        buttonSetup(qButton, wButton, eButton, rButton, tButton, yButton, uButton, iButton, oButton, pButton,
                aButton, sButton, dButton, fButton, gButton, hButton, jButton, kButton, lButton,
                zButton, xButton, cButton, vButton, bButton, nButton, mButton);

        pane.getChildren().addAll(
                qButton, wButton, eButton, rButton, tButton, yButton, uButton, iButton, oButton, pButton,
                aButton, sButton, dButton, fButton, gButton, hButton, jButton, kButton, lButton,
                zButton, xButton, cButton, vButton, bButton, nButton, mButton);
    }

    private void buttonSetup(Button... buttons) {
        var keyCounter = 0;
        for (Button button : buttons) {
            button.setPrefSize(60.0, 60.0);
            int finalKeyCounter = keyCounter;
            button.setOnMouseClicked(mouseEvent -> gameManager.guess(orderedKeys[finalKeyCounter]));
            button.getStyleClass().add("keyboard-button");
            button.getStylesheets().add("file:stylesheets/virtual-keyboard.css");
            keyCounter++;
            //TODO: add in stylesheet and remove
            button.setStyle("-fx-font-size: 22; -fx-font-weight: bold");

        }
    }

    private void setRowIndexes(int row, Button... buttons) {
        var column = 0;
        for (Button button : buttons) {
            GridPane.setConstraints(button, column, row);
            column++;
        }
    }

}

enter image description here

  • you could simplify this question to improve it, as well as adding what you've tried (like the 3 individual stacked gridpane being something you want to avoid) – activedecay Jul 14 '21 at 17:44
  • 1
    Sorry for not saying that I already thought that and wanted to avoid it, it completely went off my mind. I edited the question though. – Thomas Herondale Jul 14 '21 at 17:50
  • You might want to use [SceneBuilder](https://gluonhq.com/products/scene-builder/) to create your design visually, and then save the result to FXML. Even if you don't end up using FXML, it might help you see what combination of layout panes and constraints work to get you the behaviour you wish. – jewelsea Jul 14 '21 at 18:32
  • Note that JavaFX has an [in-built virtual keyboard](https://stackoverflow.com/questions/26768523/javafx-virtual-keyboard) which can be [shown and hidden on demand](https://stackoverflow.com/questions/42467648/javafx-show-virtual-keyboard) and may or may not be usable in your situation. – jewelsea Jul 14 '21 at 18:34
  • This answer also [displays a custom virtual keyboard](https://stackoverflow.com/a/23230943/1155209), the display might be OK to be adapted for your purposes or perhaps it might end up being a bit too simplistic as a starting point. – jewelsea Jul 14 '21 at 18:37
  • I didn't want to use the embedded JavaFX keyboard since it needs certain system properties to be set in a certain way, and I don't like this constraint. I could try using SceneBuilder with FXML, even if I don't know a word of syntax of the language itself haha. Thanks for the advice tho – Thomas Herondale Jul 14 '21 at 18:48
  • Don't bother with GridPane. Three HBox's placed into a VBox is sufficient. Load a row of keys into each HBox. Set the padding on the second row to "new Insets(0,0,0,20)", and the second to "new insets(0,0,0,40)". Done. – DaveB Jul 14 '21 at 19:53
  • The class `ResizableKeyboardSample.java` in the duplicate question demonstrates the solution proposed in DaveB's comment. – jewelsea Jul 14 '21 at 20:21

1 Answers1

0

I think using a single grid is incorrect, because the first element is always vertically aligned.

Maybe try using 3 stacked GridPanes and modify the size of the first element that you add to offset the starting position of the 'A' key. Is a label constructed like this invisible?

 Label label = new Label();
 GridPane.setConstraints(label, ...);

The resulting grids would look a bit like this

GRID1 =                        [Q]...
GRID2 = [invisible skinny label][A][S]...
GRID3 = [invisible less skinny   ][Z][X]...

If what you really want is to use a single grid, you may need to figure out if vertical alignment is a required property of the grid, and in that case you'd have to span several grid cells with each button placement. Spanning multiple cells is a property of which I am not sure is possible with GridPane. (but I explain the idea anyway, so maybe you can invent it :))

Here's the idea for a single grid layout where spanning-cells is possible. A button spans 4 wide, the "A-row-spacer" spans 1 wide, and the "Z-row-spacer" spans 3 wide. If the standard width is 4 wide, then the W does not appear above the Z.

|   |   |
[Q ][W ]...
_[A ][S ]...
___[Z ][X ]...
|   |   |
activedecay
  • 10,129
  • 5
  • 47
  • 71
  • To be honest I have already thought of doing that, but I hoped you guys could lead me to a simpler solution, as stacking three GridPanes seemed a little bit forced to me. I'm new to programming though, so if you say this is not a horrible solution, I'll try it for sure. – Thomas Herondale Jul 14 '21 at 17:42
  • 1
    Could inserting a cell with a blank label solve the problem? Do you know if I can modify the widht of just one cell in a row? – Thomas Herondale Jul 14 '21 at 17:53
  • 1
    Your idea could be right, but since the cells share all the same dimensions, the A key, for example, would just end aligning with the W key (which is incorrect too). In the comment above, in fact, I was asking if it's possible to resize just a cell in a row (so that resizing your "underscores" in the answer above would end putting the A in the middle between the Q and the W, and not just under the W. – Thomas Herondale Jul 14 '21 at 17:56
  • 1
    And yes, an element can span multiple cells in a ```GridPane```, but if the cells share all the same width, the problem with alignment remains the same: the A would end up being under the W. – Thomas Herondale Jul 14 '21 at 17:58
  • You could double the number of columns and have each key span two columns. Then on the alternate rows start the keys in column 1 instead of column 0. Alternatively, and probably cleaner, you could use a translate X transform on alternate rows. This would also yield a partial shift, which more closely matches a real keyboard. – DaveB Jul 14 '21 at 19:15