Word Editor in WPF

Often rich editing controls for desktop applications use the RTF format as their internal object model or provide support for importing and exporting RTF content.

GemBox.Document supports both reading and writing RTF, which enables you to combine it with such controls to create an advanced Word editor control, one that's capable of saving to PDF, printing, mail merging and a lot more.

The following example shows interoperability between GemBox.Document library and WPF's RichTextBox control via RTF. The example also implements some common Word functionalities for text editing like modifying, styling, copying and pasting.

Word editor with common functionalities like modifying, styling, copying and pasting from WPF application
Screenshot of rich text editor for Word files in WPF
<Window x:Class="WpfWordEditor.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Word Editor"
        WindowState="Maximized">
    <DockPanel>

        <ToolBarTray DockPanel.Dock="Top">

            <ToolBarTray.Resources>
                <Style TargetType="Image">
                    <Style.Triggers>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Opacity" Value="0.5"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
                <Style TargetType="TextBlock">
                    <Setter Property="Width" Value="30"/>
                    <Setter Property="TextAlignment" Value="Center"/>
                    <Setter Property="FontFamily" Value="Palatino Linotype"/>
                    <Setter Property="FontSize" Value ="14"/>
                    <Setter Property="FontWeight" Value="Bold"/>
                </Style>
            </ToolBarTray.Resources>
            
            <ToolBar Header="GemBox ToolBar" ToolTip="Commands in this group use GemBox.Document component">
                <ToolBar.CommandBindings>
                    <CommandBinding Command="Open" Executed="Open"/>
                    <CommandBinding Command="Save" Executed="Save" CanExecute="CanSave" />
                    <CommandBinding Command="SaveAs" Executed="Save" CanExecute="CanSave" />
                    <CommandBinding Command="Cut" Executed="Cut" CanExecute="CanCut" />
                    <CommandBinding Command="Copy" Executed="Copy" CanExecute="CanCopy" />
                    <CommandBinding Command="Paste" Executed="Paste" CanExecute="CanPaste"/>
                </ToolBar.CommandBindings>
                <Button Command="Open" ToolTip="Open">
                    <Image Source="Icons/Open.png"/>
                </Button>
                <Button Command="Save" ToolTip="Save">
                    <Image Source="Icons/Save.png"/>
                </Button>
                <Separator/>
                <Button Command="Cut" ToolTip="Cut with GemBox.Document">
                    <Image Source="Icons/Cut.png"/>
                </Button>
                <Button Command="Copy" ToolTip="Copy with GemBox.document">
                    <Image Source="Icons/Copy.png"/>
                </Button>
                <Button Command="Paste" CommandParameter="prepend" ToolTip="Paste prepend with GemBox.Document">
                    <Image Source="Icons/Paste.png"/>
                </Button>
                <Button Command="Paste" CommandParameter="append" ToolTip="Paste append with GemBox.Document">
                    <Image Source="Icons/Paste.png"/>
                </Button>
            </ToolBar>

            <ToolBar Header="WPF ToolBar" ToolTip="Commands in this group use only WPF">
                <Button Command="Undo" ToolTip="Undo">
                    <Image Source="Icons/Undo.png"/>
                </Button>
                <Button Command="Redo" ToolTip="Redo">
                    <Image Source="Icons/Redo.png"/>
                </Button>
                <Separator/>
                <Button Command="Cut" ToolTip="Cut">
                    <Image Source="Icons/Cut.png"/>
                </Button>
                <Button Command="Copy" ToolTip="Copy">
                    <Image Source="Icons/Copy.png"/>
                </Button>
                <Button Command="Paste" ToolTip="Paste">
                    <Image Source="Icons/Paste.png"/>
                </Button>
                <Separator/>
                <Button Command="ToggleBold" ToolTip="Bold">
                    <TextBlock Text="B" FontWeight="Bold"/>
                </Button>
                <Button Command="ToggleItalic" ToolTip="Italic">
                    <TextBlock Text="I" FontStyle="Italic"/>
                </Button>
                <Button Command="ToggleUnderline" ToolTip="Underline">
                    <TextBlock Text="U" TextDecorations="Underline"/>
                </Button>
                <Separator/>
                <Button Command="IncreaseFontSize" ToolTip="Increase Font Size">
                    <Image Source="Icons/IncreaseFontSize.png"/>
                </Button>
                <Button Command="DecreaseFontSize" ToolTip="Decrease Font Size">
                    <Image Source="Icons/DecreaseFontSize.png"/>
                </Button>
                <Separator/>
                <Button Command="ToggleBullets" ToolTip="Bullets">
                    <Image Source="Icons/ToggleBullets.png"/>
                </Button>
                <Button Command="ToggleNumbering" ToolTip="Numbering">
                    <Image Source="Icons/ToggleNumbering.png"/>
                </Button>
                <Separator/>
                <Button Command="DecreaseIndentation" ToolTip="Decrease Indentation">
                    <Image Source="Icons/DecreaseIndentation.png"/>
                </Button>
                <Button Command="IncreaseIndentation" ToolTip="Increase Indentation">
                    <Image Source="Icons/IncreaseIndentation.png"/>
                </Button>
                <Separator/>
                <Button Command="AlignLeft" ToolTip="Align Left">
                    <Image Source="Icons/AlignLeft.png"/>
                </Button>
                <Button Command="AlignCenter" ToolTip="Align Center">
                    <Image Source="Icons/AlignCenter.png"/>
                </Button>
                <Button Command="AlignRight" ToolTip="Align Right">
                    <Image Source="Icons/AlignRight.png"/>
                </Button>
                <Button Command="AlignJustify" ToolTip="Align Justify">
                    <Image Source="Icons/AlignJustify.png"/>
                </Button>
            </ToolBar>

        </ToolBarTray>

        <RichTextBox x:Name="richTextBox" AcceptsTab="True" VerticalScrollBarVisibility="Auto"/>

    </DockPanel>
