Layout API

  • 6 minutes to read

This document introduces the Rich Text Editor Layout API - objects, properties and methods that allow you traverse the document layout tree and access layout elements.

Layout API Overview

The Layout API is located in the DevExpress.RichEdit.v20.2.Core.dll assembly, and is available for both WinForms and WPF Rich Text Editor, as well as for Word Processing Document API. It can be used to arbitrarily display layout elements and draw graphics within the element boundaries (expected in future versions).

Layout API Object Model

The document layout model in the Layout API is a hierarchical tree-like structure. Each node in a tree is an instance of a certain class which implements a base interface named LayoutElement. A class representing a particular element is named after that element with addition of the prefix Layout or a suffix Box. There are LayoutPage, LayoutHeader, LayoutPageArea, LayoutFooter, ... BookmarkBox, PlainTextBox etc. A layout element may or may not have a related RangedLayoutElement.Range in the document.

The structure of a sample document is shown in the picture below. The document consists of three pages, has a header and a footer, the first page has a floating text box and a floating picture. The text is organized in one column, the first page has only three lines of text. The first line of text includes a floating object's anchor and five plain text boxes (a plain text box can be a space, a text with uniform formatting or a paragraph mark).

DocumentLayoutTree

TIP

Call the HitTestManager.HitTest method to obtain the RichEditHitTestResult instance containing information about the element located under the test point. Refer to the How to: Determine the Document Element under the Mouse Pointer topic for details.

Starting Point

The main entry point of the Layout API in WPF Rich Editor is the RichEditControl.DocumentLayout property. This property provides access to the DocumentLayout object containing basic properties and methods for working with the hierarchy of the document layout objects. After any changes in text or formatting, the document layout is recalculated and the DocumentLayout.DocumentFormatted event fires.

NOTE

The DocumentFormatted event handler is running in a background thread. To avoid concurrency issues, execute the code which affects the document or interacts with the user asynchronously in a UI thread using the RichEditControl.BeginInvoke method.

Traversing Layout Tree

The Iterator and the Visitor are independent at the document layout level. The LayoutIterator and LayoutVisitor allow you to collect information about document element's location (bounds, nearest element, etc.) and range.

The table below compares LayoutIterator and LayoutVisitor features. This comparison allows you to decide what to use depending on your current task.

  LayoutVisitor LayoutIterator
Travel Direction Down Up and Down (using MoveNext and MovePrevious methods).
Can Iterate Only a Specific Layout Level No Yes. Pass the one of the LayoutLevel values to the Move.. method to complete the task.
Visits Complex Layout Elements True. False. Create a new LayoutIterator when visiting complex elements (header/footer, floating objects, comments)

Code samples below show how to use LayoutIterator and LayoutVisitor to count lines in the document's main body. The message box displays the result amount, as shown on the image.

CountLines

Use LayoutIterator

//Create a new LayoutIterator object
LayoutIterator iterator = new LayoutIterator(richEditControl1.DocumentLayout);
int documentRows = 0;

    //Call the MoveNext method with the passed
    //LayoutLevel.Row value to process page lines:
    while (iterator.MoveNext(LayoutLevel.Row))
    {
       documentRows++;
    }

//Show the result number in the message box 
MessageBox.Show(String.Format("The document contains {0} lines", documentRows));

Use LayoutVisitor

int rowCount = 0;
int pageCount = richEditControl1.DocumentLayout.GetPageCount();

    //Create a new Visitor object for every document page
    for (int i = 0; i < pageCount; i++)
    {
        MyLayoutVisitor visitor = new MyLayoutVisitor();

        //Call the Visit method for the current layout page and count its lines
         visitor.Visit(richEditControl1.DocumentLayout.GetPage(i));
        visitor.Visit(richEditControl1.DocumentLayout.GetPage(i));
        rowCount += visitor.DocumentRows;
    }

//Show the result in the message box
MessageBox.Show(String.Format("The document contains {0} lines", rowCount));

//Create a LayoutVisitor class descendant: 
public class MyLayoutVisitor : LayoutVisitor
{
    public int DocumentRows = 0;

    //Override the VisitRow method to count lines 
    //located only in main document body,
    //i.e. lines located on the LayoutPageArea level
    protected override void VisitRow(LayoutRow row)
    {
         if (row.GetParentByType<LayoutPageArea>() != null)
        {   
            DocumentRows++;
            base.VisitRow(row);
        }
    }
}

Custom Draw

The RichEditControl.BeforePagePaint event allows you to specify a descendant of the PagePainter class to implement custom draw for layout elements.

The following code snippet illustrates how to custom draw the PlainTextBox and LayoutFloatingPicture elements.

This code snippet illustrates the implementation of a PagePainter descendant. It draws color rectangles in place of the words and labels a floating picture with a text string drawn over it.

PagePainter

View Example

class MyLayoutPainter : PagePainter {
    public override void DrawPlainTextBox(PlainTextBox plainTextBox) {
        Canvas.DrawRectangle(new RichEditPen(System.Windows.Media.Color.FromRgb(141, 179, 226)), plainTextBox.Bounds);
        //base.DrawPlainTextBox(plainTextBox);
    }
    public override void DrawFloatingPicture(LayoutFloatingPicture floatingPicture) {
        base.DrawFloatingPicture(floatingPicture);
        Rectangle bounds = floatingPicture.Bounds;
        Point startPoint = new Point(bounds.X + 40, bounds.Y + bounds.Height - 120);
        Canvas.DrawString("Approved", new Font("Courier New", 26), new RichEditBrush(System.Windows.Media.Colors.DarkRed), startPoint);
    }
}