Show grid lines for GridPane

There is good option in JavaFX 2. I can turn on visibility of grid lines for GridPane. I like it. With Swing and GridBagLayout it takes time to find out that element is placed in wrong cell and that is why form looks bad. New options helps.

I was happy but not for long. The lines cross elements if elements span two or more cells. Gaps are not visible. The lines are gray and it is inpossible to change color.
I desided to draw my lines.

Method that draw lines is private. I can override it if I rewrite almost whole GridPane and not just it. Adding new classes to project just for debugging is not good idea.
So I replaced GridPane in jfxrt.jar.

Lines

The method that draw lines is

private void layoutGridLines(double x, double y, double columnHeight, double rowWidth) {...}

, where (x,y) are top left corner coordinates of the grid. columnHeight и rowWidth - colomn height and row width respectively.

This method has drawn lines from start to end with a gap between them. I had to add new checks.

At first I draw vertical lines. To do this, I iterate over all columns. And inner loop checks rows to find cells that span columns. If sush cells exist, line does not cross them.
If the gap between columns (Hgap) is more than 0, I draw a rectangle, otherwise a line.\

The same algorithm is used for horizontal lines.

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];
		}
	}
	
}

Color

I want to change color of lines.

I added alpha channel to default color for grid lines.

private static final Color GRID_LINE_COLOR = Color.rgb(0, 0, 0, 0.3);

New Property for color.

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

Now I need to add new StyleableProperty to internal class StyleableProperties.

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

	};

Now I can set color.

pane.setGridLinesVisible(true);
pane.setStyle("-fx-grid-line-color: rgba(200,0,0,.3);");
pane.setGridLineColor(Color.rgb(0,200,0,0.3));

Style overrides property. Grid lines will be red. Not green.

Test example:

@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:2\n" +
			"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();
}

It will look like that (before and after): image