With v15.1, we'll introduce a new Layout API for our WinForms & WPF Rich Text Editor - objects, properties and methods that allow you traverse the document layout tree and access layout elements. The Layout API is located in the DevExpress.RichEdit.v15.1.Core.dll assembly, and is available for both WinForms and WPF Rich Editors, as well as for Rich Edit Document Server.
This new API can be used to control all aspects of document layout. In previous versions, the DevExpress Rich Edit core gave access only to objects related to the document model - so that you have more control over the logical structure of the document. The document’s visual appearance, however, was not available...even for analysis. To understand at which page location and on which line a particular word is located was a problem without an easy solution. With the new Layout API all of this changes dramatically....
Use Case – Pagination
Let's look at a simple use-case...that the first document table must always be on the second page. When a document is modified, we need to check that this requirement is met and alert the user if it has not. The code snippet below uses the Layout API to address this issue.
void CheckLayout()
{
DevExpress.XtraRichEdit.API.Native.Table table = richEditControl1.Document.Tables.First;
if (table != null)
{
// Obtain the layout element related to the table.
LayoutTable ltable = richEditControl1.DocumentLayout.GetElement<LayoutTable>(table.Range.Start);
// Obtain zero-based page index of the page containing the layout element.
int pageIndex = this.richEditControl1.DocumentLayout.GetPageIndex(ltable);
// Check whether the layout element is located at the second page.
if (pageIndex != 1)
MessageBox.Show("The first table is not on the page 2. Review pagination.");
}
}
Technical Breakdown...
The document layout model in the Layout API is a hierarchical tree-like structure. Each node in a tree is an instance of a class which implements a base interface named LayoutElement. A class representing a specific element is named after the element in question with the addition of the prefix Layout or the suffix Box. There are LayoutPage, LayoutHeader, LayoutPageArea, LayoutFooter, ... BookmarkBox, PlainTextBox etc. A layout element may also include a related range in the document.
The main entry point of the Layout API is the RichEditControl.DocumentLayout property. This property provides access to the DocumentLayout object containing basic properties and methods for work with the hierarchy of document layout objects. After any change in text or formatting, the document's layout is recalculated and the DocumentLayout.DocumentFormatted event fires. You can call the CheckLayout() method shown above in the DocumentFormatted event handler to validate changes.
The DocumentFormatted event handler is running in UI thread. To avoid concurrency issues, always execute the CheckLayout method asynchronously in UI thread using the RichEditControl.BeginInvoke method, as illustrated in the following code:
richEditControl1.BeginInvoke(newAction(() =>
{
CheckLayout();
}));
How to Start...
To obtain access to elements which constitute the document’s layout, create a Visitor object (object that implements a Visitor pattern) and navigate to a node of the document layout tree. Subsequently your visitor will traverse down the tree and visit every child node. For each node it may call a specific method. At this time, you can only get information on the visited node. However, future API versions will allow you to hide the element, change the order in which elements are rendered or draw custom graphics.
The Visitor object must be a descendant of the DevExpress.XtraRichEdit.API.Layout.LayoutVisitor abstract class. For each layout element the LayoutVisitor class has a virtual method, thus the descending class must override this method to obtain access to a particular element type. The following code is a visitor class which gets access to every line of text in the main body of the document. It then prints visual coordinates and document position on which the line starts. A line in the document is a LayoutRow object in terms of Layout API. The VisitRow method will be automatically called for each line.
classMyDocumentLayoutVisitor : DevExpress.XtraRichEdit.API.Layout.LayoutVisitor
{
protectedoverridevoid VisitRow(LayoutRow row)
{
if (row.GetParentByType<LayoutPageArea>() != null)
System.Diagnostics.Debug.WriteLine("This row is located at X: {0}, Y: {1}, related range starts at {2}",
row.Bounds.X, row.Bounds.Y, row.Range.Start);
// Call the base method to walk down the tree to the child elements of the Row.
// If you don't need them, comment out the next line.
base.VisitRow(row);
}
}
To start traversing the tree, pass the node (in this situation, it is the document’s page, the LayoutPage object) of the document’s layout tree to the Visit method of the MyDocumentLayoutVisitor class.
Summing up, the resulting code for calling the visitor looks as follows:
richEditControl1.DocumentLayout.DocumentFormatted += DocumentLayout_DocumentFormatted;
// ...
privatevoid DocumentLayout_DocumentFormatted(object sender, EventArgs e)
{
richEditControl1.BeginInvoke(newAction(() =>
{
int pageCount = richEditControl1.DocumentLayout.GetFormattedPageCount();
for (int i = 0; i < pageCount; i++)
{
MyDocumentLayoutVisitor visitor = newMyDocumentLayoutVisitor();
visitor.Visit(richEditControl1.DocumentLayout.GetPage(i));
}
}));
}
Example of the Visitor Approach
In the XtraRichEdit Layout API demo, the document layout structure is visualized by populating aTreeView control with layout elements. Clicking a node selects the related document range. The application window looks as follows:
The selected node is the ParagraphMarkBox element located on the first line (element LayoutRow) of the main page area (element LayoutPageArea). The main page area is located on the third page (element LayoutPage) of the document.