В JavaFX 2 меня порадовала возможность включить отображение линий сетки в GridPane. При использовании Swing и GridBagLayout приходилось искать, в какую ячейку попал элемент, что так криво отображается. Новая возможна должна помочь решить эту проблему.
Но радость моя длилась не долго. Линии сетки мало о чём говорят. Они пересекают элементы, которые располагаются не на одной ячейке. Промежутки между ячейками не отмечены. Линии сетки серые, и цвет поменять нельзя.
Я решил нарисовать свои линии, так как это пригодится не для одной программы.
Метод, который рисует линии сетки, переопределить в проекте нельзя. Области видимости настроены так, что надо будет переписать весь GridPane и не только. Добавлять в проект лишние классы для отладки — тоже не лучший вариант.
Поэтому я заменил GridPane прямо в jfxrt.jar.
Линии
Отображением линий занимается метод
private void layoutGridLines(double x, double y, double columnHeight, double rowWidth) {...}
, где (x,y) — координаты верхнего левого угла сетки, а columnHeight и rowWidth — высота колонки и длина стоки соответственно. Последние мне не понадобились.
Стандартный метод просто рисовал линии от начала и до конца на нужном удалении друг от друга. Мне пришлось добавить несколько дополнительных проверок.
Сначала рисуются вертикальные линии. Для этого перебираются все столбцы.
Внутри этого цикла идёт перебор строк для того, чтобы проверить: занимает ли элемент одну ячейку или нет. От этого зависит, будет ли линия пересекать эту строку.
Если промежуток между столбцами (Hgap) не 0, то рисуется прямоугольник, иначе линия.
И так по вертикали до конца столбца. Затем осуществляется переход к новому столбцу.
С горизонтальными линиями также.
private void layoutGridLines(double x, double y, double columnHeight, double rowWidth)
{
if (!isGridLinesVisible()) {
return;
}
if (!gridLines.getChildren().isEmpty()) {
gridLines.getChildren().clear();
}
// create vertical lines
double linex = x;
double liney = y;
//iterate through the columns
for (int columnIndex = 0; columnIndex <= columnWidths.length; columnIndex++)
{
//iterate through the rows
rows:for(int rowIndex = 0; rowIndex < rowHeights.length; rowIndex++)
{
//look for children that span more then 1 columns
for(int k=0; k < getChildren().size(); k++)
{
Node node = getChildren().get(k);
//line crosses the node
if(getNodeColumnIndex(node) < columnIndex && getNodeColumnEnd(node) >= columnIndex
&& getNodeRowIndex(node) <= rowIndex && getNodeRowEnd(node) >= rowIndex)
{
//no draw line
liney += rowHeights[rowIndex];
//draw vgap
if (rowIndex < rowHeights.length - 1 && getVgap() != 0)
{
//draw rectangle
if(getHgap() != 0 && columnIndex < columnWidths.length && columnIndex > 0)
{
if(getNodeRowEnd(node) <= rowIndex)
{
gridLines.getChildren().add(createGridRectangle(linex, liney
, getHgap(), getVgap()));
}
}
//draw line
else
{
if(getNodeRowEnd(node) <= rowIndex || columnIndex == columnWidths.length || columnIndex == 0)
gridLines.getChildren().add(createGridLine(linex, liney
, linex, liney + getVgap()));
}
}
liney += getVgap();
continue rows;
}
}
//draw row
if(getHgap() != 0 && columnIndex < columnWidths.length && columnIndex > 0)
{
gridLines.getChildren().add(createGridRectangle(linex, liney
, getHgap(), rowHeights[rowIndex]));
}
else
{
gridLines.getChildren().add(createGridLine(linex, liney
, linex, liney + rowHeights[rowIndex]));
}
liney += rowHeights[rowIndex];
//draw vgap
if(getVgap() != 0 && rowIndex < rowHeights.length - 1)
{
if(getHgap() != 0 && columnIndex < columnWidths.length && columnIndex > 0)
{
gridLines.getChildren().add(createGridRectangle(linex, liney
, getHgap(), getVgap()));
}
else
gridLines.getChildren().add(createGridLine(linex, liney
, linex, liney + getVgap()));
liney += getVgap();
}
}
//to new column
liney = y;
if(columnIndex < columnWidths.length)
{
if(getHgap() != 0 && columnIndex > 0)
linex += getHgap();
linex += columnWidths[columnIndex];
}
}
// create horizontal lines
linex = x;
liney = y;
//iterate through the rows
for (int rowIndex = 0; rowIndex <= rowHeights.length; rowIndex++)
{
//iterate through the column
columns:for(int columnIndex = 0; columnIndex < columnWidths.length; columnIndex++)
{
//look for children that span more then 1 row
for(int k=0; k < getChildren().size(); k++)
{
Node node = getChildren().get(k);
//line crosses the node
if(getNodeColumnIndex(node) <= columnIndex && getNodeColumnEnd(node) >= columnIndex
&& getNodeRowIndex(node) < rowIndex && getNodeRowEnd(node) >= rowIndex)
{
//no draw line
linex += columnWidths[columnIndex];
//draw hgap
if (columnIndex < columnWidths.length - 1 && getHgap() != 0)
{
//draw rectangle
if(getVgap() != 0 && rowIndex < rowHeights.length && rowIndex > 0)
{
if(getNodeColumnEnd(node) <= columnIndex)
{
gridLines.getChildren().add(createGridRectangle(linex, liney
, getHgap(), getVgap()));
}
}
//draw line
else
{
if(getNodeColumnEnd(node) <= columnIndex || rowIndex == rowHeights.length || rowIndex == 0)
gridLines.getChildren().add(createGridLine(linex, liney
, linex, liney + getHgap()));
}
}
linex += getHgap();
continue columns;
}
}
//draw column
if(getVgap() != 0 && rowIndex < rowHeights.length && rowIndex > 0)
{
gridLines.getChildren().add(createGridRectangle(linex, liney
, columnWidths[columnIndex], getVgap()));
}
else
{
gridLines.getChildren().add(createGridLine(linex, liney
, linex + columnWidths[columnIndex], liney));
}
linex += columnWidths[columnIndex];
//draw hgap
if(getHgap() != 0 && columnIndex < columnWidths.length - 1)
{
if(getVgap() != 0 && rowIndex < rowHeights.length && rowIndex > 0)
{
gridLines.getChildren().add(createGridRectangle(linex, liney
, getHgap(), getVgap()));
}
else
gridLines.getChildren().add(createGridLine(linex, liney
, linex + getHgap(), liney));
linex += getHgap();
}
}
//to new row
linex = x;
if(rowIndex < rowHeights.length)
{
if(getVgap() != 0 && rowIndex > 0)
liney += getVgap();
liney += rowHeights[rowIndex];
}
}
}
Цвета
Теперь добавим возможность менять цвет сетки.
Изменим значение по умолчанию для цвета сетки. Я добавил прозрачности.
private static final Color GRID_LINE_COLOR = Color.rgb(0, 0, 0, 0.3);
Добавим Property для цвета.
/**
* Grid line color.
*/
public final ObjectProperty<Paint> gridLineColorProperty()
{
if (gridLineColor == null)
{
gridLineColor = new StyleableObjectProperty<Paint>(Color.rgb(0, 0, 0, 0.3))
{
@Override
public void invalidated()
{
requestLayout();
}
@Override
public StyleableProperty getStyleableProperty()
{
return StyleableProperties.GRID_LINE_COLOR;
}
@Override
public Object getBean()
{
return GridPane.this;
}
@Override
public String getName()
{
return "gridLineColor";
}
};
}
return gridLineColor;
}
private ObjectProperty<Paint> gridLineColor;
public final void setGridLineColor(Paint value) {
gridLineColorProperty().set(value);
}
public final Paint getGridLineColor() {
return gridLineColor == null ? Color.rgb(0, 0, 0, 0.3) : gridLineColor.get();
}
Осталось во внутренний класс StyleableProperties добавить ещё одно StyleableProperty.
private static final StyleableProperty<GridPane,Paint> GRID_LINE_COLOR =
new StyleableProperty<GridPane,Paint>("-fx-grid-line-color",
PaintConverter.getInstance(), Color.rgb(0, 0, 0, 0.3))
{
@Override
public boolean isSettable(GridPane node)
{
return node.gridLineColor == null || !node.gridLineColor.isBound();
}
@Override
public WritableValue<Paint> getWritableValue(GridPane node)
{
return node.gridLineColorProperty();
}
};
Теперь можно задавать цвета.
pane.setGridLinesVisible(true);
pane.setStyle("-fx-grid-line-color: rgba(200,0,0,.3);");
pane.setGridLineColor(Color.rgb(0,200,0,0.3));
Стиль, оказывается, имеет приоритет. Сетка будет красной.
Тестовый пример:
@Override
public void start(Stage stage) throws Exception
{
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
pane.setPadding(new Insets(15,15,15,15));
pane.setVgap(30);
pane.setHgap(10);
pane.setGridLinesVisible(true);
pane.setStyle("-fx-grid-line-color: rgba(200,0,0,.3);");
// pane.setGridLineColor(Color.rgb(0,200,0,0.3));
Text column0row0cs4 = new Text("columnSpan:4");
pane.add(column0row0cs4, 0, 0, 4, 1);
Text column0row1 = new Text("column0row1");
pane.add(column0row1, 0, 1);
Text column1row1cs2 = new Text("columnSpan:2");
pane.add(column1row1cs2, 1, 1, 2, 1);
Text column3row0rs2 = new Text("rowSpan:2");
pane.add(column3row0rs2, 3, 1, 1, 2);
Text column0row2 = new Text("column0row2");
pane.add(column0row2, 0, 2);
Text column1row2cs2rs2 = new Text("columnSpan:2n" +
"rowSpan:2");
pane.add(column1row2cs2rs2, 1, 2, 2, 2);
Text column0row3 = new Text("column0row3");
Text column3row3 = new Text("column3row3");
pane.add(column0row3, 0, 3);
pane.add(column3row3, 3, 3);
Text column0row4cs3 = new Text("columnSpan:3");
pane.add(column0row4cs3, 0, 4, 3, 1);
Text column3row4 = new Text("column3row4");
pane.add(column3row4, 3, 4);
Text column0row5 = new Text("column0row5");
pane.add(column0row5, 0, 5);
Text column1row5 = new Text("column1row5");
pane.add(column1row5, 1, 5);
Text column2row5 = new Text("column2row5");
pane.add(column2row5, 2, 5);
Text column3row5 = new Text("column3row5");
pane.add(column3row5, 3, 5);
Scene scene = null;
scene = new Scene(pane, 360, 330);
stage.setScene(scene);
stage.setTitle("Custom grid lines");
stage.show();
}
Выглядеть это будет так (слева — до, справа — после):
Автор: Qwertovsky