WinForms Spreadsheet – How to Create a Rich Text Editor for Worksheet Cells

In our most recent update (v19.1), we added rich text support to our WinForms Spreadsheet control. It now displays rich text within worksheet cells. As you may already know, you can also print and export documents with rich text to PDF. Though our WinForms Spreadsheet control does not allow users to apply rich formatting to cell text within its UI, you can use the following approach to create your own rich text editor for Spreadsheet cells.

This example uses the DevExpress Rich Text Editor control to format cell text. The Rich Text Edit Form appears when a user selects ‘Set Rich Text’ within the Spreadsheet control’s context menu or starts editing a cell that already contains rich text.

How to Create a Custom Rich Text Edit Form

Create a custom form within your Spreadsheet application and add the Rich Text Editor with the Font bar items to the form.

Use the RichEditControl.Options.Behavior properties to disable the Open, Save, Save As, Print, ShowPopupMenu, and Zoom commands in the form. In addition, set the RichEditControl.Options.DocumentCapabilities properties to disable all document features except for CharacterFormatting, Paragraphs, and Undo.

Add a custom item to the Spreadsheet cell’s context menu to invoke the form. Use the SpreadsheetControl.PopupMenuShowing event for this purpose.

private void spreadsheetControl_PopupMenuShowing(object sender, 
												 PopupMenuShowingEventArgs e) 
{
	if (e.MenuType == DevExpress.XtraSpreadsheet.SpreadsheetMenuType.Cell) 
    {
    	Cell activeCell = spreadsheetControl.ActiveCell;
        // If a cell is empty or contains a text value,
        // add the 'Set Rich Text' item to its context menu.
        if (activeCell.Value.IsEmpty || (!activeCell.HasFormula && activeCell.Value.IsText))
        {
        	var setRichTextItem = new SpreadsheetMenuItem("Set Rich Text", 
													new EventHandler(SetRichTextItemClick));
            e.Menu.Items.Add(setRichTextItem);
        }
    }
}

private void SetRichTextItemClick(object sender, EventArgs e)
{
	using (var richEditForm = new RichTextEditForm(spreadsheetControl.ActiveCell))
    	richEditForm.ShowDialog();
}

Handle the SpreadsheetControl.CellBeginEdit event to display the form when a user starts to edit a cell with rich text.

private void spreadsheetControl_CellBeginEdit(object sender, 
                                              SpreadsheetCellCancelEventArgs e)
{
	if (e.Cell.HasRichText)
    {
    	e.Cancel = true;
        using (var richEditForm = new RichTextEditForm(e.Cell)) 
        	richEditForm.ShowDialog();
    }
}

Display Cell Text in the Rich Text Editor When a User Opens the Custom Form

To display cell text in the Rich Text Editor, you need to transform the Spreadsheet control’s document model into the RichEdit control’s document model (for individual cell values).

Use the Spreadsheet API to split a cell’s rich text into runs (text regions with unique formatting), and retrieve text and format settings for all runs in the run collection.

Call the Rich Text Editor’s InsertText method to add each text run to a document. Use CharacterProperties to format the text based on font settings for corresponding runs.

public partial class RichTextEditForm : DevExpress.XtraEditors.XtraForm
{
	private Cell cell;
    
    public RichTextEditForm(Cell cell)
    {
    	InitializeComponent();
        this.cell = cell;
        InitRichEditControl();
    }
    
    private void InitRichEditControl()
    {
        richEditControl1.BeginUpdate();
        if (cell.HasRichText)
        {
        	// Retrieve rich text from a cell.
            RichTextString richText = cell.GetRichText();
            // Obtain a document loaded into the Rich Text Editor.
            Document document = richEditControl1.Document;
            foreach (RichTextRun run in richText.Runs)
            {
            	// Append the current run's text to the RichEditControl document.
                DocumentRange range = document.InsertText(document.Range.End, run.Text);
                // Create CharacterProperties based on the run's font settings
                // and format the inserted text.
                CharacterProperties cp = document.BeginUpdateCharacters(range);
                cp.Bold = run.Font.Bold;
                cp.ForeColor = run.Font.Color;
                cp.Italic = run.Font.Italic;
                cp.FontName = run.Font.Name;
                cp.FontSize = (float)run.Font.Size;
                cp.Strikeout = run.Font.Strikethrough ? StrikeoutType.Single 
                									  : StrikeoutType.None;
                switch (run.Font.Script)
                {
                	case ScriptType.Subscript:
                    	cp.Subscript = true;
                        break;
                    case ScriptType.Superscript:
                    	cp.Superscript = true;
                        break;
                    default:
                        cp.Subscript = false;
                        cp.Superscript = false;
                        break;
               	}
                switch (run.Font.UnderlineType)
                {
                	case DevExpress.Spreadsheet.UnderlineType.Single:
                    	cp.Underline = 
                        	DevExpress.XtraRichEdit.API.Native.UnderlineType.Single;
                        break;
                    case DevExpress.Spreadsheet.UnderlineType.Double:
                    	cp.Underline = 
                        	DevExpress.XtraRichEdit.API.Native.UnderlineType.Double;
                        break;
                    default:
                    	cp.Underline = 
                        	DevExpress.XtraRichEdit.API.Native.UnderlineType.None;
                        break;
                }
            	document.EndUpdateCharacters(cp);
        	}
        }
		else
        	richEditControl1.Text = cell.DisplayText;
        richEditControl1.EndUpdate();
    }
}

