Table Authoring Service

Tables are usually authored by the end user through the editor's built-in table dialog and context menu. When you need to drive table editing from code — for instance, applying a templated row from your view-model, adding columns when the user clicks an external button, or merging cells programmatically — the WPF HTML Editor exposes a dedicated ITableAuthoringService at editor.Content.TableAuthoringService.

The service operates on the active table — that is, the table the editor's caret is currently inside. Most calls are no-ops when the caret is not inside a <table>, so guard from the host side if you want to give the user clear feedback (for example, only enable a "Merge Cells" button when CanMergeCells() returns true).

Visual Studio IntelliSense showing the ITableAuthoringService members exposed on Editor.Content.TableAuthoringService in the WPF HTML Editor.

Getting the active table

To inspect the currently-active table from code, ask the content surface for its current element snapshot. The TableElement entity is an editable projection of the underlying MSHTML <table>: it surfaces Rows, Columns, BorderWidth, CellPadding, CellSpacing, Width/WidthUnit, Height/HeightUnit, BgColor, BorderColor, BorderStyle, BorderCollapse, Caption, CssClassName, and a GetFirstCellElement() helper that hands back a TableCellElement primed to push edits down to every cell.

To check whether the manipulator can act on the current selection (for example before invoking a merge), call CanMergeCells() on the service or use the helper instance returned from GetTableManipulator():

using SpiceLogic.HtmlEditor.Abstractions.Services.ElementAuthoring;

ITableAuthoringService tables = Editor.Content.TableAuthoringService;
ITableManipulator manipulator = tables.GetTableManipulator();
bool canMerge = manipulator.CanMerge();

Inserting a brand-new table

Important: ITableAuthoringService does not expose a method that creates a new table from scratch — it operates on whichever table the caret is already inside. To insert a brand-new table from code, use the editor's general-purpose HTML insertion path on the content surface, editor.Content.InsertHtml, with a small <table> snippet. After insertion, the caret will be inside the new table, so any subsequent TableAuthoringService calls will operate on it.

private void InsertNewTableButton_Click(object sender, RoutedEventArgs e)
{
    const string tableHtml =
        "<table border=\"1\" cellpadding=\"4\" cellspacing=\"0\">" +
        "<tr><td>A1</td><td>B1</td><td>C1</td></tr>" +
        "<tr><td>A2</td><td>B2</td><td>C2</td></tr>" +
        "<tr><td>A3</td><td>B3</td><td>C3</td></tr>" +
        "</table>";

    Editor.Content.InsertHtml(tableHtml);
}
WPF HTML Editor showing a freshly-inserted 3x3 table created by calling Editor.Content.InsertHtml with a table HTML snippet.

Modifying structure: rows, columns, and merge

Once the caret is inside a table, the row and column methods on ITableAuthoringService let you mutate it without rebuilding the markup. Position-taking methods accept an InsertPositions enum that maps to "before the current row/column" or "after the current row/column":

using SpiceLogic.HtmlEditor.Abstractions;
using SpiceLogic.HtmlEditor.Abstractions.Services.ElementAuthoring;

ITableAuthoringService tables = Editor.Content.TableAuthoringService;

tables.InsertRow(InsertPositions.After);
tables.InsertColumn(InsertPositions.Before);
tables.DeleteRow();
tables.DeleteColumn();

if (tables.CanMergeCells())
    tables.MergeSelectedCells();
WPF HTML Editor demonstrating InsertRow After and InsertColumn Before transforming a 3x3 table into a 4x4 table with the new column on the left and the new row at the bottom.Before-and-after view of MergeSelectedCells in the WPF HTML Editor: two adjacent selected cells in the top row collapse into a single merged cell after the call.

Cell-merging is gated by EnableTableCellMerging on the service (defaults are controlled by the editor's options). When merging is disabled or the current selection cannot be merged (for example, the selected cells do not form a rectangle), CanMergeCells returns false and you should disable your merge button accordingly.

Two helpers are available for tables that should look invisible at runtime but still be visible to the author at design time: ShowGuidelinesForTablesWithZeroBorder and HideGuidelinesOfTablesWithZeroBorder toggle dotted guideline overlays for any table whose border attribute is zero. Use IsTableBorderZero to check the state of a specific IHTMLElement reference.

Cell-level access

For per-cell formatting use the TableCellElement entity. It exposes BgColor, Width/WidthUnit, Height/HeightUnit, HorizontalAlign, VerticalAlign, NoWrap, the four BorderWidth* properties, BorderColor, BorderStyle, and CssClassName. Set OverrideSettingsToAllCells to true when you want a single value — for example a uniform background colour — pushed down to every cell in the parent <table>. To bulk-paint just the currently-selected cells' background, set SelectedCellBackgroundColor on the service.

using System.Drawing;

Editor.Content.TableAuthoringService.SelectedCellBackgroundColor = Color.LightYellow;

To inspect the snapshot of every cell in the current table (text, span, header flag, inline style), use the TableData rows materialized by TableElement (Value, RowSpan, ColSpan, IsHeader, Style). This gives a stable, plain-data view you can serialize, diff, or feed into a templating step before writing the document back.

Selected cells in a WPF HTML Editor table painted light yellow after assigning Color.LightYellow to TableAuthoringService.SelectedCellBackgroundColor.

Full example

The following window inserts a 3x3 table on Loaded, then offers two buttons: one adds a row below the active row, the other merges whatever cells are currently selected, gated by CanMergeCells:

using System.Windows;
using SpiceLogic.HtmlEditor.Abstractions;
using SpiceLogic.HtmlEditor.Abstractions.Services.ElementAuthoring;

namespace WpfTableAuthoringDemo;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += OnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        Editor.Content.InsertHtml(
            "<table border=\"1\" cellpadding=\"4\">" +
            "<tr><td>A</td><td>B</td><td>C</td></tr>" +
            "<tr><td>1</td><td>2</td><td>3</td></tr>" +
            "<tr><td>X</td><td>Y</td><td>Z</td></tr>" +
            "</table>");
    }

    private void AddRowBelow_Click(object sender, RoutedEventArgs e)
    {
        Editor.Content.TableAuthoringService.InsertRow(InsertPositions.After);
    }

    private void MergeCells_Click(object sender, RoutedEventArgs e)
    {
        ITableAuthoringService tables = Editor.Content.TableAuthoringService;
        if (tables.CanMergeCells())
            tables.MergeSelectedCells();
    }
}

Wire AddRowBelow_Click and MergeCells_Click to two ordinary Button elements alongside the editor in your XAML, and you have a small custom table-editing surface that delegates the heavy DOM work to TableAuthoringService.

Last updated on May 12, 2026