Windows Presentation Foundation (WPF) is the graphics and user interface engine that debuted in .NET 3.0. WPF's FlowDocument class lets you easily manipulate documents containing rich content. FlowDocuments can be displayed in FlowDocumentReaders and FlowDocumentPageViewers, and are editable, with built-in spellcheck, if they're nested in RichTextBox controls. FlowDocuments can include all the content you'd expecttext, graphics, bullet and numbered lists, tables, and even hyperlinks. Additionally, since FlowDocuments can contain objects derived from the UIElement class, interactive GUIs can be embedded in FlowDocumentsbuttons, radio buttons, custom controls, and user controls, for instance. (When embedding GUIs in RichTextBoxes, set the IsDocumentEnabled property to True.)
Unfortunately, WPF 3.5 does not fully support custom GUIs in FlowDocuments. In particular, drag-and-drop, cut-and-paste, deserialization, and printing require extra coding for full-fledged embedded GUI support. In this article, I show how to insert UserControls in FlowDocuments to make deserialization, clipboard operations, and printing work. I've included the full source code for two applications that demonstrate the issuesand workaroundswith embedded GUIs (available online; see "Resource Center," page 5). To use the code, load WPFCompoundDocuments.sln in Visual Studio 2008 or Visual C# 2008 Express Edition, and build and run the User Control Demo program (Figure 1). The User Control Demo illustrates the problems with UserControls in FlowDocuments.
Before pressing the Insert UserControl button, type some text in the RichTextBox on the left. Press Serialize and the document's contents are serialized to a string. Then press Clear and Deserialize, and you see the document's text restored. So far, so good. Now press Insert UserControl, Serialize, and Deserialize. When you press Deserialize, a XamlParseException is thrown, with this message:
Cannot set Name attribute value "timestampTextBlock" on element "TextBlock". "TextBlock" is under the scope of element "TimeStampControl", which already had a name registered when it was defined in another scope.
One solution to this problem is to not use a Name attribute for any child element in a UserControl's XAML markup. However, for all but the simplest UserControls, this would require too much effort. For example, the PhotoControl (see PhotoControl.xaml and PhotoControl.xaml.cs) manipulates several XAML elements by name (for instance, slideShowCheckBox, displayImage, imageListBox, and so on). The XAML element names could be removed, and each element could be searched for in the control's XAML markup, but there's a better solution. Keep reading!
Now try to copy-and-paste. Press Clear and press Insert UserControl. A TimeStampControl is inserted. Right-click TimeStampControl and select Copy. Notice that the UserControl's markup does not appear in the selection's markup in the Details window. Press Clear and right-click RichTextBox and select Paste. Oops! No UserControl is pasted because it was filtered out of the selection before it was copied to the clipboard. Fortunately, each of these issues can be handled without too much code. To see the workarounds in action, run the WPF Compound Documents app (Figure 2).
The WPF Compound Documents app can deserialize, copy/paste, and print UserControls. Here's how it works.