</Window>
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using GemBox.Document;
using Microsoft.Win32;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        // If using the Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY");
    }

    private void Open(object sender, ExecutedRoutedEventArgs e)
    {
        var dialog = new OpenFileDialog()
        {
            AddExtension = true,
            Filter =
                "All Documents (*.docx;*.docm;*.doc;*.dotx;*.dotm;*.dot;*.htm;*.html;*.rtf;*.xml;*.txt)|*.docx;*.docm;*.dotx;*.dotm;*.doc;*.dot;*.htm;*.html;*.rtf;*.xml;*.txt|" +
                "Word Documents (*.docx)|*.docx|" +
                "Word Macro-Enabled Documents (*.docm)|*.docm|" +
                "Word 97-2003 Documents (*.doc)|*.doc|" +
                "Word Templates (*.dotx)|*.dotx|" +
                "Word Macro-Enabled Templates (*.dotm)|*.dotm|" +
                "Word 97-2003 Templates (*.dot)|*.dot|" +
                "Web Pages (*.htm;*.html)|*.htm;*.html|" +
                "Rich Text Format (*.rtf)|*.rtf|" +
                "Flat OPC (*.xml)|*.xml|" +
                "Plain Text (*.txt)|*.txt"
        };

        if (dialog.ShowDialog() == true)
            using (var stream = new MemoryStream())
            {
                // Convert input file to RTF stream.
                DocumentModel.Load(dialog.FileName).Save(stream, SaveOptions.RtfDefault);

                stream.Position = 0;

                // Load RTF stream into RichTextBox.
                var textRange = new TextRange(this.richTextBox.Document.ContentStart, this.richTextBox.Document.ContentEnd);
                textRange.Load(stream, DataFormats.Rtf);
            }
    }

    private void Save(object sender, ExecutedRoutedEventArgs e)
    {
        var dialog = new SaveFileDialog()
        {
            AddExtension = true,
            Filter =
                "Word Document (*.docx)|*.docx|" +
                "Word Macro-Enabled Document (*.docm)|*.docm|" +
                "Word Template (*.dotx)|*.dotx|" +
                "Word Macro-Enabled Template (*.dotm)|*.dotm|" +
                "PDF (*.pdf)|*.pdf|" +
                "XPS Document (*.xps)|*.xps|" +
                "Web Page (*.htm;*.html)|*.htm;*.html|" +
                "Single File Web Page (*.mht;*.mhtml)|*.mht;*.mhtml|" +
                "Rich Text Format (*.rtf)|*.rtf|" +
                "Flat OPC (*.xml)|*.xml|" +
                "Plain Text (*.txt)|*.txt|" +
                "Image (*.png;*.jpg;*.jpeg;*.gif;*.bmp;*.tif;*.tiff;*.wdp,*.svg)|*.png;*.jpg;*.jpeg;*.gif;*.bmp;*.tif;*.tiff;*.wdp;*.svg"
        };

        if (dialog.ShowDialog(this) == true)
            using (var stream = new MemoryStream())
            {
                // Save RichTextBox content to RTF stream.
                var textRange = new TextRange(this.richTextBox.Document.ContentStart, this.richTextBox.Document.ContentEnd);
                textRange.Save(stream, DataFormats.Rtf);

                stream.Position = 0;

                // Convert RTF stream to output format.
                DocumentModel.Load(stream, LoadOptions.RtfDefault).Save(dialog.FileName);
                Process.Start(dialog.FileName);
            }
    }

    private void Cut(object sender, ExecutedRoutedEventArgs e)
    {
        this.Copy(sender, e);

        // Clear selection.
        this.richTextBox.Selection.Text = string.Empty;
    }

    private void Copy(object sender, ExecutedRoutedEventArgs e)
    {
        using (var stream = new MemoryStream())
        {
            // Save RichTextBox selection to RTF stream.
            this.richTextBox.Selection.Save(stream, DataFormats.Rtf);

            stream.Position = 0;

            // Save RTF stream to clipboard.
            DocumentModel.Load(stream, LoadOptions.RtfDefault).Content.SaveToClipboard();
        }
    }

    private void Paste(object sender, ExecutedRoutedEventArgs e)
    {
        using (var stream = new MemoryStream())
        {
            // Save RichTextBox content to RTF stream.
            var textRange = new TextRange(this.richTextBox.Document.ContentStart, this.richTextBox.Document.ContentEnd);
            textRange.Save(stream, DataFormats.Rtf);

            stream.Position = 0;

            // Load document from RTF stream and prepend or append clipboard content to it.
            var document = DocumentModel.Load(stream, LoadOptions.RtfDefault);
            var position = (string)e.Parameter == "prepend" ? document.Content.Start : document.Content.End;
            position.LoadFromClipboard();

            stream.Position = 0;

            // Save document to RTF stream.
            document.Save(stream, SaveOptions.RtfDefault);

            stream.Position = 0;

            // Load RTF stream into RichTextBox.
            textRange.Load(stream, DataFormats.Rtf);
        }
    }

    private void CanSave(object sender, CanExecuteRoutedEventArgs e)
    {
        if (this.richTextBox != null)
        {
            var document = this.richTextBox.Document;
            var startPosition = document.ContentStart.GetNextInsertionPosition(LogicalDirection.Forward);
            var endPosition = document.ContentEnd.GetNextInsertionPosition(LogicalDirection.Backward);
            e.CanExecute = startPosition != null && endPosition != null && startPosition.CompareTo(endPosition) < 0;
        }
        else
            e.CanExecute = false;
    }

    private void CanCut(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = this.richTextBox != null && !this.richTextBox.Selection.IsEmpty;
    }

    private void CanCopy(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = this.richTextBox != null && !this.richTextBox.Selection.IsEmpty;
    }

    private void CanPaste(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = this.richTextBox != null && this.richTextBox.IsKeyboardFocused;
    }
}
Imports System.Diagnostics
Imports System.IO
Imports System.Windows
Imports System.Windows.Documents
Imports System.Windows.Input
Imports GemBox.Document
Imports Microsoft.Win32

