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
| Method | What 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.
| Method | Default 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