在 JavaFX 中将元素动态添加到固定大小的 GridPane

Posted

技术标签:

【中文标题】在 JavaFX 中将元素动态添加到固定大小的 GridPane【英文标题】:Dynamically add elements to a fixed-size GridPane in JavaFX 【发布时间】:2014-06-09 23:25:20 【问题描述】:

我想在 JavaFX 中显示一个包含不同数量矩形的网格。不能调整此网格的大小,这一点很重要。

我选择了GridPane 布局。我动态添加javafx.scene.shape.Rectangle 到它。这是 2 行 4 列的网格的样子。

在调整大小时,我希望它保持相同的整体形状,也就是说每个Rectangle 具有相同的大小并在我的Rectangles 之间保持水平和垂直间隙。

但是,这是我使用 4x4 网格得到的结果:

问题是:

最后一行和最后一列的大小与Rectangles 的其余部分不同。 差距已经消失。

这是我负责刷新显示的代码:

public void refreshConstraints() 
    getRowConstraints().clear();
    getColumnConstraints().clear();

    for (int i = 0; i < nbRow; i++) 
        RowConstraints rConstraint = new RowConstraints();
        // ((nbRow - 1) * 10 / nbRow) = takes gap into account (10% of height)
        rConstraint.setPercentHeight(100 / nbRow - ((nbRow - 1) * 10 / nbRow));
        getRowConstraints().add(rConstraint);
    

    for (int i = 0; i < nbColumn; i++) 
        ColumnConstraints cConstraint = new ColumnConstraints();
        cConstraint.setPercentWidth(100 / nbColumn - ((nbColumn - 1) * 10 / nbColumn));
        getColumnConstraints().add(cConstraint);
    


使用setFillWidthsetHgrow 也不会产生任何结果,我的Rectangles 之间的间隙保持不变,但Rectangles 没有调整大小,它们与我的其余GUI 元素重叠。


