sirgeezer21
sirgeezer21

Reputation: 123

Javafx: Binding font size to container

I am currently working on my final year project which is to create a Sudoku game/solver. The problem I have is that I am cannot find a method of resizing font size to that of its container when using a scene framework.

I would like to have the font size of a label change to the corresponding size of the gridpane it is within. I however am finding it hard to bind the properties I need such as 'widthProperty' and 'heightProperty' to that of 'Font.font(size)'

The difference of this question to other binding questions, is that my scene framework interferes with putting the binding for controls in the initialize method of a controller. I cannot do this as all of my controllers are initialized on start up not when they are shown to the scene

Here is all my code:

Main.java

package Controller;

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

public class Main extends Application {

    public static String mainMenu = "Main Menu";
    public static String mainMenuFXML = "/FXML/MainMenu.fxml";
    public static String chooseLevel = "Level Selection";
    public static String chooseLevelFXML = "/FXML/ChooseLevel.fxml";

    //Global Variable to be accessed across all classes
    //To be used to determine which scenes are loaded in the HashMap and also which scene has been set
    public static SceneFramework mainController = new SceneFramework();

    @Override
    public void start(Stage primaryStage) throws Exception{

        //Only the main menu scene is needed to be loaded at the start
        mainController.loadScene(Main.mainMenu, Main.mainMenuFXML);
        mainController.setScene(Main.mainMenu);

        StackPane root = new StackPane();
        root.getChildren().addAll(mainController);
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {

        launch(args);
    }
}

SceneFramework.java

package Controller;


import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.layout.StackPane;

import java.util.HashMap;

public class SceneFramework extends StackPane{

    //Constructor for the class
    public SceneFramework(){
        //The keyword super, overrides methods from the superclass
        super();
    }

    //This HashMap is used to hold the screens(scenes) to be used within the application
    protected HashMap<String, Node> scenes = new HashMap<>();

    //Adds the selected scene to the HashMap
    public void addScene(String title, Node scene) {
        scenes.put(title, scene);
    }

    //Returns the Node with the specified title for use within application
    public Node getScene(String title) {
        return scenes.get(title);
    }

    //Removes the selected scene from the framework
    public boolean unloadScene(String title){
        if(scenes.remove(title) == null){
            System.out.println("Scene cannot be located or it doesn't exist");
            return false;
        } else{
            return true;
        }
    }

    public boolean loadScene(String title, String resource){
        //encase code segment in try for exception handling,
        // as resources are required to be found and then loaded into memory
        try{
            //Load FXML File
            FXMLLoader loadedFXMLFile = new FXMLLoader(getClass().getResource(resource));

            //Get Parent of scene
            Parent loadScene = loadedFXMLFile.load();

            //Get the Controller class of the parent
            SetSceneParent sceneController = (loadedFXMLFile.getController());

            //Method of making sure every scene knows who it's parent is
            sceneController.setSceneParent(this);

            //Add scene to HashMap
            addScene(title, loadScene);
            return true;

        }catch (Exception e){
            //If FXML resource can't be loaded then generate this
            System.out.println("Could't load FXML file");
            return false;
        }
    }

    //Method for showing scenes
    //If one scenes if wanting to be shown then it is added to the root
    //If multiple scenes are wanting to be loaded then the first scene is removed and the new scene is then displayed
    public boolean setScene(final String title){
        //Check to see if scene can be found
        if(scenes.get(title) != null) {
            if(!getChildren().isEmpty()){

                getChildren().remove(0);
                getChildren().add(0 ,scenes.get(title));

            }else{
                    getChildren().add(scenes.get(title));
            }
            return true;
        } else{
            System.out.println("Scene not located!!!");
            return false;
        }
    }
}

SetSceneParent.java

package Controller;

import Controller.SceneFramework;

public interface SetSceneParent {

   void setSceneParent(SceneFramework sceneParent);
}

MainMenuController.java

package Controller;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;

import java.net.URL;
import java.util.ResourceBundle;


public class MainMenuController implements Initializable, SetSceneParent {

    @FXML
    public Button playButton, optionsButton, quitButton;


