October 26, 2018

How to Create a Richtext Editor in WPF using C#

How to Create a Richtext Editor in WPF using C#

During the course of undertaking of a certain project using Microsoft's WPF, I have had the privilege of learning various frameworks and figuring out workarounds to make certain processes and functionalities easier.

Once of these functionalities is the creation of a rich-text editor using a RichTextBox. Creating a WYSIWYG rich-text editor in C# was harder than I initially anticipated, but once I figured out the basics, the rest was simple enough.

The Rich Text Editor uses .Net 4.6.1 and was done in Microsoft Visual Studio 2017 IDE.


 Download Project


This tutorial gives a step by step breakdown of how to go about it.

1. Create Application

Start up visual studio, click on the File  → New → Project or by clicking CTRL + SHIFT + N keyboard buttons. What you want, is a visual C#, windows desktop application. Select .NET framework 4 and above. Give any chosen name to your application.

In my case I named it RichTextEditor.

visual studio start page


2.  Creating the form

Add the following key references to your project if they do not already exist.

  1. System.Windows.Media.Imaging
  2. System.Windows.Controls.Primitives
  3. System.Windows.Controls.Ribbon

You  can do this by going to References → Add References → Assemblies. Search for them, tick and click ok.

Proceed to add the code below to your MainWindow.xaml form.

<Window x:Class="RichTextEditor.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:RichTextEditor"
        mc:Ignorable="d"
        WindowState="Maximized"
        Title="Editor Window" Height="450" Width="800" Icon="rte.ico">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="2*" />
            <RowDefinition Height="8*" />
        </Grid.RowDefinitions>
        <Ribbon Foreground="#333333" Margin="0,1,0,1">
            <RibbonTab Header="Complaint Report">
                <RibbonGroup Header="Quick Actions" Width="219" Margin="0,1,0,4" >
                    <RibbonButton x:Name="_btnPaste" Label="Paste" LargeImageSource="WYSIWYG/paste.png" ToolTip="Paste" Command="ApplicationCommands.Paste" CommandTarget="{Binding ElementName=_richTextBox}"/>

                    <RibbonButton x:Name="_btnCut" Label="Cut" SmallImageSource="WYSIWYG/cut.png" ToolTip="Cut" Command="ApplicationCommands.Cut" CommandTarget="{Binding ElementName=_richTextBox}"/>

                    <RibbonButton x:Name="_btnCopy" Label="Copy" SmallImageSource="WYSIWYG/copy.png" ToolTip="Copy" Command="ApplicationCommands.Copy" CommandTarget="{Binding ElementName=_richTextBox}"/>

                    <RibbonButton x:Name="_btnClear" Label="Clear" SmallImageSource="WYSIWYG/close.png" ToolTip="Clear" Command="EditingCommands.Delete" CommandTarget="{Binding ElementName=_richTextBox}"/>

                    <RibbonButton x:Name="_btnUndo" Label="Undo" SmallImageSource="WYSIWYG/undo.png " ToolTip="Undo" Command="ApplicationCommands.Undo" CommandTarget="{Binding ElementName=_richTextBox}"/>

                    <RibbonButton x:Name="_bntRedo" Label="Redo" SmallImageSource="WYSIWYG/redo.png" ToolTip="Redo" Command="ApplicationCommands.Redo" CommandTarget="{Binding ElementName=_richTextBox}"/>

                    <RibbonButton x:Name="_btnSelectAll" Label="Select All" ToolTip="Select All" Command="ApplicationCommands.SelectAll" SmallImageSource="WYSIWYG/select-all.png" CommandTarget="{Binding ElementName=_richTextBox}" />



                </RibbonGroup>
                <RibbonGroup Header="Style Document" Width="280" Margin="0,1,0,4">
                    <StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <ComboBox Name="cmbFontFamily" Width="160" SelectionChanged="cmbFontFamily_SelectionChanged" />
                            <ComboBox Name="cmbFontSize" Width="50" IsEditable="True" TextBoxBase.TextChanged="cmbFontSize_TextChanged" />
                        </StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <RibbonToggleButton Command="EditingCommands.ToggleBold" Name="btnBold" SmallImageSource="WYSIWYG/bold.png"/>

                            <RibbonToggleButton Command="EditingCommands.ToggleItalic" Name="btnItalic" SmallImageSource="WYSIWYG/italic.png"/>

                            <RibbonToggleButton Command="EditingCommands.ToggleUnderline" Name="btnUnderline" SmallImageSource="WYSIWYG/UnderLine.png" />


                            <RibbonToggleButton x:Name="SettingsFontColor" SmallImageSource="WYSIWYG/Color.png" Click="SettingsFontColor_Click" />
                            <RibbonButton x:Name="addImage" ToolTip="Add Image to Statement" SmallImageSource="WYSIWYG/image.png" Click="addImage_Click"/>
                            <RibbonRadioButton x:Name="_btnAlignLeft" ToolTip="Align Text Left" Command="EditingCommands.AlignLeft" CommandTarget="{Binding ElementName=_richTextBox}" SmallImageSource="WYSIWYG/LeftAlign.png" />
                            <RibbonRadioButton x:Name="_btnAlignCenter" Label="" ToolTip="Center" Command="EditingCommands.AlignCenter" CommandTarget="{Binding ElementName=_richTextBox}" SmallImageSource="WYSIWYG/center2.png" />
                            <RibbonRadioButton x:Name="_btnAlignRight" Label="" ToolTip="Align Text Right" Command="EditingCommands.AlignRight" CommandTarget="{Binding ElementName=_richTextBox}" SmallImageSource="WYSIWYG/RightAlign.png"/>
                            <RibbonRadioButton x:Name="_btnAlignJustify" Label="" Command="EditingCommands.AlignJustify" CommandTarget="{Binding ElementName=_richTextBox}" SmallImageSource="WYSIWYG/center.png"/>
                            <RibbonRadioButton x:Name="_btnNumbers" Label="" ToolTip="Numbering" Command="EditingCommands.ToggleNumbering" CommandTarget="{Binding ElementName=_richTextBox}" SmallImageSource="WYSIWYG/Numbered.png"/>
                            <RibbonRadioButton x:Name="_btnBullets" Label="" ToolTip="Bullets" Command="EditingCommands.ToggleBullets" CommandTarget="{Binding ElementName=_richTextBox}" SmallImageSource="WYSIWYG/bullets.png"/>

                        </StackPanel>
                    </StackPanel>
                </RibbonGroup>
            </RibbonTab>
        </Ribbon>

        <!--End Ribbon-->

        <!--Rich Text Box-->
        <RichTextBox SpellCheck.IsEnabled="True" x:Name="rtbEditor" Grid.Row="1" SelectionChanged="rtbEditor_SelectionChanged" AcceptsTab="True" VerticalScrollBarVisibility="Auto" Height="Auto" BorderBrush="#FFFF7F00" />
    </Grid>