编辑:MCVE代码:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.RowConstraints;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class DynamicGrid extends Application 

    //Class containing grid (see below)
    private GridDisplay gridDisplay;

    @Override
    public void start(Stage primaryStage) 

        //Represents the grid with Rectangles
        gridDisplay = new GridDisplay(400, 200);

        //Fields to specify number of rows/columns
        TextField rowField = new TextField();
        TextField columnField = new TextField();
        //Function to set an action when text field loses focus
        buildTextFieldActions(rowField, columnField);
        HBox fields = new HBox();
        fields.getChildren().add(rowField);
        fields.getChildren().add(new Label("x"));
        fields.getChildren().add(columnField);

        BorderPane mainPanel = new BorderPane();
        mainPanel.setLeft(gridDisplay.getDisplay());
        mainPanel.setBottom(fields);

        Scene scene = new Scene(mainPanel);
        primaryStage.setTitle("Test grid display");
        primaryStage.setScene(scene);
        primaryStage.show();
    

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) 
        launch(args);
    

    private void buildTextFieldActions(final TextField rowField, final TextField columnField) 
        rowField.focusedProperty().addListener(new ChangeListener<Boolean>() 

            @Override
            public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) 
                if (!t1) 
                    if (!rowField.getText().equals("")) 
                        try 
                            int nbRow = Integer.parseInt(rowField.getText());
                            gridDisplay.setRows(nbRow);
                            gridDisplay.updateDisplay();
                         catch (NumberFormatException nfe) 
                            System.out.println("Please enter a valid number.");
                        
                    
                
            
        );

        columnField.focusedProperty().addListener(new ChangeListener<Boolean>() 

            @Override
            public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) 
                if (!t1) 
                    if (!columnField.getText().equals("")) 
                        try 
                            int nbColumn = Integer.parseInt(columnField.getText());
                            gridDisplay.setColumns(nbColumn);
                            gridDisplay.updateDisplay();
                         catch (NumberFormatException nfe) 
                            System.out.println("Please enter a valid number.");
                        
                    
                
            
        );
    

    //Class responsible for displaying the grid containing the Rectangles
    public class GridDisplay 

        private GridPane gridPane;
        private int nbRow;
        private int nbColumn;
        private int width;
        private int height;
        private double hGap;
        private double vGap;

        public GridDisplay(int width, int height) 
            this.gridPane = new GridPane();
            this.width = width;
            this.height = height;
            build();
        

        private void build() 
            this.hGap = 0.1 * width;
            this.vGap = 0.1 * height;
            gridPane.setVgap(vGap);
            gridPane.setHgap(hGap);
            gridPane.setPrefSize(width, height);
            initializeDisplay(width, height);
        

        //Builds the first display (correctly) : adds a Rectangle for the number
        //of rows and columns
        private void initializeDisplay(int width, int height) 
            nbRow = height / 100;
            nbColumn = width / 100;

            for (int i = 0; i < nbColumn; i++) 
                for (int j = 0; j < nbRow; j++) 
                    Rectangle rectangle = new Rectangle(100, 100);
                    rectangle.setStroke(Paint.valueOf("orange"));
                    rectangle.setFill(Paint.valueOf("steelblue"));
                    gridPane.add(rectangle, i, j);

                
            
        

        //Function detailed in post
        //Called in updateDisplay()
        public void refreshConstraints() 
            gridPane.getRowConstraints().clear();
            gridPane.getColumnConstraints().clear();
            for (int i = 0; i < nbRow; i++) 
                RowConstraints rConstraint = new RowConstraints();
                rConstraint.setPercentHeight(100 / nbRow - ((nbRow - 1) * 10 / nbRow));
                gridPane.getRowConstraints().add(rConstraint);
            

            for (int i = 0; i < nbColumn; i++) 
                ColumnConstraints cConstraint = new ColumnConstraints();
                cConstraint.setPercentWidth(100 / nbColumn - ((nbColumn - 1) * 10 / nbColumn));
                gridPane.getColumnConstraints().add(cConstraint);
            

        

        public void setColumns(int newColumns) 
            nbColumn = newColumns;
        

        public void setRows(int newRows) 
            nbRow = newRows;
        

        public GridPane getDisplay() 
            return gridPane;
        

        //Function called when refreshing the display
        public void updateDisplay() 
            Platform.runLater(new Runnable() 

                @Override
                public void run() 
                    //The gridpane is cleared of the previous children
                    gridPane.getChildren().clear();

                    //A new rectangle is added for row*column
                    for (int i = 0; i < nbColumn; i++) 
                        for (int j = 0; j < nbRow; j++) 
                            Rectangle rectangle = new Rectangle(100, 100);
                            rectangle.setStroke(Paint.valueOf("orange"));
                            rectangle.setFill(Paint.valueOf("steelblue"));
                            gridPane.add(rectangle, i, j);
                        
                    
                    //Call to this function to update the grid's constraints
                    refreshConstraints();
                
            );

        

    


【问题讨论】:

将 GridPane 粘贴到一个组中。它解决了你的问题吗?如果没有,请编辑您的帖子以包含MCVE。 使用组不会改变任何事情。包括 MCVE。要更改行数,请单击第一个文本字段,输入一个整数,然后单击另一个文本字段。这里的问题似乎有点不同,当我不希望 GridPane 调整大小时。 【参考方案1】:

似乎TilePane 比 GridPane 更适合此用例。

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.TilePane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

