JavaFX 中具有自动拉伸图块的 TilePane

Sas*_*asQ 6 layout javafx flowpane gridpane

JavaFX 中是否有任何方法可以充分利用 TilePane 或 FlowPaneGridPane?
\n这是我想要实现的目标:

\n\n

首先,我喜欢 GridPane 的想法,我可以在其中设置一个 M\xc3\x97N 网格,该网格会在其父容器内自动调整大小,以将空间平均划分为 M 列和 N 行。然后我可以放置一些完全填充每个单元格的子元素,它们将沿着网格拉伸。这很酷。
\n但有一个缺点:我需要通过设置其行号和列号来明确指定每个控件的位置。

\n\n

然后,还有诸如 FlowPane 或 TilePane 之类的布局容器,当父容器更改其大小时,它们会自动重排其子元素。当我添加另一个子元素时,它会自动附加到列表的末尾,并且当空间太少而无法容纳另一个元素时,元素列表在到达容器边缘后会自动换行。
\n但这也有一个缺点:子元素只能有严格的、预定义的大小。它们不会随其父元素一起拉伸。

\n\n

这就是我所需要的:
\n我需要这两个容器的最佳性能,也就是说,我想要一个 M × N 网格(比方说 4\xc3\x974),其中每个单元格都是 ParentWidth/M by ParentHeight/N 并沿与窗口,因此它始终是 4\xc3\x974 个单元格,但它们的大小(以及其内容的大小)相应地拉伸。\n但我不想明确告诉容器要放在哪一行和哪列中我在那里添加的每个新孩子。相反,我只想添加它,并让容器找出要放入的第一个空单元格,从左到右填充单元格,如果当前行中没有空单元格,则从上到下填充单元格。

\n\n

这些预定义容器的属性是否有一些神奇的设置可以让我实现这一目标?还是我需要自己写一个这样的容器?

\n

Sas*_*asQ 0

好的,这是我自己尝试的解决方案:

\n\n

由于它几乎按照GridPane我需要的方式工作,只是不会自动流动其内容,因此我决定子类化并添加自定义代码以自动流动其子控件。(感谢 @jns 提示 JavaFX 库控制类可以进行子类化。)GridPane

\n\n

所以我将GridPane,并为其内部子级列表添加了一个侦听器,以便每当从该列表中添加或删除子级时,或者列表以某种方式发生更改(例如,我们对子级重新排序)时,都会调用一个私有方法来回流通过根据列表中的位置将子级分配给正确的列和行来调整网格中的子级。我还添加了两个私有辅助函数,将列表中的位置转换为行号和列号,然后再转换回来。

\n\n

我知道这既不完美也不优雅,但是嘿,它有效,并且它足以满足我的需求:P所以我在这里发布代码以防其他人遇到同样的问题:

\n\n
package flowgrid;\n\nimport javafx.collections.ObservableList;\nimport javafx.collections.ListChangeListener;\n\nimport javafx.scene.Node;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.RowConstraints;\nimport javafx.scene.layout.Priority;\nimport javafx.geometry.HPos;\nimport javafx.geometry.VPos;\n\nimport javafx.beans.property.IntegerProperty;\nimport javafx.beans.property.SimpleIntegerProperty;\nimport javafx.beans.NamedArg;\n\n/**\n * This class subclasses the GridPane layout class.\n * It manages its child nodes by arranging them in rows of equal number of tiles.\n * Their order in the grid corresponds to their indexes in the list of children\n * in the following fashion (similarly to how FlowPane works):\n * \n *      +---+---+---+---+\n *      | 0 | 1 | 2 | 3 |\n *      +---+---+---+---+\n *      | 4 | 5 | 6 | 7 |\n *      +---+---+---+---+\n *      | 8 | 9 | \xe2\x80\xa6 |   |\n *      +---+---+---+---+\n *  \n * It observes its internal list of children and it automatically reflows them\n * if the number of columns changes or if you add/remove some children from the list.\n * All the tiles of the grid are of the same size and stretch accordingly with the control.\n */\npublic class FlowGridPane extends GridPane\n{\n   // Properties for managing the number of rows & columns.\n   private IntegerProperty rowsCount;\n   private IntegerProperty colsCount;\n\n   public final IntegerProperty colsCountProperty() { return colsCount; }\n   public final Integer getColsCount() { return colsCountProperty().get(); }\n   public final void setColsCount(final Integer cols) {\n      // Recreate column constraints so that they will resize properly.\n      ObservableList<ColumnConstraints> constraints = getColumnConstraints();\n      constraints.clear();\n      for (int i=0; i < cols; ++i) {\n         ColumnConstraints c = new ColumnConstraints();\n         c.setHalignment(HPos.CENTER);\n         c.setHgrow(Priority.ALWAYS);\n         c.setMinWidth(60);\n         constraints.add(c);\n      }\n      colsCountProperty().set(cols);\n      reflowAll();\n   }\n\n   public final IntegerProperty rowsCountProperty() { return rowsCount; }\n   public final Integer getRowsCount() { return rowsCountProperty().get(); }\n   public final void setRowsCount(final Integer rows) {\n      // Recreate column constraints so that they will resize properly.\n      ObservableList<RowConstraints> constraints = getRowConstraints();\n      constraints.clear();\n      for (int i=0; i < rows; ++i) {\n         RowConstraints r = new RowConstraints();\n         r.setValignment(VPos.CENTER);\n         r.setVgrow(Priority.ALWAYS);\n         r.setMinHeight(20);\n         constraints.add(r);\n      }\n      rowsCountProperty().set(rows);\n      reflowAll();\n   }\n\n   /// Constructor. Takes the number of columns and rows of the grid (can be changed later).\n   public FlowGridPane(@NamedArg("cols")int cols, @NamedArg("rows")int rows) {\n      super();\n      colsCount = new SimpleIntegerProperty();  setColsCount(cols);\n      rowsCount = new SimpleIntegerProperty();  setRowsCount(rows);\n      getChildren().addListener(new ListChangeListener<Node>() {\n         public void onChanged(ListChangeListener.Change<? extends Node> change) {\n            reflowAll();\n         }\n      } );\n   }\n\n   // Helper functions for coordinate conversions.\n   private int coordsToOffset(int col, int row) { return row*colsCount.get() + col; }\n   private int offsetToCol(int offset) { return offset%colsCount.get(); }\n   private int offsetToRow(int offset) { return offset/colsCount.get(); }\n\n   private void reflowAll() {\n      ObservableList<Node> children = getChildren();\n      for (Node child : children ) {\n         int offs = children.indexOf(child);\n         GridPane.setConstraints(child, offsetToCol(offs), offsetToRow(offs) );\n      }\n   }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

正如您所看到的,我还使用了行数和列数的属性以及 getter 和 setter。当使用 setter 更改行数或列数时,将GridPane重新创建内部列/行约束列表,以便正确调整它们的大小,并且当列数更改时,将重新排列内容。

\n\n

构造函数中还有@NamedArg注释,以便可以使用 FXML 文件中描述的布局中的初始行数和列数创建此控件。

\n\n

由于它在某种程度上“解决”了我的问题,因此我将我的答案标记为已接受。但如果有人能发布更好的解决方案,我会很乐意接受他的答案。

\n\n

如果您对这段代码的改进有任何建议,或者您认为可以做得更优雅,也请随时告诉我,因为 \xe2\x80\x93 正如我所说的 \xe2\x80\x93 ,它还不是完美的。

\n