V4 V5

Implement IGridGraphics and the grid renders to any target you like. WinForms (GDI+), WPF, Avalonia and PDF are all implementations of this one interface — none of them is special.

The namespace is unvell.ReoGrid.Core.Rendering.

Implementing it

using unvell.ReoGrid.Core;
using unvell.ReoGrid.Core.Rendering;

/// <summary>
/// The smallest possible drawing target. Five methods are required; ellipses, polygons
/// and images have default implementations, so override those only when you need them.
/// </summary>
public sealed class TraceGraphics : IGridGraphics
{
	public void FillRectangle(double x, double y, double w, double h, uint argb)
		=> Console.WriteLine($"fill {x},{y} {w}x{h} #{argb:x8}");

	public void DrawLine(double x1, double y1, double x2, double y2, uint argb, double width)
		=> Console.WriteLine($"line {x1},{y1} -> {x2},{y2}");

	public void DrawText(string text, double x, double y, double w, double h, uint argb, in TextStyle style)
		=> Console.WriteLine($"text \"{text}\" at {x},{y}");

	public double MeasureTextWidth(string text, in TextStyle style)
		=> text.Length * style.FontSize * 0.6;   // a real implementation would measure the text

	public void PushClip(double x, double y, double w, double h) { }

	public void PopClip() { }
}

Required methods

MethodWhat it does
FillRectangle(x, y, w, h, argb)Fills a rectangle
DrawLine(x1, y1, x2, y2, argb, width)Draws a line
DrawText(text, x, y, w, h, argb, style)Draws one line of text
MeasureTextWidth(text, style)Measures text width
PushClip(x, y, w, h) / PopClip()Clipping

MeasureTextWidth drives column auto-fit and the decision about text overflowing. An approximation will visibly break the layout — measure with real font metrics.

You do not implement wrapping

Line breaking is done by the core (TextLayout) using nothing but MeasureTextWidth, and DrawText receives one line, without newlines, at TextWrap.None. Leaving it to the platform’s own line breaking (FormattedText.MaxTextWidth and the like) produces differences between targets — Japanese without spaces failing to wrap, for instance — so do not wrap it yourself.

If some caller hands DrawText a style whose WrapMode is not None, delegate to TextLayout.DrawBlock(this, text, style, x, y, w, h, argb, style.WrapMode) and you get the same break points and the same kinsoku handling.

Methods with a default

Override these only when you need them.

MethodDefault behaviour
DrawBorderLine(...)Ignores the line style and delegates to DrawLine
FillEllipse(...)Fills the bounding rectangle
DrawEllipse(...)Does nothing
FillPolygon(points, argb)Fills the bounding rectangle
DrawImage(image, x, y, w, h)Does nothing
MeasureTextMetrics(text, style)Estimates from the font size (baseline = 0.8em)
MeasureTextHeight(style)The line height from MeasureTextMetrics("Ag", style)

MeasureTextMetrics is what aligns baselines

For rich text with mixed formatting, the core picks a single baseline per line and hands you each run as “a VAlign.Top rectangle shifted up by that run’s ascent”. So the Ascent that MeasureTextMetrics returns must be on the same footing as where DrawText places text at VAlign.Top (FormattedText.Baseline on WPF / Avalonia, FontFamily.GetCellAscent on GDI+).

It takes the text, not just the style, because font fallback happens per character. In a cell whose font is Arial, a Japanese word is drawn in some substitute font, so laying it out with Arial’s metrics makes it overflow the cell.

Ellipses and polygons are used to draw icon sets. Implement them if you want conditional formatting icons to come out right.

DrawImage is used to draw images.

Rendering

var viewport = new GridViewport(ws);
var graphics = new TraceGraphics();

viewport.Render(graphics, viewWidth: 800, viewHeight: 600);

What Render calls, and in what order, is the viewport’s business. Your implementation just receives primitives and draws them; it never needs to know what a cell means.

Coordinate system

Everything is in logical pixels (at 96 DPI). The origin is the top-left of the view, and headers and scroll position arrive already resolved by the viewport.

A target that needs physical pixels (GDI+, say) converts on its own side.

License checking

GridViewport.Render is the single path that screen, print and PDF all go through. The unlicensed watermark is drawn there, so a custom drawing layer gets it too.

What it is for

  • Generating images server-side
  • Embedding in your own UI framework
  • Exporting to another format such as SVG
Was this article helpful?