    @Override
    public void initialize(URL location, ResourceBundle resources) {
        SelectionStatus.initialize();
    }

    @Override
    public void setSceneParent(SceneFramework sceneParent) {
        //Sets the parent of the scene by using the global variable of the class SceneFramework
        Main.mainController = sceneParent;
    }

    public void handlePlayButtonAction(){
        //On clicking of the play button
        //The user is taken to the level difficulty scene

        Main.mainController.loadScene(Main.chooseLevel, Main.chooseLevelFXML);
        Main.mainController.setScene(Main.chooseLevel);
        Main.mainController.unloadScene(Main.mainMenu);
    }

}

MainMenu.fxml

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

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

<BorderPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="480.0" prefWidth="640.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller.MainMenuController">
   <center>
      <HBox>
         <children>
            <VBox alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" BorderPane.alignment="TOP_CENTER" HBox.hgrow="ALWAYS">
                <children>
                  <Label alignment="TOP_CENTER" maxHeight="120.0" prefHeight="60.0" text="Sudoku Solver" textAlignment="CENTER" underline="true" wrapText="true" VBox.vgrow="ALWAYS">
                     <font>
                        <Font name="System Bold" size="48.0" />
                     </font>
                     <VBox.margin>
                        <Insets right="10.0" />
                     </VBox.margin>
                  </Label>
                    <Button fx:id="playButton" maxHeight="175.0" maxWidth="1400.0" mnemonicParsing="false" onAction="#handlePlayButtonAction" prefHeight="60.0" prefWidth="300.0" text="Play" VBox.vgrow="ALWAYS">
                        <VBox.margin>
                            <Insets />
                        </VBox.margin>
                     <font>
                        <Font name="System Bold" size="18.0" />
                     </font>
                    </Button>
                  <Region maxHeight="80.0" prefHeight="0.0" VBox.vgrow="ALWAYS" />
                    <Button fx:id="optionsButton" maxHeight="175.0" maxWidth="1400.0" mnemonicParsing="false" prefHeight="60.0" prefWidth="300.0" text="Options" VBox.vgrow="ALWAYS">
                        <VBox.margin>
                            <Insets />
                        </VBox.margin>
                     <font>
                        <Font name="System Bold" size="18.0" />
                     </font>
                    </Button>
                  <Region maxHeight="80.0" prefHeight="0.0" VBox.vgrow="ALWAYS" />
                    <Button fx:id="quitButton" maxHeight="175.0" maxWidth="1400.0" mnemonicParsing="false" prefHeight="60.0" prefWidth="300.0" text="Quit" VBox.vgrow="ALWAYS">
                        <VBox.margin>
                            <Insets />
                        </VBox.margin>
                     <font>
                        <Font name="System Bold" size="18.0" />
                     </font>
                    </Button>
                </children>
            </VBox>
         </children>
      </HBox>
   </center>
   <left>
      <HBox BorderPane.alignment="CENTER">
         <children>
            <Region maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefWidth="100.0" HBox.hgrow="ALWAYS" />
         </children>
      </HBox>
   </left>
   <right>
      <HBox BorderPane.alignment="CENTER">
         <children>
            <Region maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefWidth="100.0" HBox.hgrow="ALWAYS" />
         </children>
      </HBox>
   </right>
   <bottom>
      <VBox BorderPane.alignment="CENTER">
         <children>
            <Region prefHeight="60.0" VBox.vgrow="ALWAYS" />
         </children>
      </VBox>
   </bottom>
</BorderPane>

ChooseLevelController.java

package Controller;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;

import java.net.URL;
import java.util.ResourceBundle;

public class ChooseLevelController implements Initializable, SetSceneParent {

