Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / automation

Word-like Editor and RTF Text Manipulation with Telerik RadRichTextEditor

3.67/5 (3 votes)
21 Nov 2017CPOL8 min read 11.3K  
Create a Word-like software using RadRichTextEditor and C#
In this article, we will see how to create a Word-like software, using one of Telerik's controls for WinForms, i.e., RadRichTextEditor and C#.

Introduction

Telerik is a famous company - acquired by Progress Software in 2014, which offers top-tier tools for software development, as advanced customized controls, libraries, and so on. Mainly focused on .NET development tools, more recently Telerik sells platform for web, hybrid and native app development. In this article, we will see how to create a Word-like software, using one of Telerik's controls for WinForms, i.e., RadRichTextEditor and C#. Moreover, we will discuss several ideas in regards to the possibility of a programmatical modification of the RTF text hosted by the control, in order to produce DOCX/PDF files compiled by our applications.

Pre-Requisites

For the present article, it will be assumed Telerik DevCraft Suite is already installed on the developing machine.

If not, Telerik provided a very straightforward howto to run the setup. Please check this link for WinForms libraries and controls installation.

Create a New Project

In Visual Studio, click "New Project", and select "Windows Form Application" template from Visual C#. Give your solution a name, then click OK.

Once your project has been created, take a look at the toolbox: if Visual Studio Extensions (from Telerik DevCraft) has been previously installed, Telerik's controls will show up. In the following image, the reader can see the highlighted RadRichTextEditor, and as any other control, it can be dragged on our form.

Image 1

Running now, the sample will show a simple Windows Forms with a RadRichTextEditor on it, but the latter is already fully usable. In the following image, a sample of typed text can be seen. To modify parts of the text, adding bold or underline to what has been typed, it is possible to use known Microsoft Word shortcuts: pressing Ctrl+B will result in bold, Ctrl+U produces underlined text, and so on. For a full list of RadRichTextEditor's shortcuts, please refer to this document.

Image 2

Add RichTextEditorRibbonBar for Quick Access to Common Features

Telerik provides a very neat ribbon bar, which can be used in conjunction with RadRichTextEditor, to have common commands at hand, making it easier to modify text. The RichTextEditorRibbonBar can be easily dragged on form from the toolbox, and linked to the RichTextEditor by setting its AssociatedRichTextEditor property.

Image 3

In running the sample, the reader can now test the features that the two controls union will produce: a pretty much full-fledged text editor, without having written any code so far.

Forewords for a Case Study

But what are some of the possibilities offered for a programmatically automated use of those controls?

