How-To Guide

This chapter contains a collection of how-to guides for specific typical tasks that can be realized with TX Text Control. In contrast to the shipped sample applications that are described in the different user's guides, this chapter describes the non-basic functionality of TX Text Control.

Creating Multipage TIFF Images

TX Text Control .NET 15.0 introduced the page rendering engine that allows you to export a metafile or a bitmap of each separate page. This enables developers to create thumbnails of the pages or to export images to view them in a browser. This sample shows how to create a multipage TIFF image from all pages of a document.

Two significant steps are required to create these images:

  • Create a TIFF image using the page rendering engine
  • Combine those images to a single TIFF image

First, it is required to iterate through all pages of TX Text Control to create separate TIFF images:

ArrayList inputImages = new ArrayList();

foreach (Page page in textControl1.GetPages())
{
    MemoryStream image = new MemoryStream();
    Bitmap bitmap = page.GetImage(100, TXTextControl.Page.PageContent.All);
    bitmap.Save(image, ImageFormat.Tiff);
    inputImages.Add(image);
}
Dim inputImages As New ArrayList()

For Each page As Page In textControl1.GetPages()
    Dim image As New MemoryStream()
    Dim bitmap As Bitmap = page.GetImage(100, TXTextControl.Page.PageContent.All)
    bitmap.Save(image, ImageFormat.Tiff)
    inputImages.Add(image)
Next

Each TIFF image is stored in a memory stream which is added to an ArrayList for an easier handling when combining them.

In a second step, the TIFF images are combined to a single image. Therefore, a new image is created in order to append all other images from the ArrayList to a new frame of the new image using the SaveAdd method.

public static void CreateMultipageTIF(ArrayList InputImages, string Filename)
{
    // set the image codec
    ImageCodecInfo info = null;
    foreach (ImageCodecInfo ice in ImageCodecInfo.GetImageEncoders())
    {
        if (ice.MimeType == "image/tiff")
        {
            info = ice;
            break;
        }
    }

    EncoderParameters ep = new EncoderParameters(2);

    bool firstPage = true;

    System.Drawing.Image img = null;

    // create an image instance from the 1st image
    for (int nLoopfile = 0; nLoopfile < InputImages.Count; nLoopfile++)

    {
        //get image from src file
        System.Drawing.Image img_src = System.Drawing.Image.FromStream((Stream)InputImages[nLoopfile]);

        Guid guid = img_src.FrameDimensionsList[0];
        System.Drawing.Imaging.FrameDimension dimension = new System.Drawing.Imaging.FrameDimension(guid);

        //get the frames from src file
        for (int nLoopFrame = 0; nLoopFrame < img_src.GetFrameCount(dimension); nLoopFrame++)
        {
            img_src.SelectActiveFrame(dimension, nLoopFrame);

            ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, Convert.ToInt32(EncoderValue.CompressionLZW));

            // if first page, then create the initial image
            if (firstPage)
            {
                img = img_src;

                ep.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, Convert.ToInt32(EncoderValue.MultiFrame));
                img.Save(Filename, info, ep);

                firstPage = false;
                continue;
            }

            // add image to the next frame
            ep.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, Convert.ToInt32(EncoderValue.FrameDimensionPage));
            img.SaveAdd(img_src, ep);
        }
    }

    ep.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, Convert.ToInt32(EncoderValue.Flush));
    img.SaveAdd(ep);
}
Public Shared Sub CreateMultipageTIF(InputImages As ArrayList, Filename As String)
    ' set the image codec
    Dim info As ImageCodecInfo = Nothing
    For Each ice As ImageCodecInfo In ImageCodecInfo.GetImageEncoders()
        If ice.MimeType = "image/tiff" Then
            info = ice
            Exit For
        End If
    Next

    Dim ep As New EncoderParameters(2)

    Dim firstPage As Boolean = True

    Dim img As System.Drawing.Image = Nothing

    ' create an image instance from the 1st image
    For nLoopfile As Integer = 0 To InputImages.Count - 1
        ' get image from src file
        Dim img_src As System.Drawing.Image = System.Drawing.Image.FromStream(DirectCast(InputImages(nLoopfile), Stream))

        Dim guid As Guid = img_src.FrameDimensionsList(0)
        Dim dimension As New System.Drawing.Imaging.FrameDimension(guid)

        ' get the frames from src file
        For nLoopFrame As Integer = 0 To img_src.GetFrameCount(dimension) - 1
            img_src.SelectActiveFrame(dimension, nLoopFrame)

            ep.Param(0) = New EncoderParameter(System.Drawing.Imaging.Encoder.Compression, Convert.ToInt32(EncoderValue.CompressionLZW))

            ' if first page, then create the initial image
            If firstPage Then
                img = img_src

                ep.Param(1) = New EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, Convert.ToInt32(EncoderValue.MultiFrame))
                img.Save(Filename, info, ep)

                firstPage = False
                Continue For
            End If

                ' add image to the next frame
                 ep.Param(1) = New EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, Convert.ToInt32(EncoderValue.FrameDimensionPage))
            img.SaveAdd(img_src, ep)
        Next
    Next

    ep.Param(1) = New EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, Convert.ToInt32(EncoderValue.Flush))
    img.SaveAdd(ep)
