[C# Helper]
Index Books FAQ Contact About Rod
[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

[C# 24-Hour Trainer]

[C# 5.0 Programmer's Reference]

[MCSD Certification Toolkit (Exam 70-483): Programming in C#]

Title: Insert a table into a RichTextBox in C#

[Insert a table into a RichTextBox in C#]

Someone recently asked me if I could write a program to insert a table in a RichTextBox control and give it all of the features of a table in Microsoft Word. The short answer is "no." The RichTextBox control just doesn't provide the support that you would need. You might be able to coerce it into a program into doing this, but I suspect it would be easier to build your own table editor from scratch rather than trying to cram one into a RichTextBox.

It would also be better to consider embedding another tool such as Word in the application. Or just using Word.

However, you can add a table to a RichTextBox with a little effort. You just won't be able to interactively resize rows and columns, change cell alignments, add and remove cell borders, and everything else you would expect from a table editor.

Rich Text Format

One problem with Rich Text Format (RTF) is that it is, well, rich. It contains a lot of features so deciding which features to implement is difficult. To make matters worse, the RichTextBox control doesn't seem to support all of the RTF features. Or at least I was unable to figure out how to use them.

A RTF table isn't really a table; it's just a sequence of rows. The syntax that I'm going to use for a row begins with \trowd to indicate that a row is starting.

The next entry is \trgaph followed by a measurement in twips. This measurement gives half of the amount of minimum space that you want between cells. A twip is 1440th of an inch. For example, \trgaph180 means there each cell should have a 180 twip or 180 / 1440 = 1/8 inch internal margin. Two adjacent cells each have an internal margin, so the contents of the cells are at least 1/4 inch apart. (All of those measurements are approximate, depending on your screen's calibration.)

After the \trgaph element, the program includes a list of cell reaches. These are the distances from the left margin to the cell's right edge, again in twips. Note that this is the text's margin not the cell's, so these are not cell widths. Each position consists of \cellx followed by the position. For example, \cellx1440 means the cell's right edge is 1 inch from the left margin.

At this point, the program includes the data that should go inside the row's cells. Each cell's entry is written as \pard\intbl{<contents>}\cell. Here <contents> is whatever you want to put in the cell.

The row's definition ends with \row.

The RtfTable Class

To make creating a table a bit easier, I made an RtfTable class. The following code shows the class's fields and constructor.

public int InternalMargin = 180; public int NumRows, NumCols; public int[] ColumnWidths = null; public string[,] Contents = null; public RtfTable(int num_rows, int num_cols, int internal_margin) { NumRows = num_rows; NumCols = num_cols; InternalMargin = internal_margin; ColumnWidths = Enumerable.Repeat(1440, NumCols).ToArray(); Contents = new string[NumRows, NumCols]; for (int r = 0; r < NumRows; r++) for (int c = 0; c < NumCols; c++) Contents[r, c] = ""; }

The InternalMargin value is the value used by the RTF \trgaph command. Obviously the NumRows and NumCols values give the numbers of rows and columns in the table.

The ColumnWidths array holds the widths of the columns in twips. (These are widths, not reaches as described earlier.)

Finally the two-dimensional Contents array holds the table's contents.

The constructor saves the NumRows, NumCols, and InternalMargin values. It then uses the Enumerable class's Repeat method to make a list containing the value 1440 once for each column. The code invokes the list's ToArray method and saves the result in the ColumnWidths array.

The code then makes the Contents array, giving it the correct numbers of rows and columns. It finishes by looping through the rows and columns setting each cell's value to an empty string.

The following method lets you set the table's column widths.

public void SetColumnWidths(params int[] widths) { for (int c = 0; c < NumCols; c++) ColumnWidths[c] = widths[c]; }

This method's widths parameter uses the params keyword so it is a parameter list and can take any number of values. For example, the statement table.SetColumnWidths(1440, 770, 1440) passes the method three values.

The method simply loops through the widths values and copies them into the ColumnWidths array.

If the widths parameter contains fewer values than the number of table columns, then the last values in the ColumnWidths array remain unchanged. If the method receives too many values, the method crashes. (You can add code to check for that if you like.)

The last part of the following RtfTable class is the ToString method, which returns RTF codes to build the table.

public override string ToString() { StringBuilder sb = new StringBuilder(); string column_widths_string = ColumnWidthsString(); for (int r = 0; r < NumRows; r++) { // Start the row. sb.Append(@"\trowd"); sb.Append(@"\trgaph" + InternalMargin.ToString()); // Column widths. sb.Append(column_widths_string); // Column contents. for (int c = 0; c < NumCols; c++) { sb.Append(@"\pard\intbl{" + Contents[r, c].Replace(@"\", @"\\") + @"}\cell"); } // End the row. sb.Append(@"\row"); } return sb.ToString(); }

The method first creates a StringBuilder object. Next the method calls the ColumnWidthsString method to get the column width commands and saves the result in a string. (I'll describe that method shortly.)

The code then loops through the table's rows. For each row, the program adds the \trowd command to the StringBuilder. It then adds the \trgaph command followed by the InternalMargin value. It then adds the column width string.

Next the code loops through the row's columns and adds the value for this row and column to the StringBuilder. It begins each entry with \pard\intbl\{ and ends each entry with }\cell.

When it is done adding the row's values, the method ends the row with \row.

After it has processed every row, the ToString method returns the text stored in the StringBuilder.

The following code shows the ColumnWidthsString method.

private string ColumnWidthsString() { StringBuilder sb = new StringBuilder(); int total = 0; for (int c = 0; c < NumCols; c++) { total += ColumnWidths[c]; sb.Append(@"\cellx" + total.ToString()); } return sb.ToString(); }

This method creates its own StringBuilder. It uses the variable total to keep track of total distance from the left margin to the right edge of the next column. After initializing total to 0, the code loops through the ColumnWidths array.

For each column, the code adds that column's width to total. It then \cellx following by the new value in total to the StringBuilder.

After it has finished processing all of the columns, the method returns the string stored in the StringBuilder.

Using RtfTable

Having built an RTF string that defines a table, you need to insert it into the RichTextBox control. One way to do that is to remove the } at the end of the control's RTF text, append the table's code, and replace the } at the end. That works but it forces you to place the table at the end of the control's contents.

Another approach would be to replace the control's SelectedRtf value with the table's code. Unfortunately I could not get that to work. If you figure out how to make it work, please post a note in the comments below.

A final technique would be to search for text within the control's contents and insert the table code there. This example searches for the string "@@@" and replaces it with the table code.

The following code shows how the program inserts a sample table.

private void btnInsert_Click(object sender, EventArgs e) { RtfTable table = new RtfTable(4, 3, 120); table.SetColumnWidths(720, 720, 720); for (int r = 0; r < 4; r++) for (int c = 0; c < 3; c++) table.Contents[r, c] = "(" + r.ToString() + ", " + c.ToString() + ")"; // Insert the table at @@@. rtbTable.Rtf = rtbTable.Rtf.Replace("@@@", table.ToString()); // Insert the table at the end. //rtbTable.Rtf = // rtbTable.Rtf.Trim().TrimEnd('}') + // table.ToString() + // "}"; }

This code first creates an RtfTable object and initializes it to hold four rows and three columns with an internal margin of 180 twips. The code then uses the SetColumnWidths method to make every column 720 twips (1/2 inch) wide.

Next the program uses two nested loops to set the table's contents. For example, the cell at row 3 column 1 contains the string (3, 1).

Having prepared the RtfTable object, the program gets the RichTextBox control's RTF code and replaces the string @@@ with the table's RTF code.

If you look at the code, you'll see that it also contains a commented out statement that puts the table's code at the end instead of replacing the string @@@.

Conclusion

This example creates a simple table and inserts it into the control. RTF supports many other features such as cell borders, different fonts, foreground and background colors, and more. The example doesn't handle them.

This example also doesn't provide extra features such as interactive column and row resizing. You might be able to add some of those features to the program, but it's probably not worth the effort. If you need such advanced features, then you're probably better off using Microsoft Word or some other word processor to create your documents.

Download the example to experiment with it and to see additional details.

© 2009-2023 Rocky Mountain Computer Consulting, Inc. All rights reserved.