We will see a very small case study in the following. Suppose a user has a set of already existent .docx models, among which he must choose and load the proper one, each of which contains some tags that must be replaced with a given text (for example, let's think about a precompiled letter, that must be completed by adding the recipient's name, and some other information. To further complicate things, suppose the letter must include the compiler's stamp).

We will break down the code that will be necessary to accomplish what listed above. Starting from the simplest tasks, we will see how to achieve each step programmatically. At the end of that analysis, a complete class will be presented, named EMWordProcess.cs, and a small sample that uses it.

Save Document as .Docx or .Pdf Formats

Saving documents from RadRichTextEditor is pretty simple. The DocxFormatProvider and PdfFormatProvider provides a very immediate Export() method. In both cases, given a RadDocument object (the RichTextEditor contents, that can be read from the RadRichTextEditor Document property) and a path in which to save, it is sufficient to pass the document to the Export() method, along with an opened stream toward the destination path.

The following are two extension methods for RadDocument which will save a RadRichTextEditor.Document to .Docx and .Pdf files:

C#
public static void SaveAsDocx(this RadDocument document, string path)
{
    var provider = new DocxFormatProvider();
    using (Stream output = new FileStream(path, FileMode.OpenOrCreate))
    {
        provider.Export(document, output);
    }
}
 
public static void SaveAsPdf(this RadDocument document, string path)
{
    var provider = new PdfFormatProvider();
    using (Stream output = new FileStream(path, FileMode.OpenOrCreate))
    {
        provider.Export(document, output);
    }
}

Load a Previously Edited .Docx Files

The DocxFormatProvider can be used also to load/read .Docx files. In the following method, given the existing .Docx path, the file will be read and imported (by the method Import) to a RadDocument variable, which can be later assigned, for example, to the Document property of a RadRichTextEditor.

C#
public static RadDocument LoadDocument(string path)
{
    RadDocument document = null;
    var provider = new DocxFormatProvider();
    using (var stream = new FileStream(path, FileMode.Open))
    {
        document = provider.Import(stream);
    }
    return document;
}

Clone a Document

Cloning a document can be useful in some cases. The following method has been implemented mainly because a RadDocument is always passed by reference. If we need to take a RadDocument and alter it, doing so will also alter the source RadDocument, and maybe we don't want to do this. That will be more clear when we'll see the merge method. For now, let's say that cloning will result in a exact copy of a RadDocument in another RadDocument. That function can be achieved by using XamlFormatProvider to access the data of the source document, and using the Export method to copy it on the second RadDocument.

C#
public static RadDocument CloneDocument(this RadDocument document)
{
    var copy = new RadDocument();
    var provider = new XamlFormatProvider();
 
    string data = provider.Export(document);
    copy = provider.Import(data);
 
    return copy;
}

Merge Documents

Merge two or more document implies we have a list of RadDocuments, desiring to obtain a single document form their concatenation. Here, a typical use of cloning can be observed: since the original file must not be altered, the first document will be cloned on a new RadDocument, then the caret will be moved at its end, and the nth document will be inserted with the InsertFragment method, that allows to insert an RTF text to be added at the caret position.

C#
public static RadDocument MergeDocuments(RadDocument[] documents)
{
    if (documents[0] == null) return null;
 
    RadDocument mergedDocument = CloneDocument(documents[0]);
 
    for (int i = 1; i < documents.Length; i++)
    {
        if (documents[i] == null) continue;
 
        mergedDocument.CaretPosition.MoveToLastPositionInDocument();
        mergedDocument.InsertFragment(new DocumentFragment(documents[i]));
    }
 
    return mergedDocument;
}

Replace Text

For replacing text, we will implement a method with source document, text to be searched, text to be replaced as arguments.

First, we'll declare a DocumentTextSearch object, which will be used to perform a text search, identifying the positions of matching text (TextRange objects) and use those information to apply the replacing text. For this mean we use two for-each loops: the first one, based on occurrences of search.FindAll(toSearch) method, will populate a list of TextRanges, while the second loop uses that list to move the caret on each found position, inserting the replacing text in place of the old one.

C#
public static void ReplaceText
       (RadDocument document, string toSearch, string toReplaceWith)
{
    var search = new DocumentTextSearch(document);
    var rangesTrackingDocumentChanges = new List<TextRange>();
 
    foreach (var textRange in search.FindAll(toSearch))
    {
        var newRange = new TextRange
                       (new DocumentPosition(textRange.StartPosition, true), 
        new DocumentPosition(textRange.EndPosition, true));
        rangesTrackingDocumentChanges.Add(newRange);
    }
 
    foreach (var textRange in rangesTrackingDocumentChanges)
    {
        document.CaretPosition.MoveToPosition(textRange.StartPosition);
        document.DeleteRange(textRange.StartPosition, textRange.EndPosition);
 
        var r = new RadDocumentEditor(document);
        r.Insert(toReplaceWith);
 
        textRange.StartPosition.Dispose();
        textRange.EndPosition.Dispose();
    }
}

Replace Text with Image

Replacing text with an image is accomplished - for the first part of the procedure - in the same way as replacing text: with a first foreach loop, the text to be replaced is searched for, populating a list of positions, or TextRanges. The second loop will go through that list, moving the caret accordingly, and replacing the found text with a FloatingImageBlock object, a particular RadDocument element that can obtain a picture from a stream.

In the case below, we declare a FloatingImageBlock of 160x120 px, which will host a JPG image. Please note the used MemoryStream must point to a valid JPG file. The we proceed in setting some properties, like WrappingStyle, to put image behind text, and AllowOverlap. With InsertInline method, the image will be inserted at the required position.

C#
public static void ReplaceWithImage(RadDocument document, string toSearch)
{
    var search = new DocumentTextSearch(document);
    var rangesTrackingDocumentChanges = new List<TextRange>();
 
    foreach (var textRange in search.FindAll(toSearch))
    {
        var newRange = new TextRange
                       (new DocumentPosition(textRange.StartPosition, true), 
        new DocumentPosition(textRange.EndPosition, true));
        rangesTrackingDocumentChanges.Add(newRange);
    }
 
    foreach (var textRange in rangesTrackingDocumentChanges)
    {
        document.CaretPosition.MoveToPosition(textRange.StartPosition);
        document.DeleteRange(textRange.StartPosition, textRange.EndPosition);
 
        var r = new RadDocumentEditor(document);
 
        using (var imgStream = new MemoryStream(File.ReadAllBytes
                               (@"<PATH_TO_A_VALID_JPG_FILE>")))
        {             
            var imgInline = new FloatingImageBlock(imgStream, 
                new Telerik.WinControls.RichTextEditor.UI.Size(160, 120), "jpg");
            imgInline.VerticalPosition = new FloatingBlockVerticalPosition
            (Telerik.WinForms.Documents.Model.FloatingBlocks.
             VerticalRelativeFrom.Paragraph, -140);
            imgInline.AllowOverlap = true;
            imgInline.WrappingStyle = WrappingStyle.BehindText;
 
            r.InsertInline(imgInline);
        }
 
        textRange.StartPosition.Dispose();
        textRange.EndPosition.Dispose();
    }
}

Full EMWordProcess Class Source Code

The following is the complete source code for EMWordProcess.cs class file, which will be used to develop our case study:

C#
using System.Collections.Generic;
using System.IO;
using Telerik.WinForms.Documents;
using Telerik.WinForms.Documents.FormatProviders.OpenXml.Docx;
using Telerik.WinForms.Documents.FormatProviders.Pdf;
using Telerik.WinForms.Documents.FormatProviders.Xaml;
using Telerik.WinForms.Documents.Model;
using Telerik.WinForms.Documents.TextSearch;
 
namespace TelerikRadTextEdSample
{
    public static class EMWordProcess
    { 
        public static void SaveAsDocx(this RadDocument document, string path)
        {
            var provider = new DocxFormatProvider();
            using (Stream output = new FileStream(path, FileMode.OpenOrCreate))
            {
                provider.Export(document, output);
            }
        }
 
        public static void SaveAsPdf(this RadDocument document, string path)
        {
            var provider = new PdfFormatProvider();
            using (Stream output = new FileStream(path, FileMode.OpenOrCreate))
            {
                provider.Export(document, output);
            }
        }
 
        public static RadDocument LoadDocument(string path)
        {
            RadDocument document = null;
            var provider = new DocxFormatProvider();
            using (var stream = new FileStream(path, FileMode.Open))
            {
                document = provider.Import(stream);
            }
            return document;
        }
 
        public static RadDocument CloneDocument(this RadDocument document)
        {
            var copy = new RadDocument();
            var provider = new XamlFormatProvider();
 
            string data = provider.Export(document);
            copy = provider.Import(data);
 
            return copy;
        }
 
        public static RadDocument MergeDocuments(RadDocument[] documents)
        {
            if (documents[0] == null) return null;
 
            RadDocument mergedDocument = CloneDocument(documents[0]);
 
            for (int i = 1; i < documents.Length; i++)
            {
                if (documents[i] == null) continue;
 
                mergedDocument.CaretPosition.MoveToLastPositionInDocument();
                mergedDocument.InsertFragment(new DocumentFragment(documents[i]));
            }
 
            return mergedDocument;
        }
 
        public static void ReplaceText(RadDocument document, 
                      string toSearch, string toReplaceWith)
        {
            var search = new DocumentTextSearch(document);
            var rangesTrackingDocumentChanges = new List<TextRange>();
 
            foreach (var textRange in search.FindAll(toSearch))
            {
                var newRange = new TextRange(new DocumentPosition
                               (textRange.StartPosition, true), 
                new DocumentPosition(textRange.EndPosition, true));
                rangesTrackingDocumentChanges.Add(newRange);
            }
 
            foreach (var textRange in rangesTrackingDocumentChanges)
            {
                document.CaretPosition.MoveToPosition(textRange.StartPosition);
                document.DeleteRange(textRange.StartPosition, textRange.EndPosition);
 
                var r = new RadDocumentEditor(document);
                r.Insert(toReplaceWith);
 
                textRange.StartPosition.Dispose();
                textRange.EndPosition.Dispose();
            }
        }
 
        public static void ReplaceWithImage(RadDocument document, 
                           string toSearch, string imagePath)
        {
            var search = new DocumentTextSearch(document);
            var rangesTrackingDocumentChanges = new List<TextRange>();
 
            foreach (var textRange in search.FindAll(toSearch))
            {
                var newRange = new TextRange(new DocumentPosition
                               (textRange.StartPosition, 
                true), new DocumentPosition(textRange.EndPosition, true));
                rangesTrackingDocumentChanges.Add(newRange);
            }
 
            foreach (var textRange in rangesTrackingDocumentChanges)
            {
                document.CaretPosition.MoveToPosition(textRange.StartPosition);
                document.DeleteRange(textRange.StartPosition, textRange.EndPosition);
 
                var r = new RadDocumentEditor(document);
 
                using (var imgStream = new MemoryStream(File.ReadAllBytes(imagePath)))
                { 
                    var imgInline = new FloatingImageBlock(imgStream, 
                    new Telerik.WinControls.RichTextEditor.UI.Size(160, 120), "jpg");
                    imgInline.VerticalPosition = new FloatingBlockVerticalPosition
                    (Telerik.WinForms.Documents.Model.FloatingBlocks.
                     VerticalRelativeFrom.Paragraph, -140);
                    imgInline.AllowOverlap = true;
                    imgInline.WrappingStyle = WrappingStyle.BehindText;
 
                    r.InsertInline(imgInline);
                }
 
                textRange.StartPosition.Dispose();
                textRange.EndPosition.Dispose();
            }
        }
    }
}

Developing a Simple App

To develop the case study mentioned above, we need to drag a RadRichTextEditor and a RichTextEditorRibbonBar (binded to the editor) on a WinForm, and create the EMWordProcess class as listed.

Now, suppose we have a simple .Docx file like the following:

Image 4

 

In the app, the bracketed word will be used as tags, so that they can be easily identified. CURRENTDATE tag will be used to expose the current date, RECIPIENT will be replaced with the name of the recipient of the letter, while SENDER will be the name of the letter's author. STAMP is the tag that we will replace with an image, hypothetically representing the sender's stamp.

In the Load() event of the form, that .Docx file can be simply loaded by using our LoadDocument() method, from EMWordProcess class:

Image 5

Replacing tags can be done with the methods seen above.

Consider the following snippet:

C#
EMWordProcess.ReplaceText(radRichTextEditor1.Document, 
              "{CURRENTDATE}", DateTime.Now.ToLongDateString());
EMWordProcess.ReplaceText(radRichTextEditor1.Document, "{RECIPIENT}", "Mr. Smith");
EMWordProcess.ReplaceText(radRichTextEditor1.Document, "{SENDER}", "The author");
EMWordProcess.ReplaceWithImage(radRichTextEditor1.Document, 
              "{STAMP}", @"c:\tmp\keyb.jpg");

Given the RadDocument read from radRichTextEditor1.Document property, we simply proceed in asking that a certain text/tag will be replaced by another text. In case of STAMP tag, we'll use the ReplaceWithImage() method, passing to it a valid JPG file path. Running our sample that way will result in the following:

 560

What was presented here is the simplest way to use the implemented text manipulation functions, but the code can be extended according to real needs. For example, we can produce a certain number of PDF files replacing tags with records coming from a SQL Server data source, or every other automation can be useful to achieve our production needs.

Demo

GIF file, if you cannot see animation, please open it in another tab:

Image 7

Source Code

The source code used in the above samples can be freely downloaded from https://code.msdn.microsoft.com/C-Word-like-editor-and-RTF-a81219ef

Please note the downloadable package doesn't contain any of the Telerik libraries and/or controls. To acquire those components, it is necessary to purchase a license (refer to the link in "Pre-Requisites" section).

References

History

  • 21st November, 2017: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)