Bind Font Size in Javafx

Javafx: Binding font size to container

Ok your code does not compile AGAIN. So I'll just post a minimal example:

App.java

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class App extends Application {
@Override
public void start(Stage primaryStage) {
View view = new View();
Scene scene = new Scene(view, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

View.java

import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.control.Label;
import javafx.scene.layout.*;

public class View extends AnchorPane {
public View() {
GridPane gridPane = new GridPane();
ColumnConstraints column = new ColumnConstraints();
column.setPercentWidth(33.33);
column.setHalignment(HPos.CENTER);
gridPane.getColumnConstraints().addAll(column, column, column);
RowConstraints row = new RowConstraints();
row.setPercentHeight(33.33);
row.setValignment(VPos.CENTER);
gridPane.getRowConstraints().addAll(row, row, row);
gridPane.setGridLinesVisible(true);

AnchorPane.setTopAnchor(gridPane, 0.0);
AnchorPane.setBottomAnchor(gridPane, 0.0);
AnchorPane.setLeftAnchor(gridPane, 0.0);
AnchorPane.setRightAnchor(gridPane, 0.0);

for ( int i = 0 ; i < 9; i++ ) {
gridPane.add(new Label(i+1+""), i%3, i/3, 1, 1);
}

this.widthProperty().addListener( event -> {
this.setStyle("-fx-font-size: " + this.getWidth()/10);
});

this.getChildren().add(gridPane);
}
}

This is EXACTLY what i proposed on your other question...


Edit:

To change the font of the exact element you could go about it like this:

    this.widthProperty().addListener( event -> {
ObservableList<Node> labelList = gridPane.getChildren();
for ( int i = 0; i < labelList.size(); i++ ) {
//labelList.get(i).setStyle("-fx-font-size: " + this.getWidth()/10);
if ( labelList.get(i).getClass().equals(Label.class) ) {
Label.class.cast(labelList.get(i)).setFont(Font.font(this.getWidth()/10));
}
}
});

Edit2:

To have the font scale with the height AND the width you could use a method such as this:

private void changeFontSize(List<Node> labelList) {
Double newFontSizeDouble = Math.hypot(this.getWidth(), this.getHeight())/10;
int newFontSizeInt = newFontSizeDouble.intValue();

for ( int i = 0; i < labelList.size(); i++ ) {
if ( labelList.get(i).getClass().equals(Label.class) ) {
Label.class.cast(labelList.get(i)).setFont(Font.font(newFontSizeInt));
}
}
}

And call it like this:

this.widthProperty().addListener( event -> changeFontSize(gridPane.getChildren()));
this.heightProperty().addListener( event -> changeFontSize(gridPane.getChildren()));

Edit3:

You can also bind them using property bindings. There's a good tutorial on youtube here:

https://www.youtube.com/watch?v=s8GomyEOA8w&index=29&list=PL6gx4Cwl9DGBzfXLWLSYVy8EbTdpGbUIG

How to dynamically change font size in UI to always be the same width in JavaFX?

You can use temp Text object to measure text size, and scale the font if it doesn't fit. Something like this:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Main extends Application {

//maximum width of the text/label
private final double MAX_TEXT_WIDTH = 400;
//default (nonscaled) font size of the text/label
private final double defaultFontSize = 32;
private final Font defaultFont = Font.font(defaultFontSize);

@Override
public void start(Stage primaryStage) {

final TextField tf = new TextField("Label text goes here");

final Label lbl = new Label();
lbl.setFont(defaultFont);
lbl.textProperty().addListener((observable, oldValue, newValue) -> {
//create temp Text object with the same text as the label
//and measure its width using default label font size
Text tmpText = new Text(newValue);
tmpText.setFont(defaultFont);

double textWidth = tmpText.getLayoutBounds().getWidth();

//check if text width is smaller than maximum width allowed
if (textWidth <= MAX_TEXT_WIDTH) {
lbl.setFont(defaultFont);
} else {
//and if it isn't, calculate new font size,
// so that label text width matches MAX_TEXT_WIDTH
double newFontSize = defaultFontSize * MAX_TEXT_WIDTH / textWidth;
lbl.setFont(Font.font(defaultFont.getFamily(), newFontSize));
}

});
lbl.textProperty().bind(tf.textProperty());

final AnchorPane root = new AnchorPane(lbl, tf);
AnchorPane.setLeftAnchor(tf, 0d);
AnchorPane.setRightAnchor(tf, 0d);
AnchorPane.setBottomAnchor(tf, 0d);

primaryStage.setScene(new Scene(root, MAX_TEXT_WIDTH, 200));
primaryStage.show();
}
}

Note that tmpText.getLayoutBounds() returns the bounds that do not include any transformations/effects (if these are needed, you'll have to add text object to temp scene and calculate its bounds in parent).

Text fits
Text fits, again
Text scaled down

How to change font size in the stylesheet of the tables in java

This solution requires JavaFX 17 to use a data URL.

Call the provided function, passing in some parent node that contains the tables whose headers you want to size.

It will remove all existing user style sheets you have on the node, so don't call it on a node that has existing style sheets applied to it.

private void setTableHeaderFontSize(Region parent, int size) {
parent.getStylesheets().setAll(
"""
data:text/css,
.table-view .column-header {
-fx-font-size:
"""
+ size + "px;}"
);
}

Example App

The example below passes a table node to the CSS styling function, but you don't need to apply it directly on a table, you could apply it on any parent node and it would apply to any table children in the node hierarchy.

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

public class RecordTableViewer extends Application {
public record Person(String last, String first, int age) {}

private final IntegerProperty fontSize = new SimpleIntegerProperty();

@Override
public void start(Stage stage) {
TableView<Person> table = createTable();
populateTable(table);

Pane controls = createControls();

VBox layout = new VBox(
10,
controls,
table
);
layout.setPadding(new Insets(10));
layout.setPrefSize(300, 300);

setTableHeaderFontSize(table, fontSize.intValue());
fontSize.addListener((observable, oldValue, newValue) ->
setTableHeaderFontSize(table, newValue.intValue())
);

stage.setScene(new Scene(layout));
stage.show();
}

private TableView<Person> createTable() {
TableView<Person> table = new TableView<>();

TableColumn<Person, String> lastColumn = new TableColumn<>("Last");
lastColumn.setCellValueFactory(
p -> new SimpleStringProperty(p.getValue().last())
);

TableColumn<Person, String> firstColumn = new TableColumn<>("First");
firstColumn.setCellValueFactory(
p -> new SimpleStringProperty(p.getValue().first())
);

TableColumn<Person, Integer> ageColumn = new TableColumn<>("Age");
ageColumn.setCellValueFactory(
p -> new SimpleIntegerProperty(p.getValue().age()).asObject()
);
ageColumn.setPrefWidth(60);

//noinspection unchecked
table.getColumns().addAll(lastColumn, firstColumn, ageColumn);

return table;
}

private void populateTable(TableView<Person> table) {
table.getItems().setAll(
new Person("Smith", "Justin", 41),
new Person("Smith", "Sheila", 42),
new Person("Morrison", "Paul", 58),
new Person("Tyx", "Kylee", 40),
new Person("Lincoln", "Abraham", 200)
);
}

private HBox createControls() {
Slider fontSizeSlider = new Slider(8, 24, 14);
fontSizeSlider.setMajorTickUnit(2);
fontSizeSlider.setMinorTickCount(0);
fontSizeSlider.setShowTickMarks(true);

fontSize.setValue((int) Math.round(fontSizeSlider.getValue()));
fontSizeSlider.valueProperty().addListener((observable, oldValue, newValue) ->
fontSize.setValue((int) Math.round(newValue.doubleValue()))
);

Label fontSizeLabel = new Label();
fontSizeLabel.textProperty().bind(
fontSize.asString()
);

return new HBox(
10,
new Label("Table header size: "),
fontSizeSlider,
fontSizeLabel
);
}

private void setTableHeaderFontSize(Region parent, int size) {
parent.getStylesheets().setAll("""
data:text/css,
.table-view .column-header {
-fx-font-size:
"""
+ size + "px;}"
);
}
}

change fontsize in maximize mode

Clarification of maximize vs full screen

The button you point to in your question is a maximize button. Full screen mode is different from a maximized window (in most windowing systems).

Why you don't want to list to the maximize property

You could a listener to the maximize property of the window and set the font size as you wish based upon the maximize property value, similar to outlined here: Listener for a Stage minimizing, maximizing, etc. However my testing of this is not it is not a very robust solution, because (with the current JavaFX 8u60 implementation of the maximized property on Mac), the listener is fired when the user presses the maximize button (and the maximized property set to true), but if the user later resizes the window manually via the window borders, the maximized property stays true and no listener is fired to note that the window is no longer maximized (this might actually be a bug in a the Mac implementation of JavaFX 8u60). So you can capture the maximize event and resize your UI when maximized, but when no longer maximized there is no event to reset your UI to the normal (un-maximized) view.

Listen to root layout bounds instead

A more robust implementation seems to implement a kind of responsive layout algorithm listening for the current size of the layout bounds for the scene and doing a different layout (or font size) based upon changes in the layout bounds. The concepts are similar to responsive web design, though the implementation is different than on a HTML based web site, as CSS and layout in JavaFX works differently than HTML.

Here is a sample of this approach:

Smaller size window (small font)

The smaller size font is used when the window size is not very large.

smaller size (small font)

Larger size (including maximized or full screen) window (larger font)

A larger font size is used when the window size is larger.

Larger size

MaximizeFontResponder.java

import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class MaximizeFontResponder extends Application {

private static final String MAXIMIZED_ROOT_STYLE_CLASS = "maximized-root";
private static final double FONT_SIZE_WIDTH_ON_THRESHOLD = 800;
private static final double FONT_SIZE_HEIGHT_ON_THRESHOLD = 400;
private static final double FONT_SIZE_WIDTH_OFF_THRESHOLD = 780;
private static final double FONT_SIZE_HEIGHT_OFF_THRESHOLD = 380;

@Override
public void start(Stage stage) throws Exception {
StackPane layout = new StackPane(new Label("Now is the time for all good men\nto come to the aid of the party."));

Scene scene = new Scene(layout);
scene.getStylesheets().add(
MaximizeFontResponder.class.getResource(
"font-responder.css"
).toURI().toURL().toExternalForm()
);
stage.setScene(scene);

refreshRootFontSize(layout);
scene.getRoot().layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
refreshRootFontSize(layout);
});

stage.show();
}

private void refreshRootFontSize(Pane root) {
final ObservableList<String> rootStyleClass = root.getStyleClass();
final Bounds layoutBounds = root.getLayoutBounds();

if (layoutBounds.getWidth() >= FONT_SIZE_WIDTH_ON_THRESHOLD
&& layoutBounds.getHeight() >= FONT_SIZE_HEIGHT_ON_THRESHOLD
&& !rootStyleClass.contains(MAXIMIZED_ROOT_STYLE_CLASS)) {
rootStyleClass.add(MAXIMIZED_ROOT_STYLE_CLASS);
}

if (layoutBounds.getWidth() <= FONT_SIZE_WIDTH_OFF_THRESHOLD
|| layoutBounds.getHeight() <= FONT_SIZE_HEIGHT_OFF_THRESHOLD) {
rootStyleClass.remove(MAXIMIZED_ROOT_STYLE_CLASS);
}
}

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

font-responder.css

.root { -fx-font-size: 20px; }
.maximized-root { -fx-font-size: 40px; }

The technique used in this sample for sizing fonts and UI components based upon CSS defined -fx-font-size is explained in the following answers to related question:

  • javafx automatic resizing and button padding
  • Bind Font Size in JavaFX?

Note on em units and control/layout sizing

Although the example does not include any controls, if it did, those would also be sized larger to accommodate the larger font size as all JavaFX controls are sized based upon em units. If you want the rest of your UI layout to also adapt in the same way, then also size it based upon em units rather than absolute pixels. Note I've linked a HTML based resource for the em unit link (and JavaFX CSS is different from HTML CSS), but the general em concept is similar enough that you should be able to grasp it from the info in that link.

Further Resources

For a more robust solution, you might want to look into:

  • Hendrik Ebbers: Response Design for JavaFX.

How can I change the BASE font size in JavaFX 8 Modena theme that would cascade out?

If you define your sizes in terms of em, then they will be defined as proportions of the font size. Then having something like

.root {
-fx-font-size: 15pt ;
}

in an external stylesheet will change the definition of 1em, thereby changing the sizes of your padding, etc.

If you want to change the font size dynamically from Java, you can do things like

DoubleProperty fontSize = new SimpleDoubleProperty(12); // font size in pt
root.styleProperty().bind(Bindings.format("-fx-font-size: %.2fpt;", fontSize));

SSCCE:

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class FontSizeTest extends Application {

@Override
public void start(Stage primaryStage) {
Label label = new Label("Some text");
Region rect = new Region();
rect.getStyleClass().add("rect");
VBox vbox = new VBox(label, rect);
vbox.getStyleClass().add("vbox");

Slider slider = new Slider(6, 24, 12);

BorderPane root = new BorderPane(vbox, null, null, slider, null);
BorderPane.setMargin(slider, new Insets(10));
BorderPane.setAlignment(slider, Pos.CENTER);

root.styleProperty().bind(Bindings.format("-fx-font-size: %.2fpt;", slider.valueProperty()));

Scene scene = new Scene(root, 400, 400) ;
scene.getStylesheets().add("font-size-test.css");
primaryStage.setScene(scene);
primaryStage.show();
}

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

font-size-test.css:

.rect {
-fx-min-width: 1em ;
-fx-max-width: 1em ;
-fx-min-height: 1em ;
-fx-max-height: 1em ;
-fx-background-color: cornflowerblue ;
}
.vbox {
-fx-spacing: 1em ;
-fx-padding: 1.5em ;
}

Note how when you move the slider in this example, the sizes all change coordinately: the text size, the size of the rectangle, the spacing between the text and the rectangle, and the padding around the vbox holding the label and the rectangle.

Sample Image

Sample Image



Related Topics



Leave a reply



Submit