Partial Public Class MainWindow
    Inherits Window

    Public Sub New()

        InitializeComponent()

        ' If using the Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY")

    End Sub

    Private Sub Open(sender As Object, e As ExecutedRoutedEventArgs)

        Dim dialog = New OpenFileDialog() With {
            .AddExtension = True,
            .Filter =
                "All Documents (*.docx;*.docm;*.doc;*.dotx;*.dotm;*.dot;*.htm;*.html;*.rtf;*.xml;*.txt)|*.docx;*.docm;*.dotx;*.dotm;*.doc;*.dot;*.htm;*.html;*.rtf;*.xml;*.txt|" &
                "Word Documents (*.docx)|*.docx|" &
                "Word Macro-Enabled Documents (*.docm)|*.docm|" &
                "Word 97-2003 Documents (*.doc)|*.doc|" &
                "Word Templates (*.dotx)|*.dotx|" &
                "Word Macro-Enabled Templates (*.dotm)|*.dotm|" &
                "Word 97-2003 Templates (*.dot)|*.dot|" &
                "Web Pages (*.htm;*.html)|*.htm;*.html|" &
                "Rich Text Format (*.rtf)|*.rtf|" &
                "Flat OPC (*.xml)|*.xml|" &
                "Plain Text (*.txt)|*.txt"
        }

        If dialog.ShowDialog() = True Then
            Using stream = New MemoryStream()

                ' Convert input file to RTF stream.
                DocumentModel.Load(dialog.FileName).Save(stream, SaveOptions.RtfDefault)

                stream.Position = 0

                ' Load RTF stream into RichTextBox.
                Dim textRange = New TextRange(Me.richTextBox.Document.ContentStart, Me.richTextBox.Document.ContentEnd)
                textRange.Load(stream, DataFormats.Rtf)

            End Using
        End If

    End Sub

    Private Sub Save(sender As Object, e As ExecutedRoutedEventArgs)

        Dim dialog = New SaveFileDialog() With {
            .AddExtension = True,
            .Filter =
                "Word Document (*.docx)|*.docx|" &
                "Word Macro-Enabled Document (*.docm)|*.docm|" &
                "Word Template (*.dotx)|*.dotx|" +
                "Word Macro-Enabled Template (*.dotm)|*.dotm|" &
                "PDF (*.pdf)|*.pdf|" &
                "XPS Document (*.xps)|*.xps|" &
                "Web Page (*.htm;*.html)|*.htm;*.html|" &
                "Single File Web Page (*.mht;*.mhtml)|*.mht;*.mhtml|" &
                "Rich Text Format (*.rtf)|*.rtf|" &
                "Flat OPC (*.xml)|*.xml|" &
                "Plain Text (*.txt)|*.txt|" &
                "Image (*.png;*.jpg;*.jpeg;*.gif;*.bmp;*.tif;*.tiff;*.wdp,*.svg)|*.png;*.jpg;*.jpeg;*.gif;*.bmp;*.tif;*.tiff;*.wdp;*.svg"
        }

        If dialog.ShowDialog(Me) = True Then
            Using stream = New MemoryStream()

                ' Save RichTextBox content to RTF stream.
                Dim textRange = New TextRange(Me.richTextBox.Document.ContentStart, Me.richTextBox.Document.ContentEnd)
                textRange.Save(stream, DataFormats.Rtf)

                stream.Position = 0

                ' Convert RTF stream to output format.
                DocumentModel.Load(stream, LoadOptions.RtfDefault).Save(dialog.FileName)
                Process.Start(dialog.FileName)

            End Using
        End If

    End Sub

    Private Sub Cut(sender As Object, e As ExecutedRoutedEventArgs)

        Me.Copy(sender, e)

        ' Clear selection.
        Me.richTextBox.Selection.Text = String.Empty

    End Sub

    Private Sub Copy(sender As Object, e As ExecutedRoutedEventArgs)

        Using stream = New MemoryStream()

            ' Save RichTextBox selection to RTF stream.
            Me.richTextBox.Selection.Save(stream, DataFormats.Rtf)

            stream.Position = 0

            ' Save RTF stream to clipboard.
            DocumentModel.Load(stream, LoadOptions.RtfDefault).Content.SaveToClipboard()

        End Using

    End Sub

    Private Sub Paste(sender As Object, e As ExecutedRoutedEventArgs)

        Using stream = New MemoryStream()

            ' Save RichTextBox content to RTF stream.
            Dim textRange = New TextRange(Me.richTextBox.Document.ContentStart, Me.richTextBox.Document.ContentEnd)
            textRange.Save(stream, DataFormats.Rtf)

            stream.Position = 0

            ' Load document from RTF stream and prepend or append clipboard content to it.
            Dim document = DocumentModel.Load(stream, LoadOptions.RtfDefault)
            Dim position = If(DirectCast(e.Parameter, String) = "prepend", document.Content.Start, document.Content.End)
            position.LoadFromClipboard()

            stream.Position = 0

            ' Save document to RTF stream.
            document.Save(stream, SaveOptions.RtfDefault)

            stream.Position = 0

            ' Load RTF stream into RichTextBox.
            textRange.Load(stream, DataFormats.Rtf)

        End Using

    End Sub

    Private Sub CanSave(sender As Object, e As CanExecuteRoutedEventArgs)

        If Me.richTextBox IsNot Nothing Then
            Dim document = Me.richTextBox.Document
            Dim startPosition = document.ContentStart.GetNextInsertionPosition(LogicalDirection.Forward)
            Dim endPosition = document.ContentEnd.GetNextInsertionPosition(LogicalDirection.Backward)
            e.CanExecute = startPosition IsNot Nothing AndAlso endPosition IsNot Nothing AndAlso startPosition.CompareTo(endPosition) < 0
        Else
            e.CanExecute = False
        End If

    End Sub

    Private Sub CanCut(sender As Object, e As CanExecuteRoutedEventArgs)

        e.CanExecute = Me.richTextBox IsNot Nothing AndAlso Not Me.richTextBox.Selection.IsEmpty

    End Sub

    Private Sub CanCopy(sender As Object, e As CanExecuteRoutedEventArgs)

        e.CanExecute = Me.richTextBox IsNot Nothing AndAlso Not Me.richTextBox.Selection.IsEmpty

    End Sub

    Private Sub CanPaste(sender As Object, e As CanExecuteRoutedEventArgs)

        e.CanExecute = Me.richTextBox IsNot Nothing AndAlso Me.richTextBox.IsKeyboardFocused

    End Sub

End Class

See also


Next steps

GemBox.Document is a .NET component that enables you to read, write, edit, convert, and print document files from your .NET applications using one simple API. How about testing it today?

Download Buy