Своё отображение линий сетки GridPane в JavaFX

в 14:58, , рубрики: java, javafx, метки:

В 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();
}

Выглядеть это будет так (слева — до, справа — после):image

Автор: Qwertovsky

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js