JavaFX中有没有办法从TilePane或FlowPane 和 GridPane中获得最佳效果?
这就是我想要实现的目标:
首先,我喜欢GridPane的想法,我可以在其中设置M×N网格,该网格在其父容器中自动调整大小,将空间平均分为M列和N行。然后我可以放置一些完全填充每个单元格的子元素,它们将随网格一起拉伸。这很酷。
但是有一个缺点:我需要通过设置它的行和放大器来明确指定每个控件应该去哪里。列号。
然后,有一些布局容器,如FlowPane或TilePane,当父容器更改其大小时,它们会自动重排其子元素。当我添加另一个子元素时,它会自动附加到列表的末尾,并且元素列表在到达容器边缘后会自动换行,因为空间太小而无法容纳另一个元素。
但这也是一个缺点:子元素只能有严格的预定义大小。它们不会使用其父元素进行拉伸。
这就是我需要的东西:
我需要两个容器中最好的,也就是说,我想要一个M×N网格(比如说4×4),其中每个单元格是ParentWight / M的ParentWidth / M并且随着窗口一起伸展,所以它总是4×4细胞,但它们的大小(以及它们的内容的大小)相应地延伸。
但是我不想明确告诉容器哪一排和哪一行?列添加我添加的每个新孩子。相反,我想只是添加它,让容器找出要放入的第一个空单元格,从左到右填充单元格,如果当前行中没有空单元格,则从上到下填充。
是否有任何这些预定义容器属性的神奇设置可以让我实现这一目标?或者我自己需要写这样的容器吗?
答案 0 :(得分:4)
正是为了您所描述的目的,我创建了ButtonGridPane
。这是初稿,还有改进的余地,但也许它可以给你一个基本的想法。
public class ButtonGrid extends Pane {
private static final double DEFAULT_RATIO = 0.618033987;
private int columnCount;
private double ratio;
public ButtonGrid(int columnCount) {
this(columnCount, DEFAULT_RATIO);
}
public ButtonGrid(int columnCount, double heightToWidthRatio) {
getStyleClass().add("button-grid");
this.columnCount = columnCount;
ratio = heightToWidthRatio;
}
public void setColumnCount(int columnCount) {
this.columnCount = columnCount;
}
public void setHeightToWidthRatio(double ratio) {
this.ratio = ratio;
}
@Override
public Orientation getContentBias() {
return Orientation.HORIZONTAL;
}
@Override
protected void layoutChildren() {
double left = getInsets().getLeft();
double top = getInsets().getTop();
double tileWidth = calculateTileWidth(getWidth());
double tileHeight = calculateTileHeight(getWidth());
ObservableList<Node> children = getChildren();
double currentX = left;
double currentY = top;
for (int idx = 0; idx < children.size(); idx++) {
if (idx > 0 && idx % columnCount == 0) {
currentX = left;
currentY = currentY + tileHeight;
}
children.get(idx).resize(tileWidth, tileHeight);
children.get(idx).relocate(currentX, currentY);
currentX = currentX + tileWidth;
}
}
@Override
protected double computePrefWidth(double height) {
double w = 0;
for (int idx = 0; idx < columnCount; idx++) {
Node node = getChildren().get(idx);
w += node.prefWidth(-1);
}
return getInsets().getLeft() + w + getInsets().getRight();
}
@Override
protected double computePrefHeight(double width) {
double h = calculateTileHeight(width) * getRowCount();
return getInsets().getTop() + h + getInsets().getBottom();
}
private double calculateTileHeight(double width) {
return calculateTileWidth(width) * ratio;
}
private double calculateTileWidth(double width) {
return (-getInsets().getLeft() + width - getInsets().getRight()) / columnCount;
}
private int getRowCount() {
return getChildren().size() / columnCount;
}
}
答案 1 :(得分:0)
好的,这是我自己尝试的解决方案:
由于GridPane
几乎按照我需要的方式工作,所以只是没有自动传递其内容,我决定继承GridPane
并添加自定义代码以自动流动儿童控制。 (感谢@jns提示可以将JavaFX库控件类子类化。)
因此,我将GridPane
子类化,并为其内部子项列表添加了一个侦听器,以便每当从该列表中添加或删除子项时,或者列表以某种方式更改(例如,我们对子项重新排序) ,通过在列表中的位置将它们分配给正确的列和行,调用私有方法来重排网格中的子节点。我还添加了两个私有辅助函数来将列表中的位置转换为行和放大器。列号和后面。
我知道这不完美也不优雅,但嘿,它有效,并且它足以满足我的需求:P所以我在这里发布代码以防其他人遇到同样的问题:
package flowgrid;
import javafx.collections.ObservableList;
import javafx.collections.ListChangeListener;
import javafx.scene.Node;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.Priority;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.NamedArg;
/**
* This class subclasses the GridPane layout class.
* It manages its child nodes by arranging them in rows of equal number of tiles.
* Their order in the grid corresponds to their indexes in the list of children
* in the following fashion (similarly to how FlowPane works):
*
* +---+---+---+---+
* | 0 | 1 | 2 | 3 |
* +---+---+---+---+
* | 4 | 5 | 6 | 7 |
* +---+---+---+---+
* | 8 | 9 | … | |
* +---+---+---+---+
*
* It observes its internal list of children and it automatically reflows them
* if the number of columns changes or if you add/remove some children from the list.
* All the tiles of the grid are of the same size and stretch accordingly with the control.
*/
public class FlowGridPane extends GridPane
{
// Properties for managing the number of rows & columns.
private IntegerProperty rowsCount;
private IntegerProperty colsCount;
public final IntegerProperty colsCountProperty() { return colsCount; }
public final Integer getColsCount() { return colsCountProperty().get(); }
public final void setColsCount(final Integer cols) {
// Recreate column constraints so that they will resize properly.
ObservableList<ColumnConstraints> constraints = getColumnConstraints();
constraints.clear();
for (int i=0; i < cols; ++i) {
ColumnConstraints c = new ColumnConstraints();
c.setHalignment(HPos.CENTER);
c.setHgrow(Priority.ALWAYS);
c.setMinWidth(60);
constraints.add(c);
}
colsCountProperty().set(cols);
reflowAll();
}
public final IntegerProperty rowsCountProperty() { return rowsCount; }
public final Integer getRowsCount() { return rowsCountProperty().get(); }
public final void setRowsCount(final Integer rows) {
// Recreate column constraints so that they will resize properly.
ObservableList<RowConstraints> constraints = getRowConstraints();
constraints.clear();
for (int i=0; i < rows; ++i) {
RowConstraints r = new RowConstraints();
r.setValignment(VPos.CENTER);
r.setVgrow(Priority.ALWAYS);
r.setMinHeight(20);
constraints.add(r);
}
rowsCountProperty().set(rows);
reflowAll();
}
/// Constructor. Takes the number of columns and rows of the grid (can be changed later).
public FlowGridPane(@NamedArg("cols")int cols, @NamedArg("rows")int rows) {
super();
colsCount = new SimpleIntegerProperty(); setColsCount(cols);
rowsCount = new SimpleIntegerProperty(); setRowsCount(rows);
getChildren().addListener(new ListChangeListener<Node>() {
public void onChanged(ListChangeListener.Change<? extends Node> change) {
reflowAll();
}
} );
}
// Helper functions for coordinate conversions.
private int coordsToOffset(int col, int row) { return row*colsCount.get() + col; }
private int offsetToCol(int offset) { return offset%colsCount.get(); }
private int offsetToRow(int offset) { return offset/colsCount.get(); }
private void reflowAll() {
ObservableList<Node> children = getChildren();
for (Node child : children ) {
int offs = children.indexOf(child);
GridPane.setConstraints(child, offsetToCol(offs), offsetToRow(offs) );
}
}
}
如您所见,我还使用属性作为行数和列数,使用getter和setter。当使用setter更改行数或列数时,正在重新创建GridPane
中的列/行约束的内部列表,以便它们正确调整大小,并且当列数更改时内容将被重新填充。
构造函数中还有@NamedArg
个注释,因此可以使用初始行数和行数创建此控件。 FXML文件中描述的布局中的列。
由于它在某种程度上几乎“解决”了我的问题,我将我的答案标记为已被接受。但如果有人会发布更好的解决方案,那么我很乐意接受他的答案。
如果您对此代码的改进有任何建议,或者您认为可以更优雅地完成某些事情,请随时告诉我,因为 - 正如我所说 - 它还不是很完美。