Tables and Calculations
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:
- The size of the table comes from the data: one row per item plus the header row, and 3 columns.
- A TableCellFormat is assigned to every cell through TableCell.CellFormat. TableRow.CellFormat and TableColumn.CellFormat format a whole row or column.
- TableCellCollection.GetItem returns the cell in a row and a column, and TableCell.Text sets its text.
- TableRow.IsHeader makes the first row part of the table header, which is repeated at the top of each page when the table continues on the next page.
- TableColumn.Width sets the width of a column in twips.
Format Cells, Rows and Columns
The properties of a TableCellFormat:
| Property | Sets |
|---|---|
BackColor | The background color |
LeftBorderWidth, TopBorderWidth, RightBorderWidth, BottomBorderWidth | The width of each border in twips |
LeftBorderColor, TopBorderColor, RightBorderColor, BottomBorderColor | The color of each border |
LeftTextDistance, TopTextDistance, RightTextDistance, BottomTextDistance | The distance between the border and the text in twips |
| VerticalAlignment | Top, Center or Bottom |
| TextType, NumberFormat | Whether 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
- Merge Blocks: repeat table rows for each record of a data source.
- Tables and Table Formulas: tables and formulas from the user's side.
- Text, Paragraphs, and Stylesheets: format the text in table cells.
- Sections, Pages, Headers, and Footers: give a wide table a page in landscape orientation.