Sections, Pages, Headers, and Footers
A report with a landscape page for a wide table, a letterhead on the first page and "Page 3 of 12" at the bottom of every page is built from sections, headers and footers. The TextControl on your form shows them as the user sees them on paper, and your code creates and changes them through three collections: the sections of the document, the headers and footers of each section, and the formatted pages.
The examples are button handlers in Form1.cs of the application from Create Your First Windows Forms Application, with a TextControl named textControl1; the samples of the pages need using System.IO; and using TXTextControl; in Form1.cs. The page size and margins of a new document are described in Creating Documents.
Insert a Section in Landscape Orientation
SectionCollection.Add inserts a section break at the input position, and Section.Format sets the page settings of a section, such as Landscape. Insert a Section Break From Code inserts a landscape section, and Format a Section lists the properties of SectionFormat and their units. The sections after a landscape section inherit the orientation, so set Landscape back to false where portrait pages continue.
Give a Section Its Own Header
Connected headers always have the same content. To give a section a header with other text, get the header from the section and set ConnectedToPrevious to false before you change it:
private void chapterHeadersButton_Click(object sender, EventArgs e)
{
textControl1.Text = "";
textControl1.Selection.Text = "Chapter 1\r\nThe first chapter.";
textControl1.Sections.Add(TXTextControl.SectionBreakKind.BeginAtNewPage);
textControl1.Selection.Text = "Chapter 2\r\nThe second chapter.";
// Section 2 receives a header that is connected to section 1
textControl1.HeadersAndFooters.Add(TXTextControl.HeaderFooterType.Header);
TXTextControl.HeaderFooter header1 = textControl1.Sections[1].HeadersAndFooters.GetItem(TXTextControl.HeaderFooterType.Header);
TXTextControl.HeaderFooter header2 = textControl1.Sections[2].HeadersAndFooters.GetItem(TXTextControl.HeaderFooterType.Header);
header2.ConnectedToPrevious = false;
header1.Selection.Text = "Chapter 1";
header2.Selection.Text = "Chapter 2";
// Restart the page numbers in section 2
textControl1.Sections[2].Format.RestartPageNumbering = true;
}
As soon as a document has more than one section, get headers and footers through Section.HeadersAndFooters: through the document's collection, most collections of a HeaderFooter are then null. RestartPageNumbering starts the page numbers of a section again, with the number that the StartNumber of the page number field sets.
Set Page Columns and Page Borders
Newsletters and brochures set text in page columns, and a section decides how many. A section that begins on a new line changes the number of columns without a page break, so a heading can span the page while the text below it runs in two columns:

The following handler writes a heading, starts a two-column section below it on the same page and draws a border around the pages:
private void columnsButton_Click(object sender, EventArgs e)
{
textControl1.Text = "";
textControl1.Selection.Text = "Newsletter\r\n";
// A new section on the same page, in two columns 0.5 inch apart
textControl1.Sections.Add(TXTextControl.SectionBreakKind.BeginAtNewLine);
TXTextControl.SectionFormat twoColumns = new TXTextControl.SectionFormat(2, 720);
twoColumns.ColumnLineWidth = 10;
twoColumns.ColumnLineColor = System.Drawing.Color.Gray;
textControl1.Sections.GetItem().Format = twoColumns;
textControl1.Selection.Text = "The text of this section flows from the first column into the second.";
// A border 0.5 inch from the edge of each page, except the first page of the section
TXTextControl.PageBorder border = new TXTextControl.PageBorder(20, 720, System.Drawing.Color.DarkBlue);
border.OmitFirstPage = true;
textControl1.Sections[1].Format.PageBorder = border;
}
The code works as follows:
- The SectionFormat constructor with a number of columns and a distance in twips creates columns of equal width, which TX Text Control calculates from the page size. Another overload takes an array of column widths and an array of distances, for columns of different widths. Columns is 1 by default, and ColumnWidths and ColumnDistances are read only: new widths and distances need a new
SectionFormat. - ColumnLineWidth draws a line between the columns, in twips; 0, the default, draws none. ColumnLineColor is its color.
- Assigning the object to Section.Format formats the section with the input position, which
Sections.GetItem()returns. - The PageBorder constructor takes the line width in twips, the distance from the edge of the page and the color of all four lines. OmitFirstPage leaves out the first page of the section, and
FirstPageOnlydraws the border on that page alone. With MeasureFromText set totrue, the distances are measured from the text instead of the edge of the page. SectionFormat.PageBorder assigns the border to a section.
A PageBorder can also set each line on its own, with properties such as TopLineWidth, LeftLineColor and BottomDistance.
Columns of Different Widths
For a narrow column beside a wide one, such as a sidebar next to the main text, the second overload of the constructor takes one width per column and one distance per gap, in twips. The widths and distances must add up to no more than the text area of the page, which is the page width less the left and the right margin; a larger sum throws an exception. The following method starts a two-column section at the input position with a first column of a given width and gives the second column the rest of the text area:
private void InsertColumns(int firstColumnWidth, int columnDistance)
{
textControl1.Sections.Add(TXTextControl.SectionBreakKind.BeginAtNewLine);
TXTextControl.SectionFormat format = textControl1.Sections.GetItem().Format;
// PageSize is always given in portrait orientation
double pageWidth = format.Landscape ? format.PageSize.Height : format.PageSize.Width;
double textAreaWidth = (pageWidth - format.PageMargins.Left - format.PageMargins.Right) * TwipsPerPageUnit();
int secondColumnWidth = (int)textAreaWidth - firstColumnWidth - columnDistance;
textControl1.Sections.GetItem().Format = new TXTextControl.SectionFormat(
2,
new int[] { firstColumnWidth, secondColumnWidth },
new int[] { columnDistance });
}
// Twips per unit of TextControl.PageUnit
private double TwipsPerPageUnit()
{
switch (textControl1.PageUnit.ToString())
{
case "Millimeter": return 1440 / 25.4;
case "StandardWPF": return 1440 / 96.0;
case "Twips": return 1;
default: return 14.4; // CentiInch, 1/100 inch
}
}
InsertColumns(2880, 360) creates a first column of two inches and a gap of a quarter inch. The code works as follows:
- Sections.Add with
BeginAtNewLinestarts the section on the same page, andSections.GetItem()returns it, because the input position is now inside it. - PageSize and PageMargins are in the unit of PageUnit, while column widths and distances are in twips.
TwipsPerPageUnitconverts with the factor of the unit that is set: millimeters, 1/100 inch, 1/96 inch or twips. - PageSize.Width and PageSize.Height always describe the page in portrait orientation, so a section with Landscape set uses the height as the width of its text area.
- The width array holds exactly one element per column, and the distance array one element less. This constructor sets
EqualColumnWidthtofalse.
Remove All Section Breaks
A document from Word can consist of many sections, each with its own page format and headers. To give it one layout, remove the section breaks. The following handler loads a document and removes the break at the start of each section after the first, as the blog article How to Delete All Section Breaks in a DOCX Document using C# shows:
private void removeSectionsButton_Click(object sender, EventArgs e)
{
textControl1.Load("report.docx", TXTextControl.StreamType.WordprocessingML);
// Sections.Count goes down with each removed break
for (int section = 2; section <= textControl1.Sections.Count;)
{
// The input position in front of the break that starts the section
textControl1.Select(textControl1.Sections[section].Start - 2, 0);
// Insert a paragraph, select its end and delete it together with the break
textControl1.Selection.Text = "\r\n";
textControl1.Selection.Length = 1;
textControl1.Selection.Text = "";
}
}
Section.Start is the one-based position of the first character of a section. The loop doesn't count up, because every removed break lowers Sections.Count until one section is left. The document then has one page orientation, one set of margins and one set of headers and footers.
Work With the Pages of a Document
TextControl.Pages is the number of pages, and GetPages returns a PageCollection with a Page object for each page. It returns the collection only in page view, when ViewMode is ViewMode.PageView; in the other view modes it returns null. A page has read-only properties such as Number, Section, Start and Length, and Header and Footer return the header and footer the page shows. InputPosition.Page is the page the user is on, in page view. Page.Select selects the text of a page, without its header and footer, so that textControl1.Selection.Text returns it.
Render a Page as an Image
Page.GetImage renders a page as an image, for thumbnails, previews or an export. The following handler shows a thumbnail of the first page in a PictureBox named thumbnailBox:
private void thumbnailButton_Click(object sender, EventArgs e)
{
TXTextControl.PageCollection pages = textControl1.GetPages();
// 20 percent of the original size, with headers, footers and main text
System.Drawing.Bitmap thumbnail = pages[1].GetImage(20, TXTextControl.Page.PageContent.All);
thumbnailBox.Image?.Dispose();
thumbnailBox.Image = thumbnail;
MessageBox.Show($"{textControl1.Pages} pages in {textControl1.Sections.Count} sections");
}
The code works as follows:
- The indexer of the
PageCollectiontakes a page number from 1. GetImage(int, PageContent)returns a bitmap in screen resolution with the zoom factor in percent. Other overloads return an enhanced metafile or a string in SVG format. The values of PageContent, written asPage.PageContentin code, select the parts of the page:Background,HeadersAndFootersandMainText, andAllfor these three.- The previous image of the
PictureBoxis disposed before the new one is assigned.
To export the pages as SVG files, which stay sharp at any size, call the overload with a maximum resolution for embedded pixel images:
private void saveSvgButton_Click(object sender, EventArgs e)
{
foreach (Page page in textControl1.GetPages())
{
// Images of the page are embedded as PNG with at most 150 dpi
string svg = page.GetImage(TXTextControl.Page.PageContent.All, 150);
File.WriteAllText($"page-{page.Number}.svg", svg);
}
}
The blog article Convert MS Word DOCX to SVG in .NET C# shows what the SVG contains.
Save the Pages as a Multipage TIFF
The pages of a document can also be saved as one TIFF file with a frame for each page. GetImage renders each page as a bitmap, and the TIFF encoder of System.Drawing adds the bitmaps to the file as frames. The handler needs using System.Drawing;, using System.Drawing.Imaging; and using System.Linq; at the top of Form1.cs:
private void saveTiffButton_Click(object sender, EventArgs e)
{
// The TIFF encoder of System.Drawing
ImageCodecInfo tiffCodec = ImageCodecInfo.GetImageEncoders()
.First(codec => codec.MimeType == "image/tiff");
using (EncoderParameters parameters = new EncoderParameters(2))
{
parameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression,
(long)EncoderValue.CompressionLZW);
Bitmap firstPage = null;
foreach (Page page in textControl1.GetPages())
{
// 100 percent, with headers, footers and main text
Bitmap bitmap = page.GetImage(100, TXTextControl.Page.PageContent.All);
if (firstPage == null)
{
// The first page creates the file
firstPage = bitmap;
parameters.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag,
(long)EncoderValue.MultiFrame);
firstPage.Save("document.tif", tiffCodec, parameters);
}
else
{
// Each further page is a new frame of the file
parameters.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag,
(long)EncoderValue.FrameDimensionPage);
firstPage.SaveAdd(bitmap, parameters);
bitmap.Dispose();
}
}
if (firstPage != null)
{
// Close the file
parameters.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag,
(long)EncoderValue.Flush);
firstPage.SaveAdd(parameters);
firstPage.Dispose();
}
}
}
The first bitmap is saved with EncoderValue.MultiFrame and creates the file; every further page is appended with SaveAdd and FrameDimensionPage, and Flush closes the file. The bitmaps are in screen resolution, scaled by the zoom factor in percent. Encoder is written with its namespace because System.Text has an Encoder class as well.
Remove Empty Pages
Documents assembled from several parts, or edited by users after they were created, can end up with pages that hold nothing but a page break or a section break. Such a page has a Length of 1, because the length counts the break at the end of the page. The following handler deletes these breaks:
private void removeEmptyPagesButton_Click(object sender, EventArgs e)
{
PageCollection pages = textControl1.GetPages();
// From the last page to the first, so that the positions of earlier pages stay valid
for (int number = pages.Count; number >= 1; number--)
{
Page page = pages[number];
// A page that holds nothing but its page or section break
if (page.Length == 1)
{
// Page.Start counts from 1, text positions from 0
textControl1.Select(page.Start - 1, 1);
textControl1.Selection.Text = "";
}
}
}
Page.Start is the one-based number of the first character of the page, so Start - 1 is the text position of the break. Page.Select doesn't help here, because it leaves out the page break characters.
Next Steps
- Headers and Footers: how users add headers and footers in the editor.
- Sections and Page Layout: page setup, columns and section breaks in the user interface.
- Fields and Subtext Parts: other fields of a document.
- Printing: print the pages of a document.