// java 8 code
public class DynamicTiles extends Application 

    //Class containing grid (see below)
    private GridDisplay gridDisplay;

    //Class responsible for displaying the grid containing the Rectangles
    public class GridDisplay 

        private static final double ELEMENT_SIZE = 100;
        private static final double GAP = ELEMENT_SIZE / 10;

        private TilePane tilePane = new TilePane();
        private Group display = new Group(tilePane);
        private int nRows;
        private int nCols;

        public GridDisplay(int nRows, int nCols) 
            tilePane.setStyle("-fx-background-color: rgba(255, 215, 0, 0.1);");
            tilePane.setHgap(GAP);
            tilePane.setVgap(GAP);
            setColumns(nCols);
            setRows(nRows);
        

        public void setColumns(int newColumns) 
            nCols = newColumns;
            tilePane.setPrefColumns(nCols);
            createElements();
        

        public void setRows(int newRows) 
            nRows = newRows;
            tilePane.setPrefRows(nRows);
            createElements();
        

        public Group getDisplay() 
            return display;
        

        private void createElements() 
            tilePane.getChildren().clear();
            for (int i = 0; i < nCols; i++) 
                for (int j = 0; j < nRows; j++) 
                    tilePane.getChildren().add(createElement());
                
            
        

        private Rectangle createElement() 
            Rectangle rectangle = new Rectangle(ELEMENT_SIZE, ELEMENT_SIZE);
            rectangle.setStroke(Color.ORANGE);
            rectangle.setFill(Color.STEELBLUE);

            return rectangle;
        

    

    @Override
    public void start(Stage primaryStage) 

        //Represents the grid with Rectangles
        gridDisplay = new GridDisplay(2, 4);

        //Fields to specify number of rows/columns
        TextField rowField = new TextField("2");
        TextField columnField = new TextField("4");

        //Function to set an action when text field loses focus
        buildTextFieldActions(rowField, columnField);

        HBox fields = new HBox(10);
        fields.getChildren().add(rowField);
        fields.getChildren().add(new Label("x"));
        fields.getChildren().add(columnField);

        BorderPane mainPanel = new BorderPane();
        mainPanel.setCenter(gridDisplay.getDisplay());
        mainPanel.setTop(fields);

        Scene scene = new Scene(mainPanel, 1000, 800);
        primaryStage.setTitle("Test grid display");
        primaryStage.setScene(scene);
        primaryStage.show();
    

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) 
        launch(args);
    

    private void buildTextFieldActions(final TextField rowField, final TextField columnField) 
        rowField.focusedProperty().addListener((ov, t, t1) -> 
            if (!t1) 
                if (!rowField.getText().equals("")) 
                    try 
                        int nbRow = Integer.parseInt(rowField.getText());
                        gridDisplay.setRows(nbRow);
                     catch (NumberFormatException nfe) 
                        System.out.println("Please enter a valid number.");
                    
                
            
        );

        columnField.focusedProperty().addListener((ov, t, t1) -> 
            if (!t1) 
                if (!columnField.getText().equals("")) 
                    try 
                        int nbColumn = Integer.parseInt(columnField.getText());
                        gridDisplay.setColumns(nbColumn);
                     catch (NumberFormatException nfe) 
                        System.out.println("Please enter a valid number.");
                    
                
            
        );
    

【讨论】:

【参考方案2】:

非常感谢您的回答。 TilePanes 确实更容易使用,尽管您所写的内容并不能完全回答我的问题。

我想要一个窗格,children 可以在其中调整大小,而不是窗格本身。似乎设置maxSizeprefSize 没有任何效果。

编辑:我设法在我的GridDisplay 类中使用了两个JavaFX Property,对应于我的网格的固定高度和宽度:

public class GridDisplay 
    private ReadOnlyDoubleProperty heightProperty;
    private ReadOnlyDoubleProperty widthProperty;

    ...

然后我为这些成员分配与构造函数中所需的固定大小相对应的值。网格内子节点的大小对应于网格高度和宽度的一小部分,具体取决于行数和列数。这是我的updateDisplay() 的样子:

public void updateDisplay() 
    gridPane.getChildren().clear();
    for (int i = 0; i < nbColumn; i++) 
        for (int j = 0; j < nbRow; j++) 
            Rectangle rectangle = new Rectangle(100, 100);
            //Binding the fraction of the grid size to the width
            //and heightProperty of the child
            rectangle.widthProperty().bind(widthProperty.divide(nbColumn));
            rectangle.heightProperty().bind(heightProperty.divide(nbRow));
            gridPane.add(rectangle, i, j);
         
    

【讨论】:

以上是关于在 JavaFX 中将元素动态添加到固定大小的 GridPane的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 11 中将 JavaFX 运行时添加到 Eclipse?

在 JavaFX 中将 BlendMode 添加到裁剪节点

如何在 JavaFX 中将 CheckBox 添加到 TableView

如何均匀分布 JavaFX VBox 的元素

如何在 JavaFX 中将图像设置为按钮的大小

子节点的 JavaFX GridPane 动态调整大小以填充分配的区域