End Sub

Importing MS Word Merge Fields

Starting with TX Text Control .NET 14.0, MS Word Merge Fields can be imported into the TextControl or ServerTextControl with the LoadSettings class. The LoadSettings.ApplicationFieldFormat property is used to enable TX Text Control to load the ApplicationField type text fields.

For MS Word merge fields, it must be set to ApplicationFieldFormat.MSWord.

The LoadSettings.ApplicationFieldTypeNames property sets the type name of each field. For MS Word Merge Fields and Form Fields, it is MERGEFIELD.

The ApplicationField.Parameters property defines the field's parameters, e.g. its functionality.

It has the format MERGEFIELD FieldName [switches].

The following code imports MS Word merge fields from a DOCX file:

TXTextControl.LoadSettings ls = new TXTextControl.LoadSettings();
ls.ApplicationFieldFormat = ApplicationFieldFormat.MSWord;
ls.ApplicationFieldTypeNames = new string[] { "MERGEFIELD" };
textControl1.Load(@"C:     est.docx", TXTextControl.StreamType.WordprocessingML, ls);
Dim LS As New TXTextControl.LoadSettings()
LS.ApplicationFieldFormat = ApplicationFieldFormat.MSWord
LS.ApplicationFieldTypeNames = New String() {"MERGEFIELD"}
TextControl1.Load("C:      est.docx", TXTextControl.StreamType.WordprocessingML, LS)

Each MS Word merge field has been imported into the TextControl.ApplicationFields collection after that.

Inserting a Table with Text Fields

Tables are often used to display data and TX Text Control's ApplicationField type text fields are the perfect tool to merge data into tables. The whole procedure can be divided into three simple steps:

  • Insert a table
  • Fill the table with text and fields
  • Apply text formatting to the table cells

The following code inserts a 6 x 3 table and adds the header texts to the first table row.

To format the first table row's text bold, the cursor is set to the first text position in the first table cell with

textControl1.Selection.Start = table.Cells.GetItem(1, 1).Start - 1;.

The lenght of the selection is calculated with

textControl1.Selection.Length = table.Cells.GetItem(1, 3).Start - 1 + table.Cells.GetItem(1, 3).Length;.

As the Selection class is zero-based and the TableCell text index one-based, it is necessary to subtract one from the TableCell text index.

int tableID = 10;
textControl1.Tables.Add(6, 3, tableID);
TXTextControl.Table table = textControl1.Tables.GetItem(tableID);
table.Cells.GetItem(1, 1).Text = "Date / Time";
table.Cells.GetItem(1, 2).Text = "Customer data";
table.Cells.GetItem(1, 3).Text = "Result";
textControl1.Selection.Start = table.Cells.GetItem(1, 1).Start - 1;
textControl1.Selection.Length = table.Cells.GetItem(1, 3).Start - 1
                               + table.Cells.GetItem(1, 3).Length;
