1

I made a grid of buttons in JavaFX.
When I resize the window with the grid inside, the buttons resize within the grid as well.
The problem is that the text on those buttons doesn't resize along with them: it stays the same size all the time, so when the buttons grow big, there's a lot of empty space on a button and then a tiny little text in the middle, which looks terrible.
I would rather like the text to automatically resize along with these buttons to fit the empty space, so that when the entire user interface gets bigger, the text on the buttons gets bigger as well.
How can I accomplish that?

I tried setting the -fx-font-size in the CSS stylesheet to percentage values, but it doesn't seem to work the same way as for websites: the text doesn't scale as a percentage of its container, but as a percentage of some predefined text size.

Edit
This is not a duplicate! Stop marking each question out there as duplicate! If it has been answered, I wouldn't have asked it in the first place!

From what I see, the first of those threads was about a situation where someone wanted to set the size/style of the text for newly-created buttons to account for the current size of their container etc. This is not what I need, because I want the buttons which has been already created as well to automatically resize their texts when these buttons resize inside their container in some way.

The other thread was about scaling the text along with the root container / window with a preset font size. This is also different from what I need, because I don't want the text to be scaled with the window, but with the sizes of the buttons themselves. And it has to be scaled in a certain way: to always fit the size of the button. You know: the text stays the same, but stretches so that it always fits the inside of the button (with a little padding, not a huge empty area around the text).

It is the button's size which is to determine the size of the text on it, not the window or container or something else, and it needs to be done automatically by the button itself (either the built-in one or a subclassed one), not manually by its encompassing container iterating over all these buttons and updating their text's sizes (which would be dumb way to do it).

SasQ
  • 14,009
  • 7
  • 43
  • 43
  • This might be a duplicate of http://stackoverflow.com/questions/34812022/javafx-automatically-resize-buttons-font-size-to-fit-in or http://stackoverflow.com/questions/23705654/bind-font-size-in-javafx – hotzst May 04 '16 at 16:41
  • If more people were actually answering the questions instead of just hunting for duplicates or potential closes, StackOverflow would be great again, as in these olden days... ;P I read these two questions before asking my own. If I found the solution there, I wouldn't ask my question in the first place. (Unless I missed something in those threads.) – SasQ May 04 '16 at 16:47
  • What is the difference between setting the font size of a button when you create it, so that it matches the fixed button size, and updating the font size when the size of the button changes, so that it does the same thing? I'm sorry: I *really* cannot see how those are different things. (Not that that solution is particularly nice, but still... how on earth is that different except in a completely trivial way?) – James_D May 04 '16 at 19:53
  • I understand the question fine. I don't understand your rant about someone signposting an almost identical question. I answered because I thought I could improve on the answer to the duplicate. – James_D May 04 '16 at 21:17
  • The difference is that I would have to do it manually by looping through all the buttons and updating their fonts sizes everytime I detect a change (and I'd have 2 calculate a correct size 1st). This solution isn't much elegant (as well as Java, but well, I need to use it anyway so...) and doesn't scale well :P I would rather want to "teach" my buttons to update their text sizes on their own when their inner area changes size, because they are more in a position to know how to do it well than any external object. That's how a good event-driven, object-oriented programming is supposed to work. – SasQ May 04 '16 at 21:21
  • Any answer is necessarily just demo code. You don't have to loop through the buttons, though, you just register a listener when you create them. You can refactor that idea (or mine) into a `Button` subclass (or alternative skin) easily enough if you prefer other design approaches. – James_D May 04 '16 at 21:24

2 Answers2

2

This is, liked the linked questions, something of a hack: but consider scaling the text node inside the button instead of changing the font size. This seems to work ok:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;

public class ScaledButtons extends Application {

    @Override
    public void start(Stage primaryStage) {
        GridPane root = new GridPane();
        root.setHgap(5);
        root.setVgap(5);
        for (int i = 1; i <= 9 ; i++) {
            root.add(createScaledButton(Integer.toString(i)), (i-1) % 3, (i-1) / 3);
        }
        root.add(createScaledButton("#"), 0, 3);
        root.add(createScaledButton("0"), 1, 3);
        root.add(createScaledButton("*"), 2, 3);

        primaryStage.setScene(new Scene(root, 250, 400));
        primaryStage.show();
    }

    private Button createScaledButton(String text) {
        Button button = new Button(text);
        GridPane.setFillHeight(button, true);
        GridPane.setFillWidth(button, true);
        GridPane.setHgrow(button, Priority.ALWAYS);
        GridPane.setVgrow(button, Priority.ALWAYS);

        button.layoutBoundsProperty().addListener((obs, oldBounds, newBounds) -> 
            scaleButton(button));

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

        return button ;
    }

    private void scaleButton(Button button) {
        double w = button.getWidth();
        double h = button.getHeight();

        double bw = button.prefWidth(-1);
        double bh = button.prefHeight(-1);

        if (w == 0 || h == 0 || bw == 0 || bh == 0) return ;

        double hScale = w / bw ;
        double vScale = h / bw ;

        double scale = Math.min(hScale, vScale);

        button.lookup(".text").setScaleX(scale);
        button.lookup(".text").setScaleY(scale);
    }

    public static void main(String[] args) {
        launch(args);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Hmm... interesting. I didn't know that there is a text node inside the button. Is it documented somewhere? – SasQ May 04 '16 at 21:24
  • I thought it was documented in the CSS docs, though when I briefly checked I didn't see it there. – James_D May 04 '16 at 21:26
1

An alternate approach to get a similar effect could be to subclass com.sun.javafx.scene.control.skin.ButtonSkin and override the layoutLabelInArea(double x, double y, double w, double h, Pos alignment) method from the skin's parent (LabeledSkinBase). You can then explicitly assign the updated skin to your button (either via CSS or via Java API calls).

Doing so would requires the subclassing of com.sun APIs which could change without notice in subsequent JavaFX releases. Also layoutLabelInArea is reasonably complex in its operation so changing the layout logic could be a little tricky. Certainly, James's suggestion of applying a text rescaling operation based upon a listener to the layout bounds property is simpler in this particular case.

I'm not necessarily advocating this approach, just providing a route to something that you could create that would satisfy your goal of: "It is the button's size which is to determine the size of the text on it, not the window or container or something else, and it needs to be done automatically by the button itself".

jewelsea
  • 150,031
  • 14
  • 366
  • 406