</Window>


3. The soul of the project

Add this code to your MainWindow.xaml.cs form

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.IO;
using System.Windows.Documents;
using System.Windows.Input;
using Microsoft.Win32;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Windows.Controls.Primitives;
using System.Windows.Controls.Ribbon;
using System.Collections.ObjectModel;
using System.Text;

namespace RichTextEditor
{
    /// 
    /// Interaction logic forMainWindow.xaml
    /// 
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            cmbFontFamily.ItemsSource = Fonts.SystemFontFamilies.OrderBy(f => f.Source);
            cmbFontSize.ItemsSource = new List() { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 };
        }

        private void rtbEditor_SelectionChanged(object sender, RoutedEventArgs e)
        {
            object temp = rtbEditor.Selection.GetPropertyValue(Inline.FontWeightProperty);
            btnBold.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontWeights.Bold));
            temp = rtbEditor.Selection.GetPropertyValue(Inline.FontStyleProperty);
            btnItalic.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(FontStyles.Italic));
            temp = rtbEditor.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
            btnUnderline.IsChecked = (temp != DependencyProperty.UnsetValue) && (temp.Equals(TextDecorations.Underline));

            temp = rtbEditor.Selection.GetPropertyValue(Inline.FontFamilyProperty);
            cmbFontFamily.SelectedItem = temp;
            temp = rtbEditor.Selection.GetPropertyValue(Inline.FontSizeProperty);
            cmbFontSize.Text = temp.ToString();

            UpdateVisualState();
        }

        private void Open_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
            if (dlg.ShowDialog() == true)
            {
                FileStream fileStream = new FileStream(dlg.FileName, FileMode.Open);
                TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
                range.Load(fileStream, DataFormats.Rtf);
            }
        }

        private void Save_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Filter = "Rich Text Format (*.rtf)|*.rtf|All files (*.*)|*.*";
            if (dlg.ShowDialog() == true)
            {
                FileStream fileStream = new FileStream(dlg.FileName, FileMode.Create);
                TextRange range = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
                range.Save(fileStream, DataFormats.Rtf);
            }
        }

        private void cmbFontFamily_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (cmbFontFamily.SelectedItem != null)
                rtbEditor.Selection.ApplyPropertyValue(Inline.FontFamilyProperty, cmbFontFamily.SelectedItem);
        }

        private void cmbFontSize_TextChanged(object sender, TextChangedEventArgs e)
        {
            rtbEditor.Selection.ApplyPropertyValue(Inline.FontSizeProperty, cmbFontSize.Text);
        }


        void UpdateItemCheckedState(ToggleButton button, DependencyProperty formattingProperty, object expectedValue)
        {
            object currentValue = rtbEditor.Selection.GetPropertyValue(formattingProperty);
            button.IsChecked = (currentValue == DependencyProperty.UnsetValue) ? false : currentValue != null && currentValue.Equals(expectedValue);
        }

        void ApplyPropertyValueToSelectedText(DependencyProperty formattingProperty, object value)
        {
            if (value == null)
                return;
            rtbEditor.Selection.ApplyPropertyValue(formattingProperty, value);
        }

        private void UpdateToggleButtonState()
        {
            UpdateItemCheckedState(_btnAlignLeft, Paragraph.TextAlignmentProperty, TextAlignment.Left);
            UpdateItemCheckedState(_btnAlignCenter, Paragraph.TextAlignmentProperty, TextAlignment.Center);
            UpdateItemCheckedState(_btnAlignRight, Paragraph.TextAlignmentProperty, TextAlignment.Right);
            UpdateItemCheckedState(_btnAlignJustify, Paragraph.TextAlignmentProperty, TextAlignment.Right);
        }

        private void UpdateItemCheckedState(RibbonButton btnAlignLeft, DependencyProperty textAlignmentProperty, TextAlignment left)
        {
            throw new NotImplementedException();
        }

        private void UpdateSelectionListType()
        {
            Paragraph startParagraph = rtbEditor.Selection.Start.Paragraph;
            Paragraph endParagraph = rtbEditor.Selection.End.Paragraph;
            if (startParagraph != null && endParagraph != null && (startParagraph.Parent is ListItem) && (endParagraph.Parent is ListItem) && object.ReferenceEquals(((ListItem)startParagraph.Parent).List, ((ListItem)endParagraph.Parent).List))
            {
                TextMarkerStyle markerStyle = ((ListItem)startParagraph.Parent).List.MarkerStyle;
                if (markerStyle == TextMarkerStyle.Disc) //bullets  
                {
                    _btnBullets.IsChecked = true;
                }
                else if (markerStyle == TextMarkerStyle.Decimal) //number  
                {
                    _btnNumbers.IsChecked = true;
                }
            }
            else
            {
                _btnBullets.IsChecked = false;
                _btnNumbers.IsChecked = false;
            }
        }
        private void UpdateVisualState()
        {
            UpdateToggleButtonState();
            UpdateSelectionListType();
        }


        public void selectImg(RichTextBox rc)
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Filter = "Image files (*.jpg, *.jpeg,*.gif, *.png) | *.jpg; *.jpeg; *.gif; *.png";
            var result = dlg.ShowDialog();
            if (result.Value)
            {
                Uri uri = new Uri(dlg.FileName, UriKind.Relative);
                BitmapImage bitmapImg = new BitmapImage(uri);
                Image image = new Image();
                image.Stretch = Stretch.Fill;
                image.Width = 250;
                image.Height = 200;
                image.Source = bitmapImg;
                var tp = rc.CaretPosition.GetInsertionPosition(LogicalDirection.Forward);
                new InlineUIContainer(image, tp);
            }
        }
        private void addImage_Click(object sender, RoutedEventArgs e)
        {
            selectImg(rtbEditor);
        }

        private void fontcolor(RichTextBox rc)
        {
            var colorDialog = new System.Windows.Forms.ColorDialog();
            if (colorDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                var wpfcolor = Color.FromArgb(colorDialog.Color.A, colorDialog.Color.R, colorDialog.Color.G, colorDialog.Color.B);
                TextRange range = new TextRange(rc.Selection.Start, rc.Selection.End);
                range.ApplyPropertyValue(FlowDocument.ForegroundProperty, new SolidColorBrush(wpfcolor));
            }
        }


        private void SettingsFontColor_Click(object sender, RoutedEventArgs e)
        {
            fontcolor(rtbEditor);
        }

        //Save as word document

        private void btnSavetoFolder_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog savefile = new SaveFileDialog();
            // set a default file name  
            savefile.FileName = "unknown.doc";
            // set filters - this can be done in properties as well  
            savefile.Filter = "Document files (*.doc)|*.doc";
            if (savefile.ShowDialog() == true)
            {
                TextRange t = new TextRange(rtbEditor.Document.ContentStart, rtbEditor.Document.ContentEnd);
                FileStream file = new FileStream(savefile.FileName, FileMode.Create);
                t.Save(file, System.Windows.DataFormats.Rtf);
                file.Close();
            }


        }
    }
}

Now click on the Start button to build your project and voila! You now have your Rich Text Editor.