textControl1.Selection.Bold = true;
textControl1.Selection.ParagraphFormat.Alignment = TXTextControl.HorizontalAlignment.Center;
textControl1.Selection.Length = 0;
Dim TableID As Integer = 10
TextControl1.Tables.Add(6, 3, tableID)
Dim Table As TXTextControl.Table = TextControl1.Tables.GetItem(TableID)
Table.Cells.GetItem(1, 1).Text = "Date / Time"
Table.Cells.GetItem(1, 2).Text = "Customer data"
Table.Cells.GetItem(1, 3).Text = "Result"
TextControl1.Selection.Start = Table.Cells.GetItem(1, 1).Start - 1
TextControl1.Selection.Length = Table.Cells.GetItem(1, 3).Start - 1 _
                               + Table.Cells.GetItem(1, 3).Length
TextControl1.Selection.Bold = True
TextControl1.Selection.ParagraphFormat.Alignment = TXTextControl.HorizontalAlignment.Center
TextControl1.Selection.Length = 0

Next, the TableCellCollection is looped to insert variable data, like date, customer number and result. The customer number will be inserted in an ApplicationField text field.

foreach (TXTextControl.TableCell cell in table.Cells)
{
    if (cell.Row == 1)
        continue;
    if (cell.Column == 1)
        cell.Text = DateTime.UtcNow.ToString();
    if (cell.Column == 2)
    {
        textControl1.Selection.Start = cell.Start - 1;
        TXTextControl.ApplicationField appField = new TXTextControl.ApplicationField(
                                                 TXTextControl.ApplicationFieldFormat.MSWord,
                                                 "MERGEFIELD",
                                                 cell.Row.ToString(),
                                                 new string[] { "FieldName" });
        appField.DoubledInputPosition = true;
        appField.ShowActivated = true;
        textControl1.ApplicationFields.Add(appField);
        textControl1.Selection.ParagraphFormat.Alignment = TXTextControl.HorizontalAlignment.Center;
     }
     if (cell.Column == 3)
        cell.Text = Guid.NewGuid().ToString().Substring(1, 10);
}
For Each cell As TXTextControl.TableCell In table.Cells
    If cell.Row = 1 Then
        Continue For
    End If
    If cell.Column = 1 Then
        cell.Text = DateTime.UtcNow.ToString()
    End If
    If cell.Column = 2 Then
            textControl1.Selection.Start = cell.Start - 1
            Dim appField As New TXTextControl.ApplicationField(
                TXTextControl.ApplicationFieldFormat.MSWord, _
                "MERGEFIELD", _
                cell.Row.ToString(), _
                New String() {"FieldName"})
            appField.DoubledInputPosition = True
            appField.ShowActivated = True
            textControl1.ApplicationFields.Add(appField)
            textControl1.Selection.ParagraphFormat.Alignment = TXTextControl.HorizontalAlignment.Center
    End If
    If cell.Column = 3 Then
        cell.Text = Guid.NewGuid().ToString().Substring(1, 10)
    End If
Next

Replacing a Paragraph with a Line Break on Enter Key

For some applications, it is necessary to replace the default keys with another key or action. The following code intercepts the Enter key and inserts a line break instead of a paragraph.

