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.
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:
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
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.
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:
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
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 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
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)
There are two ways to manipulate a range of text:
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:
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.
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
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
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
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:
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.