Udostępnij za pośrednictwem


Flyweight RichEdit Controls

Back when RichEdit 2.0 was being designed, Christian Fortini had a dream that every text string in a Forms^3 form or dialog would be a light-weight RichEdit control. That way there would be no display glitches in switching from static display to edit mode and text could be copied, edited, and made accessible. To keep the controls as light as possible, there would be several different flavors: plain vs. rich, single-line vs. multiline, and the controls could be windowless. Many properties would be maintained by the RichEdit host and could be shared between RichEdit instances if the client so desired. If a client wanted more functionality, the instance size would grow as needed, but simple text would not have to pay for more advanced features. Largely, RichEdit 2.0 succeeded in delivering this dream.

Over the years, the basic edit control has grown in size to accommodate greatly increased functionality. So now, even a plain-text, single-line control is pretty large (~ 1 KB). To reduce the instance size, a RichEdit 8 client can benefit from a concept called “flyweight RichEdit controls”. To get greatly reduced size, a client has to use many flyweight controls so that the size of the “parent” edit instance, a regular RichEdit ITextServices control, is amortized over the flyweights. A flyweight control is stored in its own RichEdit story (ITextStory, part of TOM 2) and shares the properties of the parent ITextServices. The flyweight controls can be “edited” simultaneously using ITextRange2’s and displayed independently of one another. A given ITextRange2 accesses one and only one story, although a story can have multiple ranges or none at all. In addition, one control at a time can be active, that is, it receives the keyboard and mouse input.

For example, OneNote has a pool of RichEdit ITextServices instances, which it applies to paragraphs, one paragraph per instance. The instances are recycled and a large OneNote document has many more paragraphs than RichEdit instances. Instead, each paragraph in a OneNote section could be a flyweight RichEdit control housed within a single ITextServices for the section. This approach should be considerably more performant than multiple relatively large ITextServices instances.

To minimize the size of flyweight controls, they only contain rich-text structures that are actually used. For example if a control only has default formatting, no character or paragraph format run arrays exist. Some languages like Thai need cluster and word breaking, but if a story doesn’t contain text of such languages, the cluster and word breaking structures are omitted. Similarly if there are no embedded objects or images, there’s no object array.

Multiple stories need a stories collection, since among other things before the parent ITextServices instance is deleted, it needs to zombie any unreleased ITextStory pointers and delete the story data. The stories collection is accessed externally via methods in ITextDocument2. RichEdit 6.0 (Office 2007) and later versions have a multistory facility (see ITextStoryRanges), but it is not sufficiently lightweight and convenient for clients like OneNote, so we added the ITextStory approach in RichEdit 8. The ITextStoryRanges facility implements the original TOM vision of stories as ranges and is oriented towards stories as used in Word. The current emphasis on a multitude of lightweight instances leads to a different model. In particular, a range adds extra instance size, so it saves space to instantiate ranges only when they are needed to manipulate stories.

The RichEdit 8 story model has a single display that’s shared between the stories. This complicates the host ITextHost implementation, since the host has to know which story owns the display whenever a redraw occurs. A more advanced model would offer multiple displays and an enhanced ITextHost interface with methods that specify the ITextStory to be displayed. In addition the methods should have a display index to identify the view in case a story has multiple views.

Multiple stories were originally added to RichEdit 6.0 for the math build-up facility. In fact, the TOM2 ITextStrings interface handles a collection of rich-text strings useful for manipulating rich text, such as converting linearly formatted math expressions into built-up form and vice versa. The ITextStrings strings collection is efficiently implemented by concatenating the strings together in a scratch story and maintaining an array of the string counts identifying the strings.

RichEdit also uses an ITextStrings scratch story to convert MathML into the internal math representation and it uses a scratch story to copy rich text when the text in the original copy selection gets changed. And there’s the main flyweight story that’s used by default. So all clients benefit from flyweight controls even those that don’t use the controls explicitly via the ITextStory interface.

Comments

  • Anonymous
    December 10, 2014
    Why doesn't Microsoft improve documentation and examples for the developers to use these features in their applications? Or is it already there?

  • Anonymous
    December 11, 2014
    The ITextDocument2 and ITextStory interfaces are documented in MSDN. To get a new story, call ITextDocument2::GetNewStory(). You may want to mark it as a scratch story by calling ITextStory::SetType(tomScratchStory). Admittedly a bunch of examples would help.