TX Text Control .NET for Windows Forms

Tables and Calculations

Topic

Invoice line items, price lists and cash flow statements are tables, and the totals under them are calculations. In a Windows Forms application, your code fills a table from data, formats its cells, rows and columns, nests tables and reads the tables of a loaded document. Each step is one button handler for the TextControl of a form.

Create a Document Programmatically inserts a first table with a header row. Inserting tables with the ribbon, working with the table at the input position and calculating with formulas are described in Tables and Table Formulas. For a row per record of a data source in a template, use a merge block, as Merge Blocks shows, instead of a loop. Form1.cs needs these using directives:

using System;
using System.Drawing;
using System.Globalization;
using System.Text;
using System.Windows.Forms;
using TXTextControl;

How Tables Are Organized

Tables returns the tables of the text part with the input focus. A Table has the collections Rows, Columns and Cells, numbered from 1, and a table your code adds gets an ID by which GetItem returns it later. How Tables Are Organized explains these rules and the units.

Fill a Table From Data

The following handler writes the line items of an invoice into a new table, with a gray header row that repeats on each page:

private void invoiceTableButton_Click(object sender, EventArgs e)
{
    var lineItems = new[]
    {
        new { Description = "TX Text Control .NET for Windows Forms License", Quantity = 1, Price = 1999.00 },
        new { Description = "Priority Support - 12 Months", Quantity = 1, Price = 299.00 }
    };

    textControl1.ResetContents();
    textControl1.Selection.Text = "Invoice INV-2025-001\r\n\r\n";

    // A header row and one row for each item, with the ID 10
    textControl1.Tables.Add(lineItems.Length + 1, 3, 10);
    Table table = textControl1.Tables.GetItem(10);

    // One format for all cells: borders of 1 pt and space left and right of the text
    TableCellFormat cellFormat = new TableCellFormat()
    {
        LeftBorderWidth = 20,
        TopBorderWidth = 20,
        RightBorderWidth = 20,
        BottomBorderWidth = 20,
        LeftTextDistance = 100,
        RightTextDistance = 100
    };

    foreach (TableCell cell in table.Cells)
    {
        cell.CellFormat = cellFormat;
    }

    // Column headings in row 1, repeated on every page
    string[] headings = { "Description", "Quantity", "Price" };

    for (int column = 1; column <= headings.Length; column++)
    {
        table.Cells.GetItem(1, column).Text = headings[column - 1];
    }

    TableRow headerRow = table.Rows.GetItem(1);
    headerRow.IsHeader = true;
    headerRow.CellFormat = new TableCellFormat() { BackColor = Color.LightGray };

    // One row for each item, starting in row 2
    for (int i = 0; i < lineItems.Length; i++)
    {
        int row = i + 2;
        table.Cells.GetItem(row, 1).Text = lineItems[i].Description;
        table.Cells.GetItem(row, 2).Text = lineItems[i].Quantity.ToString(CultureInfo.InvariantCulture);
        table.Cells.GetItem(row, 3).Text = lineItems[i].Price.ToString("0.00", CultureInfo.InvariantCulture);
    }

    // The first column is 3.5 inches wide
    table.Columns.GetItem(1).Width = (int)(3.5 * 1440);
}

The code works as follows:

Format Cells, Rows and Columns

The properties of a TableCellFormat:

PropertySets
BackColorThe background color
LeftBorderWidth, TopBorderWidth, RightBorderWidth, BottomBorderWidthThe width of each border in twips
LeftBorderColor, TopBorderColor, RightBorderColor, BottomBorderColorThe color of each border
LeftTextDistance, TopTextDistance, RightTextDistance, BottomTextDistanceThe distance between the border and the text in twips
VerticalAlignmentTop, Center or Bottom
TextType, NumberFormatWhether the text of the cell is a number, and how numbers are displayed

TableRow.MinimumHeight is the height a row has at least; it grows when its text needs more room. With TableRow.AllowPageBreak set to false, a row isn't split across two pages. To format the text of a cell, select the cell with TableCell.Select and set the character and paragraph attributes of the selection:

Table table = textControl1.Tables.GetItem(10);

// Bold text in the first cell
table.Cells.GetItem(1, 1).Select();
textControl1.Selection.Bold = true;

Work With the Table at the Input Position

An editor often has buttons that act on the table the user is working in: Tables.GetItem() without a parameter returns it, and Rows.Add, Columns.Add and MergeCells work at the input position or on the selected cells. Work With the Table at the Input Position shows these methods and how to move the input position into a cell.

Calculate With Formulas

A cell can calculate its text from other cells of the same table, like a spreadsheet, with a formula in TableCell.Formula such as SUM(C2:C4). Calculate With Formulas adds a total row to a table like the one of the first example, and the sections below it describe the formula syntax and the number formats. A cell of the type TextType.Number reads its text with the period as decimal separator, whatever the language of the system, which is why the first example writes the prices with CultureInfo.InvariantCulture.

Formula Functions lists the 85 functions with their arguments, defaults and limits, and TableCollection.SupportedFormulaFunctions returns their names at runtime. Excel formulas in tables are part of the Enterprise and Professional editions; see Editions and Feature Availability.

Nest Tables

To put a table into a cell of another table, place the input position in the cell without selecting its text and call Tables.Add:

private void nestButton_Click(object sender, EventArgs e)
{
    Table outerTable = textControl1.Tables.GetItem(10);

    // Put the input position into cell (1, 1) without selecting text
    outerTable.Cells.GetItem(1, 1).Select();
    textControl1.Selection.Length = 0;

    textControl1.Tables.Add(2, 2, 11);
    Table nestedTable = outerTable.NestedTables.GetItem(11);

    MessageBox.Show($"Nesting level {nestedTable.NestedLevel}, outer table {nestedTable.OuterTable.ID}");
}

Table.NestedTables contains the tables inside a table. NestedLevel is 1 for a table that isn't nested, and OuterTable returns the table around a nested one.

Read and Remove Tables

The tables of a loaded document are in the same collections. The following handler lists the tables of the main text with the text of each cell:

private void readTablesButton_Click(object sender, EventArgs e)
{
    StringBuilder report = new StringBuilder();
    MainText mainText = textControl1.TextParts.GetMainText();

    foreach (Table table in mainText.Tables)
    {
        report.AppendLine($"Table {table.ID}: {table.Rows.Count} rows, {table.Columns.Count} columns");

        foreach (TableCell cell in table.Cells)
        {
            report.AppendLine($"  ({cell.Row}, {cell.Column}): {cell.Text}");
        }
    }

    MessageBox.Show(report.ToString());
}

TableBaseCollection.Remove with an ID removes that table, for example textControl1.Tables.Remove(10). TableCollection.Remove without a parameter removes the table at the input position, and Clear removes all tables of the collection.

Next Steps