private void textControl1_KeyPress(object sender, KeyPressEventArgs e)
{
    if(e.KeyChar.Equals('
'))
    {
        e.Handled = true;
        textControl1.Selection.Text = "";
    }
}
Private Sub textControl1_KeyPress(sender As Object, e As KeyPressEventArgs)
    If e.KeyChar.Equals(ControlChars.Cr) Then
        e.Handled = True
        textControl1.Selection.Text = vbVerticalTab
    End If
End Sub

Scrolling programmatically to a Text Position

Scrolling in TX Text Control .NET is pretty easy when working with text fields. However, if you want to scroll to a certain text position, it is not that easy. The following method shows a way how to do that.

Please note: The textPosition value is zero-based (like the Selection.Start or the InputPosition.TextPosition property) and has to be increased by 1 to get the requested character of the TextCharCollection. If the increased value is greater than the number of TextChars of the TextCharCollection, the Y-coordinate of the last line in the TextControl will be used to set the TextControl.ScrollLocation.

private void scrollToTextPosition(int textPosition)
{
    Point newScrollLocation = textControl1.ScrollLocation;
    if (textPosition & 1 <= textControl1.TextChars.Count)
    {
        newScrollLocation = new Point(0, textControl1.TextChars[textPosition + 1].Bounds.Y);
    }
    else
    {
        newScrollLocation = new Point(0, textControl1.Lines[textControl1.Lines.Count].TextBounds.Y);
    }
    textControl1.ScrollLocation = newScrollLocation;
}
Private Sub scrollToTextPosition(ByVal textPosition As Integer)
    Dim newScrollLocation As Point = textControl1.ScrollLocation
    If textPosition + 1 <= textControl1.TextChars.Count Then
        newScrollLocation = New Point(0, textControl1.TextChars(textPosition + 1).Bounds.Y)
    Else
        newScrollLocation = New Point(0, textControl1.Lines(textControl1.Lines.Count).TextBounds.Y)
    End If
    textControl1.ScrollLocation = newScrollLocation
End Sub

Printing without the Status Dialog

The default print controller of .NET displays a status dialog that shows the status of the printing process like this:

'Page 5 of document'

If you want to print without such a print status dialog, you will need to use the StandardPrintController instead of the default PrintControllerWithStatusDialog.

The StandardPrintController is part of the System.Drawing.Printing namespace. The following code prints the content of TX Text Control without such print status dialog:

PrintDocument printDoc = new PrintDocument();
PrintController printCtl = new StandardPrintController();
printDoc.PrintController = printCtl;
textControl1.Print(printDoc);
Dim PrintDoc As New PrintDocument()
Dim PrintCtl As PrintController = New StandardPrintController()
PrintDoc.PrintController = PrintCtl
TextControl1.Print(PrintDoc)

TXTextControl.Selection and Undo

There are two ways to manipulate a range of text:

  • A text range is selected using TextControl.Select. This selection is changed using the TextControl.Selection property that manipulates the current selection.
  • A new Selection object is created and assigned to the TextControl.Selection property.

Most users are going with the first, more obvious approach. A range of text is selected and changed using a sequence of Selection properties:

textControl1.Select(0, 5);
textControl1.Selection.Bold = true;
textControl1.Selection.Italic = true;
textControl1.Selection.Text = "New Text";
textControl1.[Select](0, 5)
textControl1.Selection.Bold = True
textControl1.Selection.Italic = True
textControl1.Selection.Text = "New Text"

This works very reliably and can be used in most scenarios. But this approach has two drawbacks:

  • A selection is visible on the screen. The text is visually selected with the transparent blue background in order to be formatted.
  • Each change of the selection is counted as a single Undo history step.

In the above code, three undo steps are required to revert to the previous state.

So, what if we could modify the Selection virtually before applying to our TextControl instance? This is what the second approach below does.

A new virtual Selection object is created and manipulated using the same properties. After the object is finished, it will be assigned to the Selection property of our TextControl instance.

TXTextControl.Selection sel = new TXTextControl.Selection();
sel.Text = "New Text";
sel.Bold = true;
sel.Italic = true;
textControl1.Selection = sel;
Dim sel As New TXTextControl.Selection()
sel.Text = "New Text"
sel.Bold = True
sel.Italic = True
textControl1.Selection = sel

This is counted as a single Undo step and there is no visible selection background.

Removing empty Pages

Cleaning up documents might be necessary when building documents dynamically. Various document sections are added, forced page breaks are inserted at different positions and end-users can modify the resulting document. A typical task is to remove all empty pages from a document that consist only of a page break or section break character.

The following method uses the PageEnumerator to loop through all pages. If the Length of a page equals 1, the page is removed.

private void RemoveEmptyPages()
{
    TXTextControl.PageCollection.PageEnumerator pageEnum =
                    textControl1.GetPages().GetEnumerator();
    pageEnum.MoveNext();
    int pageCounter = textControl1.GetPages().Count;
    for (int i = 0; i < pageCounter; i++)
    {
        TXTextControl.Page curPage = (TXTextControl.Page)pageEnum.Current;
        if (curPage.Length == 1)
        {
            textControl1.Select(curPage.Start - 1, 1);
            textControl1.Selection.Text = "";
        }
        else
            pageEnum.MoveNext();
    }
}
Private Sub RemoveEmptyPages()
    Dim pageEnum As TXTextControl.PageCollection.PageEnumerator =
                    textControl1.GetPages().GetEnumerator()
    pageEnum.MoveNext()
    Dim pageCounter As Integer = textControl1.GetPages().Count
    For i As Integer = 0 To pageCounter - 1
        Dim curPage As TXTextControl.Page =
                    DirectCast(pageEnum.Current, TXTextControl.Page)
        If curPage.Length = 1 Then
            textControl1.[Select](curPage.Start - 1, 1)
            textControl1.Selection.Text = ""
        Else
            pageEnum.MoveNext()
        End If
    Next
End Sub

Inserting Columns programmatically using TX Text Control .NET

Generally, inserting page columns is a very easy task. Using the SectionFormat.Columns property, you can define the number of columns. TX Text Control will then automatically adjust the width of the columns based on the page size and number of columns.

But if the columns should have a different size and should not be averaged and spread out on the page, we need to set the width of each column. This can be done in the SectionFormat constructor:

public SectionFormat(int columns, int[] columnWidths, int[] columnDistances);
Public Sub New(ByVal columns As Integer, columnWidths As Integer(), columnDistances As Integer())

The second column width is calculated based on the first width, the distance between the columns, the page size and margins. This formula shows the calculation:

SecondColumnWidth = PageSize - PageMargins - FirstColumnWidth - ColumnDistance

The following code shows how to insert a new section with two columns where you just need to pass the first column width:

private void InsertColumns(int FirstColumnWidth, int ColumnDistance)
{
    textControl1.Sections.Add(TXTextControl.SectionBreakKind.BeginAtNewLine);
    TXTextControl.SectionFormat currentFormat =
        textControl1.Sections.GetItem().Format;

    int iSecondColumnWidth = (int)(
        (currentFormat.PageSize.Width * 14.4) -
        (currentFormat.PageMargins.Left * 14.4) -
        (currentFormat.PageMargins.Right * 14.4) -
        FirstColumnWidth -
        ColumnDistance
        );

    TXTextControl.SectionFormat newSectionFormat =
        new TXTextControl.SectionFormat(
        2,
        new int[] { FirstColumnWidth, iSecondColumnWidth },
        new int[] { ColumnDistance });

    textControl1.Sections.GetItem().Format = newSectionFormat;
}
Private Sub InsertColumns(FirstColumnWidth As Integer, ColumnDistance As Integer)
    textControl1.Sections.Add(TXTextControl.SectionBreakKind.BeginAtNewLine)
    Dim currentFormat As TXTextControl.SectionFormat = textControl1.Sections.GetItem().Format
    Dim iSecondColumnWidth As Integer = CInt(Math.Truncate(
            (currentFormat.PageSize.Width * 14.4) -
            (currentFormat.PageMargins.Left * 14.4) -
            (currentFormat.PageMargins.Right * 14.4) -
            FirstColumnWidth -
            ColumnDistance))

            Dim newSectionFormat As New TXTextControl.SectionFormat(
            2,
            New Integer() {FirstColumnWidth, iSecondColumnWidth},
            New Integer() {ColumnDistance})

    textControl1.Sections.GetItem().Format = newSectionFormat
End Sub

Getting the Page Number at the Current Scroll Location

When using TX Text Control in read-only mode, it might be required to display the page number of the currently visible page. In order to get the page number at the current input position, you just need to get the appropriate property from the InputPosition object.

But when scrolling through the pages, the actual input position is not changed, so that this property can't be used to get the page number.

But thanks to the flexible and powerful class library, it is very easy to retrieve the page number of the currently visible page.

In order to get the page number, we use the LineCollection to get the line in the middle of the visible TextControl. TX Text Control automatically factors the current scroll location into the calculation, so that we just need to pass the half of the control size to the GetItem method. It returns the Line object in the middle of the visible document part. And the Line object provides the according page number among other information.

private int GetPageAtScrollPosition()
{
    return textControl1.Lines.GetItem(
        new Point(0, textControl1.Height / 2)).Page;
}
Private Function GetPageAtScrollPosition() As Integer
    Return textControl1.Lines.GetItem(New Point(0, textControl1.Height / 2)).Page
End Function

We just need to update the information using the VScroll event:

private void textControl1_VScroll(object sender, EventArgs e)
{
    int iPageNumber = GetPageAtScrollPosition();
        Console.WriteLine("Page " +
        iPageNumber.ToString() +
        " of " +
        textControl1.Pages.ToString());

    Console.WriteLine("Section " +
        textControl1.GetPages()[iPageNumber].Section +
        " of " +
        textControl1.Sections.Count.ToString());
}
Private Sub textControl1_VScroll(sender As Object, e As EventArgs)
    Dim iPageNumber As Integer = GetPageAtScrollPosition()
    Console.WriteLine("Page " +
        iPageNumber.ToString() +
        " of " +
        textControl1.Pages.ToString())

    Console.WriteLine("Section " +
        textControl1.GetPages()(iPageNumber).Section +
        " of " +
        textControl1.Sections.Count.ToString())
End Sub

Printing into a PrintDocument

TX Text Control .NET for Windows Forms's Print method introduces many advantages over the Print method of the ActiveX control. It enables you to print into a PrintDocument of the System.Drawing.Printing namespace.

TX Text Control .NET for Windows Forms's Print method uses the following settings of this object:

  • DocumentName
  • PrintController
  • PrinterSettings.FromPage
  • PrinterSettings.ToPage
  • PrinterSettings.Copies
  • PrinterSettings.Collate
  • PrinterSettings.PrinterName
  • PrinterSettings.PrintToFile
  • PrinterSettings.PrintRange
  • DefaultPageSettings.Color

If you would like to print one specific page of the document, not only FromPage and ToPage must be specified, but also the PrintRange property.

The following code sample prints page 2 of TX Text Control and sets the current page size.

int m_curPage = 2;
PrintDocument myPrintDoc = new PrintDocument();
myPrintDoc.DefaultPageSettings.PaperSize = new PaperSize("default",
                                                         textControl1.PageSize.Width,
                                                         textControl1.PageSize.Height);
myPrintDoc.DefaultPageSettings.Margins = new Margins(textControl1.PageMargins.Left,
                                                     textControl1.PageMargins.Right,
                                                     textControl1.PageMargins.Top,
                                                     textControl1.PageMargins.Bottom);
myPrintDoc.PrinterSettings.PrintRange = PrintRange.SomePages;
myPrintDoc.PrinterSettings.FromPage = m_curPage;
myPrintDoc.PrinterSettings.ToPage = m_curPage;
textControl1.Print(myPrintDoc);
Dim CurrentPage As Integer = 2
Dim MyPrintDoc As PrintDocument = New PrintDocument()
MyPrintDoc.DefaultPageSettings.PaperSize = New PaperSize("default",
                                                         TextControl1.PageSize.Width,
                                                         TextControl1.PageSize.Height)
MyPrintDoc.DefaultPageSettings.Margins = New Margins(TextControl1.PageMargins.Left,
                                                     TextControl1.PageMargins.Right,
                                                     TextControl1.PageMargins.Top,
                                                     TextControl1.PageMargins.Bottom)
MyPrintDoc.PrinterSettings.PrintRange = PrintRange.SomePages
MyPrintDoc.PrinterSettings.FromPage = CurrentPage
MyPrintDoc.PrinterSettings.ToPage = CurrentPage
TextControl1.Print(MyPrintDoc)

As you can see, the PrintPage property must be specified with SomePages which indicates that the FromPage and ToPage is considered.