TX Text Control .NET for WPF

Tables of Contents

Topic

How does TX Text Control know what goes into a table of contents? Each heading carries a structure level, and the table of contents collects all paragraphs within a range of levels, with page numbers and links if you want them. In a WPF application, you assign the levels, insert and update tables of contents from code or let users do it with the References tab and a dialog box.

The examples use a window with a TextControl named textControl1, as in Create Your First WPF Application, and MainWindow.xaml.cs needs using TXTextControl;. Tables of contents were introduced in version 29.0 and are part of the Enterprise edition; see Editions and Feature Availability.

Structure Levels and Entries

The structure level of a paragraph is a number from 0 to 10. The default, 0, stands for body text; the levels 1 to 10 mark headings, for example 1 for chapters and 2 for sections. A table of contents has a minimum and a maximum level. When it is inserted, TX Text Control creates one entry for each paragraph whose level is in this range, in the order of the text:

A generated table of contents with a title, entries of two levels, dot leaders and right-aligned page numbers.

The entries keep the font and paragraph attributes of their headings. In addition, TX Text Control creates a formatting style for each level, named TOC_Title, TOC_Level1, TOC_Level2 and so on, and changing these styles changes the look of the table of contents. A document can contain several tables of contents. None of them changes by itself when the document changes: after headings are added, removed or moved, the table of contents has to be updated.

The API consists of TableOfContents, TableOfContentsCollection, which WPF.TextControl.TablesOfContents returns, and four events:

Class diagram of the table of contents API: the TablesOfContents property, the TableOfContentsDialog method and the events of TextControl and WPF.TextControl, TableOfContentsCollection, TableOfContents and TableOfContentsEventArgs.

Let Users Insert Tables of Contents

WPF.RibbonReferencesTab is a ribbon tab with three groups for this feature: one inserts, deletes, updates and modifies a table of contents, one sets the properties of the current table of contents, such as its levels, title, links and page numbers, and one assigns structure levels to paragraph styles or adds a paragraph with a level. Put the tab into a Ribbon and connect it with its name:

<Ribbon DockPanel.Dock="Top">
    <WPF:RibbonReferencesTab x:Name="ribbonReferencesTab" />
</Ribbon>

<WPF:TextControl x:Name="textControl1"
                RibbonReferencesTab="ribbonReferencesTab"
                Loaded="textControl1_Loaded"
                DocumentLinkClicked="textControl1_DocumentLinkClicked" />

WPF is the prefix of xmlns:WPF="clr-namespace:TXTextControl.WPF;assembly=TXTextControl.WPF" on the Window element. Connecting Ribbon Tabs in XAML shows a complete window with a ribbon.

Without a ribbon, WPF.TextControl.TableOfContentsDialog opens the built-in dialog box. If the input position is in a table of contents, the dialog box changes it; otherwise, it inserts a new one with the settings the user chooses. In WPF, it returns a WPF.DialogResult value:

private void TocDialog_Click(object sender, RoutedEventArgs e)
{
    textControl1.TableOfContentsDialog();
}

Assign Structure Levels

ParagraphFormat.StructureLevel holds the level. The most practical place for it is a paragraph style, so that every paragraph with the style is an entry. The Loaded handler below creates a style for chapters and one for sections, and the two button handlers apply a style or a level to the selected paragraphs:

private void textControl1_Loaded(object sender, RoutedEventArgs e)
{
    // Paragraphs with these styles become entries of level 1 and 2
    ParagraphStyle chapter = new ParagraphStyle("Chapter");
    chapter.Bold = true;
    chapter.ParagraphFormat.StructureLevel = 1;
    textControl1.ParagraphStyles.Add(chapter);

    ParagraphStyle section = new ParagraphStyle("Section");
    section.Bold = true;
    section.ParagraphFormat.StructureLevel = 2;
    textControl1.ParagraphStyles.Add(section);
}

private void Chapter_Click(object sender, RoutedEventArgs e)
{
    textControl1.Selection.FormattingStyle = "Chapter";
}

private void Level2_Click(object sender, RoutedEventArgs e)
{
    // 0 is body text, 1 to 10 are heading levels
    textControl1.Selection.ParagraphFormat.StructureLevel = 2;
}

Add buttons with Click="Chapter_Click" and Click="Level2_Click" to the window to try them.

Insert a Table of Contents

private void InsertToc_Click(object sender, RoutedEventArgs e)
{
    // Entries for all paragraphs with the structure levels 1 to 2
    TableOfContents tableOfContents = new TableOfContents(1, 2)
    {
        Title = "Contents",
        HasPageNumbers = true,
        HasRightAlignedPageNumbers = true,
        HasLinks = true
    };

    // Insert the table of contents at the beginning of the document
    textControl1.Select(0, 0);
    TableOfContentsCollection.AddResult result = textControl1.TablesOfContents.Add(tableOfContents);

    if (result == TableOfContentsCollection.AddResult.ContentNotFound)
    {
        MessageBox.Show(this, "The document has no paragraphs with the structure level 1 or 2.");
    }
}

The code works as follows:

TablesOfContents belongs to the text part with the input focus. With the cursor in a header, a footer or a text frame, it returns the tables of contents of that part.

Update and Remove Tables of Contents

private void UpdateToc_Click(object sender, RoutedEventArgs e)
{
    foreach (TableOfContents tableOfContents in textControl1.TablesOfContents)
    {
        tableOfContents.Update();
    }
}

private void RemoveToc_Click(object sender, RoutedEventArgs e)
{
    // The table of contents at the input position, or null
    TableOfContents tableOfContents = textControl1.TablesOfContents.GetItem();

    if (tableOfContents != null)
    {
        textControl1.TablesOfContents.Remove(tableOfContents);
    }
}
  • Update regenerates the entries and page numbers. Call it after the document has changed and after you change the properties of an existing table of contents, such as MinimumStructureLevel.
  • GetItem returns the table of contents at the input position, or the one with a Name or ID you have assigned.
  • Remove deletes it together with its text and the document targets its links point to.

Documents created by MailMerge from a template with a table of contents need an update after the merge. The blog article Combining MailMerge and Table of Contents shows the call, and Mail Merge covers merging.

Handle Clicks on the Entries

The entries of a table of contents with HasLinks are DocumentLink objects that point to DocumentTarget objects at the headings. A CTRL+click on an entry scrolls to its heading without any code. The generated targets have names beginning with TOC_ and an AutoGenerationType of TableOfContents.

A DocumentLinkClicked handler for your own links, as on Fields, Hyperlinks, and Bookmarks, should leave these targets alone:

private void textControl1_DocumentLinkClicked(object sender, DocumentLinkEventArgs e)
{
    DocumentTarget target = e.DocumentLink.DocumentTarget;

    // TextControl scrolls to table of contents targets itself on CTRL+click
    if (target != null && target.AutoGenerationType != AutoGenerationType.TableOfContents)
    {
        target.ScrollTo();
    }
}

Table of Contents Events

The WPF TextControl raises TableOfContentsCreated, TableOfContentsDeleted, TableOfContentsEntered and TableOfContentsLeft. Connect them as attributes of WPF:TextControl in XAML. Their handlers receive a TableOfContentsEventArgs with the table of contents concerned, so that you can, for example, enable your own Update button only while the cursor is in a table of contents.

Tables of Contents in Other Formats

Tables of contents are loaded from and saved to DOC, DOCX and RTF documents, compatible with MS Word. When the document is exported as PDF, linked entries remain clickable. Loading and Saving Documents covers the formats.

Next Steps