Table Authoring Service

SpiceLogic WinForms HTML Editor ITableAuthoringService IntelliSense listing InsertRow, InsertColumn, MergeSelectedCells and CanMergeCells methods in Visual Studio

The editor surfaces programmatic table editing through editor.Content.TableAuthoringService (type ITableAuthoringService). It is the headless equivalent of the built-in table dialog: insert rows or columns relative to the active cell, delete rows or columns, merge selected cells, and toggle the visual guidelines that help users design tables with border="0". It also exposes a TableManipulator the state-query layer uses to decide whether merging is currently legal.

For inspecting an existing table's structure you read the TableElement projection returned by editor.StateQuery.GetCurrentTable(). For writing structural changes you call methods on TableAuthoringService. Together these two surfaces cover the majority of host-driven table automation use cases - templated reports, mass cell-style updates, scripted layout edits - without ever opening the 2-Way Table Dialog.

Getting the active table

SpiceLogic WinForms HTML Editor StateQuery.GetCurrentTable returning a TableElement projection inspected in the Visual Studio debugger watch window

Tables are addressed implicitly through the current caret. Place the caret (or rely on the user's caret) inside any descendant of a <table>, then ask StateQuery for the table projection. GetCurrentTable walks up the DOM from the caret, finds the nearest enclosing <table>, and hands you an editable TableElement snapshot. IsActiveOrAncestorElementTable is the safe pre-check.

if (htmlEditor1.StateQuery.IsActiveOrAncestorElementTable())
{
    TableElement table = htmlEditor1.StateQuery.GetCurrentTable();
    Debug.WriteLine($"{table.Rows} rows x {table.Columns} cols, border={table.BorderWidth}");
}

Mutating the TableElement properties (width, padding, caption, border style, etc.) and then calling UpdateTheActiveHtmlElement on it writes those edits back to the live document - this is the same two-way binding the table dialog uses, exposed for programmatic use.

Inserting a table

Note: ITableAuthoringService manipulates tables that already exist in the document; it does not create new ones. To insert a fresh table from code, build the HTML yourself and call editor.Content.InsertHtml. The TableAuthoringService methods then operate on the table once the caret is inside it.

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>";

htmlEditor1.Content.InsertHtml(tableHtml, keepSelected: false);

Modifying structure

SpiceLogic WinForms HTML Editor InsertRow and InsertColumn calls growing a 3x3 table at runtime in the design view of the host application

The following methods operate on whichever cell currently contains the caret. They are the methods bound to the toolbar buttons under the Table menu, so calling them from code is equivalent to the user clicking those buttons.

InsertRow

InsertRow(InsertPositions position) inserts a new row above or below the active row. The InsertPositions enum has two values: Before and After.

htmlEditor1.Content.TableAuthoringService.InsertRow(InsertPositions.After);

DeleteRow

DeleteRow() removes the row containing the active cell. When the deleted row was the only row, the entire table is removed.

htmlEditor1.Content.TableAuthoringService.DeleteRow();

InsertColumn

InsertColumn(InsertPositions position) inserts a new column to the left of (Before) or right of (After) the active cell.

htmlEditor1.Content.TableAuthoringService.InsertColumn(InsertPositions.Before);

DeleteColumn

DeleteColumn() removes the column containing the active cell across every row in the table.

htmlEditor1.Content.TableAuthoringService.DeleteColumn();

MergeSelectedCells

SpiceLogic WinForms HTML Editor MergeSelectedCells before-and-after comparison showing two adjacent cells collapsed into one via the TableAuthoringService API

MergeSelectedCells() merges the cells the user has highlighted into a single cell. Before calling, check CanMergeCells() so you do not attempt a merge that the table's geometry would reject (the merge target must form a rectangular block of cells). The EnableTableCellMerging property gates the entire feature - leave it at its default true to allow merges.

if (htmlEditor1.Content.TableAuthoringService.CanMergeCells())
    htmlEditor1.Content.TableAuthoringService.MergeSelectedCells();

Zero-border guidelines

SpiceLogic WinForms HTML Editor zero-border table guideline overlay rendered at design time via ShowGuidelinesForTablesWithZeroBorder for layout authoring

Tables built with border="0" are invisible at design time, which makes them hard to author. ShowGuidelinesForTablesWithZeroBorder(bool inBackground) overlays a dashed guide on every zero-border table so the user can see structure while editing. HideGuidelinesOfTablesWithZeroBorder(bool inBackground) reverses it. IsTableBorderZero(IHTMLElement tableElement) reports whether a specific table element falls into this category. SelectedCellBackgroundColor controls the highlight colour used to render selected cells during multi-cell selection.

TableManipulator hooks

SpiceLogic WinForms HTML Editor GetTableManipulator returning the ITableManipulator instance used by StateQueryService CanMergeTableCells evaluation

GetTableManipulator() returns an ITableManipulator implementation used by StateQueryService to evaluate CanMergeTableCells(). ResetTableManipulator() drops the cached manipulator so the next query rebuilds it - call this if you swap out the underlying document or otherwise invalidate cell-selection state.

Cell-level access

Once you have a TableElement, the first cell is reachable through GetFirstCellElement(), which returns a TableCellElement. That projection exposes the writable cell properties you would expect: Width / WidthUnit, Height / HeightUnit, BgColor, BorderWidth, BorderColor, BorderStyle, HorizontalAlign, VerticalAlign, NoWrap, CssStyle, and CssClassName. Setting OverrideSettingsToAllCells = true before calling UpdateTheActiveHtmlElement() applies the same edits to every cell in the parent table - that is how the table dialog implements its "apply to all cells" checkbox.

The cell row/column geometry is exposed as TableData[] rows inside the TableElement projection. Each TableData entry carries the cell's Value (innerHTML), RowSpan, ColSpan, IsHeader flag, and inline Style text.

End-to-end example: build a 3x3 table, then merge two cells

The example below builds a 3x3 table from a string, inserts it at the caret, and (assuming the caret is then inside one of the new cells) shows the structural-edit calls. In a real host you would either set the caret programmatically or perform the structural edits inside an event handler that already has the caret in the right cell.

using SpiceLogic.HtmlEditor.Abstractions;
using SpiceLogic.HtmlEditor.Abstractions.Entities;

private void BuildAndEditTable_Click(object sender, EventArgs e)
{
    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>";

    htmlEditor1.Content.InsertHtml(tableHtml, keepSelected: false);

    // User (or programmatic caret) is now inside the table. Read the projection:
    if (htmlEditor1.StateQuery.IsActiveOrAncestorElementTable())
    {
        TableElement table = htmlEditor1.StateQuery.GetCurrentTable();
        Debug.WriteLine($"Inserted {table.Rows}x{table.Columns} table.");

        // Append a row to the bottom, prepend a column to the left:
        htmlEditor1.Content.TableAuthoringService.InsertRow(InsertPositions.After);
        htmlEditor1.Content.TableAuthoringService.InsertColumn(InsertPositions.Before);

        // Merge once the user multi-selects two adjacent cells:
        if (htmlEditor1.Content.TableAuthoringService.CanMergeCells())
            htmlEditor1.Content.TableAuthoringService.MergeSelectedCells();
    }
}

This pattern - create through InsertHtml, then manipulate through TableAuthoringService and TableElement - covers virtually every host-driven table workflow without forcing your users to interact with the built-in table dialog.

Last updated on May 12, 2026