Post Changes from the Rich Text Editor to a Worksheet Cell When a User Closes the Custom Form

To submit changes when the form is closed, transform the RichEdit control's document model back to the Spreadsheet’s document model and create a cell value.

Implement a custom Document Visitor to split the RichEdit control’s document into text runs and transform each run into the Spreadsheet’s RichTextRun object. Use the RichTextString.AddTextRun method to combine all RichTextRun objects into the resulting RichTextString.

public class CustomDocumentVisitor : DocumentVisitorBase
{

	private int endPosition;
    
    public RichTextString RichText { get; }

    public CustomDocumentVisitor(int endPos)
    {
    	RichText = new RichTextString();
        endPosition = endPos;
    }
    
    public override void Visit(DocumentText text)
    {
    	base.Visit(text);
        RichTextRunFont runFont = CreateRichTextRun(text.TextProperties);
        RichText.AddTextRun(text.Text, runFont);
    }
    
    public override void Visit(DocumentParagraphEnd paragraphEnd)
    {
    	base.Visit(paragraphEnd);
        if (endPosition - 1 != paragraphEnd.Position)
       	{
        	RichTextRunFont runFont = CreateRichTextRun(paragraphEnd.TextProperties);
            RichText.AddTextRun(paragraphEnd.Text, runFont);
        }
    }

    private RichTextRunFont CreateRichTextRun(ReadOnlyTextProperties tp)
    {
    	// Create a new RichTextRunFont object based on 
        // font attributes of the currently processed text.
        var runFont = new RichTextRunFont(tp.FontName, tp.DoubleFontSize / 2, tp.ForeColor)
        {
        	Bold = tp.FontBold,
            Italic = tp.FontItalic,
            Strikethrough = tp.StrikeoutType == StrikeoutType.Single
        };
        switch (tp.Script)
        {
        	case CharacterFormattingScript.Subscript:
            	runFont.Script = ScriptType.Subscript;
                break;
            case CharacterFormattingScript.Superscript:
            	runFont.Script = ScriptType.Superscript;
                break;
            default:
            	runFont.Script = ScriptType.None;
                break;
        }
		switch (tp.UnderlineType)
        {
        	case DevExpress.XtraRichEdit.API.Native.UnderlineType.Single:
            	runFont.UnderlineType = DevExpress.Spreadsheet.UnderlineType.Single;
                break;
            case DevExpress.XtraRichEdit.API.Native.UnderlineType.Double:
            	runFont.UnderlineType = DevExpress.Spreadsheet.UnderlineType.Double;
                break;
            default:
            	runFont.UnderlineType = DevExpress.Spreadsheet.UnderlineType.None;
                break;
        }
        return runFont;
    }
}

Handle the Click event of our Rich Text Edit Form’s OK button. Create the CustomDocumentVisitor and DocumentIterator instances within the event handler, and iterate over the RichEdit control’s document to combine its text regions into the rich text string. Call the Range.SetRichText method to assign the resulting text to a cell.

private void OKButton_Click(object sender, EventArgs e)
{
	var visitor = new CustomDocumentVisitor(richEditControl1.Document.Range.End.ToInt());
    var iterator = new DocumentIterator(richEditControl1.Document, true);
    while (iterator.MoveNext())
    {
    	iterator.Current.Accept(visitor);
    }
    RichTextString richText = visitor.RichText;
    // Assign the resulting rich text to a cell.
    cell.SetRichText(richText);
    // If a document contains multiple paragraphs,
    // wrap text in a cell.
    if (richEditControl1.Document.Paragraphs.Count > 1)
    	cell.Alignment.WrapText = true;
}

You can download a complete sample project from https://github.com/DevExpress-Examples/how-to-edit-rich-text-in-spreadsheetcontrol.

If you have questions, feel free to contact us via the DevExpress Support Center. We’d love to hear your feedback!

1 comment(s)
Jeff Stiegler
Jeff Stiegler
The idea of a pop-up rich editor is something I can really use...not so much for spreadsheets but for other parts of my app.  Thank you so much.
19 July, 2019

Please login or register to post comments.