    @FXML
    public Label lblLevelSelection, lblLevel1, lblLevel2, lblLevel3, lblLevel4, lblLevel5, lblLevel6, lblLevel7, lblLevel8, lblLevel9, lblLevel10; // Value injected by FXMLLoader
    public Button backButton;
    public GridPane gridPaneCenter;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        //Assert code is to be used in debugging making sure references to files and their elements are correct
        assert lblLevel1 != null : "fx:id=\"lblLevel1\" was not injected: check your FXML file 'ChooseLevel.fxml'.";
        assert lblLevel2 != null : "fx:id=\"lblLevel2\" was not injected: check your FXML file 'ChooseLevel.fxml'.";
        assert lblLevel3 != null : "fx:id=\"lblLevel3\" was not injected: check your FXML file 'ChooseLevel.fxml'.";
        assert lblLevel4!= null : "fx:id=\"lblLevel4\" was not injected: check your FXML file 'ChooseLevel.fxml'.";
        assert lblLevel5 != null : "fx:id=\"lblLevel5\" was not injected: check your FXML file 'ChooseLevel.fxml'.";
        assert lblLevel6 != null : "fx:id=\"lblLevel6\" was not injected: check your FXML file 'ChooseLevel.fxml'.";
        assert lblLevel7 != null : "fx:id=\"lblLevel7\" was not injected: check your FXML file 'ChooseLevel.fxml'.";
        assert lblLevel8 != null : "fx:id=\"lblLevel8\" was not injected: check your FXML file 'ChooseLevel.fxml'.";
        assert lblLevel9 != null : "fx:id=\"lblLevel9\" was not injected: check your FXML file 'ChooseLevel.fxml'.";
        assert lblLevel10 != null : "fx:id=\"lblLevel10\" was not injected: check your FXML file 'ChooseLevel.fxml'.";

    }

    @Override
    public void setSceneParent(SceneFramework screenParent) {
        //Sets the parent of the scene by using the global variable of the class SceneFramework
        Main.mainController = screenParent;
    }

    //Logic code
    @FXML
    public void mouseClickedLevelLabel() {

        //Load the play screen when any Label is pressed
        lblLevel1.setOnMouseClicked(e -> handleLabelClick(1));
        lblLevel2.setOnMouseClicked(e -> handleLabelClick(2));
        lblLevel3.setOnMouseClicked(e -> handleLabelClick(3));
        lblLevel4.setOnMouseClicked(e -> handleLabelClick(4));
        lblLevel5.setOnMouseClicked(e -> handleLabelClick(5));
        lblLevel6.setOnMouseClicked(e -> handleLabelClick(6));
        lblLevel7.setOnMouseClicked(e -> handleLabelClick(7));
        lblLevel8.setOnMouseClicked(e -> handleLabelClick(8));
        lblLevel9.setOnMouseClicked(e -> handleLabelClick(9));
        lblLevel10.setOnMouseClicked(e -> handleLabelClick(10));
    }

    @FXML
    public void handleBackButtonAction() {
        //On clicking of the back button
        //The user is taken to the level difficulty scene

        Main.mainController.loadScene(Main.mainMenu, Main.mainMenuFXML);
        Main.mainController.setScene(Main.mainMenu);
        Main.mainController.unloadScene(Main.chooseLevel);

    }
}

ChooseLevel.fxml

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

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

<BorderPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller.ChooseLevelController">
   <center>
      <VBox alignment="CENTER" BorderPane.alignment="CENTER">
         <children>
            <GridPane fx:id="gridPaneCenter" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1500.0" prefHeight="242.0" prefWidth="475.0" BorderPane.alignment="CENTER" VBox.vgrow="ALWAYS">
              <columnConstraints>
                <ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
                <ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
                  <ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
                  <ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
                  <ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
              </columnConstraints>
              <rowConstraints>
                <RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" prefHeight="137.0" vgrow="ALWAYS" />
                <RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" prefHeight="138.0" vgrow="ALWAYS" />
              </rowConstraints>
               <children>
                  <Label fx:id="lblLevel1" onMousePressed="#mouseClickedLevelLabel" text="1" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS">
                     <font>
                        <Font name="System Bold" size="36.0" />
                     </font>
                  </Label>
                  <Label fx:id="lblLevel2" onMouseClicked="#mouseClickedLevelLabel" onMousePressed="#mouseClickedLevelLabel" text="2" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS">
                     <font>
                        <Font name="System Bold" size="36.0" />
                     </font>
                  </Label>
                  <Label fx:id="lblLevel3" onMousePressed="#mouseClickedLevelLabel" text="3" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS">
                     <font>
                        <Font name="System Bold" size="36.0" />
                     </font>
                  </Label>
                  <Label fx:id="lblLevel4" onMousePressed="#mouseClickedLevelLabel" text="4" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS">
                     <font>
                        <Font name="System Bold" size="36.0" />
                     </font>
                  </Label>
                  <Label fx:id="lblLevel5" onMousePressed="#mouseClickedLevelLabel" text="5" GridPane.columnIndex="4" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS">
                     <font>
                        <Font name="System Bold" size="36.0" />
                     </font>
                  </Label>
                  <Label fx:id="lblLevel6" onMouseClicked="#mouseClickedLevelLabel" onMousePressed="#mouseClickedLevelLabel" text="6" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.vgrow="ALWAYS">
                     <font>
                        <Font name="System Bold" size="36.0" />
                     </font>
                  </Label>
                  <Label fx:id="lblLevel7" onMousePressed="#mouseClickedLevelLabel" text="7" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.vgrow="ALWAYS">
                     <font>
                        <Font name="System Bold" size="36.0" />
                     </font>
                  </Label>
                  <Label fx:id="lblLevel8" onMousePressed="#mouseClickedLevelLabel" text="8" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.vgrow="ALWAYS">
                     <font>
                        <Font name="System Bold" size="36.0" />
                     </font>
                  </Label>
                  <Label fx:id="lblLevel9" onMousePressed="#mouseClickedLevelLabel" text="9" GridPane.columnIndex="3" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.vgrow="ALWAYS">
                     <font>
                        <Font name="System Bold" size="36.0" />
                     </font>
                  </Label>
                  <Label fx:id="lblLevel10" onMousePressed="#mouseClickedLevelLabel" text="10" GridPane.columnIndex="4" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.vgrow="ALWAYS">
                     <font>
                        <Font name="System Bold" size="36.0" />
                     </font>
                  </Label>
               </children>
            </GridPane>
         </children>
      </VBox>
   </center>
   <top>
      <GridPane fx:id="gridPaneTop">
         <columnConstraints>
            <ColumnConstraints hgrow="ALWAYS" maxWidth="207.0" minWidth="10.0" prefWidth="20.0" />
            <ColumnConstraints hgrow="SOMETIMES" maxWidth="207.0" minWidth="10.0" prefWidth="58.0" />
            <ColumnConstraints hgrow="ALWAYS" maxWidth="207.0" minWidth="10.0" prefWidth="105.0" />
            <ColumnConstraints hgrow="ALWAYS" maxWidth="451.0" minWidth="10.0" prefWidth="418.0" />
         </columnConstraints>
         <rowConstraints>
            <RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
         </rowConstraints>
         <children>
            <Button fx:id="backButton" maxHeight="-Infinity" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#handleBackButtonAction" prefHeight="30.0" prefWidth="0.0" text="Back" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.valignment="CENTER" />
            <Separator prefHeight="63.0" prefWidth="73.0" visible="false" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.valignment="CENTER" GridPane.vgrow="ALWAYS" />
            <Label fx:id="lblLevelSelection" text="Level Selection" underline="true" GridPane.columnIndex="3">
               <font>
                  <Font name="System Bold" size="36.0" />
               </font>
            </Label>
            <Separator prefHeight="63.0" prefWidth="73.0" visible="false" />
         </children>
      </GridPane>
   </top>
   <bottom>
      <Region prefHeight="36.0" prefWidth="600.0" BorderPane.alignment="CENTER" />
   </bottom>
   <left>
      <HBox>
         <children>
            <Region maxWidth="1.7976931348623157E308" prefHeight="200.0" prefWidth="50.0" BorderPane.alignment="CENTER" HBox.hgrow="ALWAYS" />
         </children>
      </HBox>
   </left>
   <right>
      <HBox>
         <children>
            <Region maxWidth="1.7976931348623157E308" prefHeight="300.0" prefWidth="50.0" BorderPane.alignment="CENTER" HBox.hgrow="ALWAYS" />
         </children>
      </HBox>
   </right>
</BorderPane>

Upvotes: 2

Views: 3086

Answers (1)

JohnRW
JohnRW

Reputation: 778

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

Upvotes: 2

Related Questions