One of the most important requirements in modern applications is the ability to manage documents. WPF offers the System.Windows.Documents
namespace that exposes objects that enable creating flexible and
dynamic documents that can adapt their layout dynamically to the user
interface. These kinds of documents take advantage of the Clear Type™
technology and are hosted inside FlowDocument objects. A FlowDocument is composed of Paragraph
objects where you can place and format your text. Paragraphs are
powerful because they enable adding figures, bulleted lists, fully
functional hyperlinks, and text formatting. To present and browse a flow
document, you need to add a FlowDocumentReader
control to the user interface. Flexibility and dynamicity are just two
benefits of a larger number. Another cool feature in flow documents is
that users can interact with documents ,
so they can add annotations and highlights that can be stored to disk
for later reuse. Annotations are provided by the System.Windows.Annotations namespace that needs to be imported at the XAML level. The goals of next code example are
Illustrating how you can create flow documents
Illustrating how you can add and format text within flow documents
Implementing features for adding annotations to documents and saving them to disk
Add a new Window to the current one, setting it as the startup page. When ready, write the XAML code shown in Listing 1 that implements the UI side of the application. The code is explained at the end of the listing.
Listing 1. Implementing Flow Documents
<Window x:Class="ManipulatingDocuments" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ann="clr-namespace:System.Windows.Annotations;assembly=PresentationFrame- work" Title="ManipulatingDocuments" Height="480" Width="600"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="40"/> </Grid.RowDefinitions>
<StackPanel Grid.Row="1" Orientation="Horizontal"> <StackPanel.Resources> <Style x:Key="ButtonStyle" TargetType="Button"> <Setter Property="Width" Value="100"/> <Setter Property="Height" Value="30"/> <Setter Property="Margin" Value="5"/> </Style> </StackPanel.Resources>
<Button Command="ann:AnnotationService.CreateTextStickyNoteCommand" CommandTarget="{Binding ElementName=FlowReader1}" Style="{StaticResource ButtonStyle}"> Add note</Button> <Separator/> <Button Command="ann:AnnotationService.CreateInkStickyNoteCommand" CommandTarget="{Binding ElementName=FlowReader1}" Style="{StaticResource ButtonStyle}"> Add Ink </Button> <Separator/> <Button Command="ann:AnnotationService.DeleteStickyNotesCommand" CommandTarget="{Binding ElementName=FlowReader1}" Style="{StaticResource ButtonStyle}"> Remove note </Button> <Separator/> <Button Command="ann:AnnotationService.CreateHighlightCommand" CommandTarget="{Binding ElementName=FlowReader1}" Style="{StaticResource ButtonStyle}"> Highlight </Button> <Separator/> <Button Command="ann:AnnotationService.ClearHighlightsCommand" CommandTarget="{Binding ElementName=FlowReader1}" Style="{StaticResource ButtonStyle}"> Remove highlight </Button> </StackPanel>
<FlowDocumentReader Grid.Row="0" BorderThickness="2" Name="FlowReader1"> <FlowDocument Name="myDocument" TextAlignment="Justify" IsOptimalParagraphEnabled="True" IsHyphenationEnabled="True" IsColumnWidthFlexible="True" ColumnWidth="300" ColumnGap="20"> <Paragraph FontSize="36" FontWeight="Bold" FontStyle="Oblique">Chapter 31</Paragraph> <Paragraph FontSize="24" FontWeight="Bold">Introducing WPF</Paragraph> <Paragraph> Windows Presentation Foundation relies on a layered architecture that is represented in Figure 31.1. The first layer is the Windows operating system. The second layer is constituted by the combination of two communicating layers: User32, which is the part of the operating system responsible for exchanging messages with applications, and the DirectX libraries which are the real power of WPF. <!— Add other text here.... —> <Figure Width="300"> <BlockUIContainer> <StackPanel> <!—Replace the image file with a valid one—> <Image Source="/DocumentsAndMedia;component/Images/31fig01.tif" Width="200" Height="300" Stretch="Fill" /> <Separator></Separator> <TextBlock VerticalAlignment="Center" Width="220" TextWrapping="Wrap" FontSize="10" FontStyle="Italic"> Figure 31.1 – WPF architecture </TextBlock> </StackPanel> </BlockUIContainer> </Figure> <Bold>PresentationFramework</Bold> exposes namespaces and classes through a complex hierarchy of inheritance, where the root class is of course System.Object. Such hierarchy provides the infrastructure for the user interface elements. This hierarchy is composed by the following list of classes, where each class inherits from the previous one: </Paragraph> <List> <ListItem> <Paragraph FontFamily="Courier New">System.Object</Paragraph> </ListItem> <ListItem> <Paragraph FontFamily="Courier New"> System.Threading.DispatcherObject</Paragraph> </ListItem> <ListItem> <Paragraph FontFamily="Courier New"> System.Windows.DependencyObject</Paragraph> </ListItem> <ListItem> <Paragraph FontFamily="Courier New"> System.Windows.Media.Visual</Paragraph> </ListItem> </List> <Paragraph> The <Hyperlink NavigateUri="http://msdn.microsoft.com/en- us/library/ms750441(VS.100).aspx #System_Threading_DispatcherObject"> System.Threading.DispatcherObject</Hyperlink> is responsible for threading and messages which WPF relies on. The dispatcher takes advantage of the User32 messages for performing cross thread calls. </Paragraph> </FlowDocument> </FlowDocumentReader> </Grid> </Window>
|
Let’s begin by illustrating the FlowDocumentReader
control. It basically provides a container for flow documents and
automatically implements buttons for browsing multiple page documents
and controlling documents’ layout, as you see later in Figure 1. The FlowDocument object instead contains the document and exposes some interesting properties. The previous code uses the most important ones. TextAlignment enables specifying how the text must be aligned within the document and can have one of the following values: Center, Right, Left, or Justify. IsOptimalParagraph set as True enables paragraph layout optimization. IsHyphenationEnable set as True enables word hyphenation in the document. IsColumnWidthFlexible set as True means that the value of the ColumnWidth property is not fixed. This last property takes place when you enable the document view by columns. The ColumnGap property indicates the spacing between columns. A complete list of properties is available in the MSDN Library: http://msdn.microsoft.com/en-us/library/system.windows.documents.flowdocument_members(VS.100).aspx. For the document content, notice the following techniques:
You divide the content into multiple Paragraph objects to provide different paragraph formatting.
You can add inline formatting. For example, the following line contains bold formatting within a paragraph:
<Bold>PresentationFramework</Bold> exposes namespaces and classes
You can also add fully functional hyperlinks as in the following sample line:
<Hyperlink
NavigateUri="http://msdn.microsoft.com/en-
us/library/ms750441(VS.100).aspx#System_Threading_DispatcherObject">
System.Threading.DispatcherObject</Hyperlink>
The sample document also shows how to implement bulleted lists via a List object that contains ListItem elements. It is interesting how flow documents also support figures insertion via a Figure element that contains a BlockUIContainer object nesting an Image control storing the figure and a TextBlock
control describing the figure. Notice how each paragraph and
subparagraph can be customized by setting font properties different from
other paragraphs. Switching the discussion to buttons implementation,
instead of handling Click events the code makes use of a technique known as commanding
that takes advantage of built-in commands associated to specific
actions; basically each button is associated to one of the built-in
actions for the annotation service via the Command property and points to the flow document as the target of the action (CommandTarget).
At this point there is the need of writing code that enables the
annotation service at the application startup so that the user can
annotate or highlight text and then save annotations to disk for later
reuse. The annotation service relies on the System.Windows.Annotations namespace that provides an AnnotationService class whose instance allows editing the document. Next, the System.Windows.Annotations.Storage namespace provides objects for storing annotations to Xml files for later reuse. Code in Listing 2
shows how to implement the annotation service with Visual Basic. The
code must be written to the code-behind file for the current window and
contains comments for better reading.
Listing 2. Implementing the Annotation Service
Imports System.Windows.Annotations Imports System.Windows.Annotations.Storage Imports System.IO
Public Class ManipulatingDocuments
Dim annotationStream As FileStream
Private Sub ManipulatingDocuments_Initialized(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Me.Initialized
'Gets the instance of the AnnotationService pointing to the FlowDocument Dim annotationServ As AnnotationService = _ AnnotationService.GetService(FlowReader1)
'Declares a store for annotations Dim annotationArchive As AnnotationStore
'If no annotation service already exists for 'the current flow document... If annotationServ Is Nothing Then '...creates a new service ' and a new store to an Xml file annotationStream = New FileStream("annotations.xml", FileMode.OpenOrCreate) annotationServ = New AnnotationService(FlowReader1)
'Gets the instance of the stream annotationArchive = New XmlStreamStore(annotationStream)
'Enables the document annotationServ.Enable(annotationArchive) End If End Sub
Private Sub ManipulatingDocuments_Closed(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Me.Closed Dim annotationServ As AnnotationService = _ AnnotationService.GetService(FlowReader1)
'If an instance of the annotation 'service is available If annotationServ IsNot Nothing And _ annotationServ.IsEnabled Then
'shuts down the service 'and releases resources annotationServ.Store.Flush() annotationServ.Disable() annotationStream.Close() End If End Sub End Class
|
Notice how the annotation service startup is placed inside the Window.Initialized event handler, whereas the annotation service shutdown is placed inside the Windows.Closed event handler. Now run the demo application by pressing F5.
As you can see on the screen, if you resize the window, the flow
document content is automatically and dynamically adapted to the
window’s layout. Moreover you can decide, using the appropriate controls
on the FlowDocumentReader, how the
document has to be viewed (for example if one or two pages appear on the
window or with zoom enabled). The best way for getting a feeling about
how this works is to resize the window. Figure 1 shows how the application looks, showing also an example of annotation.
You
apply annotations or highlights by just selecting the desired text and
then pressing one of the related buttons. You write the annotation text
just after clicking the green box. Annotations are editable also when
reloaded.
|
You can also add ink annotations to your documents. Figure 2
shows how ink annotations look and how text is exposed with fonts
different than the standard one. Also notice how the hyperlink is
correctly highlighted and functional so that if you click it you will be
redirected to the related web page associated via the NavigateUri property in the XAML code.
Annotations are
automatically stored into an Xml file, as implemented in code. Remember
to resize the window to understand the flexibility of flow documents and
of the FlowDocumentReader control.
Understanding the RichTextBox Control
WPF offers a RichTextBox
control that works as you would expect for some aspects, thus allowing
advance formatting and image support, but it differs from other
technologies in that such control stores its content as a flow document. In XAML code the control definition looks like this:
<RichTextBox Name="RichTextBox1">
<!— add your flow document here —>
</RichTextBox>
You could nest within the
control the flow document shown in the previous section to get a fully
editable document or simply write your text into the control, where such
text takes standard formatting settings. You can also load an existing
file into the RichTextBox, which requires some lines of code. The following method shows how to load a document as text:
Private Sub LoadDocument(ByVal fileName As String)
Dim range As TextRange
If File.Exists(fileName) Then
range = New TextRange(RichTextBox1.Document.ContentStart,
RichTextBox1.Document.ContentEnd)
Using documentStream As New FileStream(fileName,
FileMode.
OpenOrCreate)
range.Load(documentStream,
System.Windows.DataFormats.Text)
End Using
End If
End Sub
The TextRange class basically represents the text area, and the code takes the entire area from start to end. Then the code invokes the TextRange.Load method to open the specified stream and converts the file content into a System.Windows.DataFormats.Text format that is acceptable for the RichTextBox.
Notice that the previous example loads a text document that is then
converted into XAML by the runtime. You can also load contents from XAML
files using the DataFormats.Xaml option. To save the document content you need to invoke the TextRange.Save method. The following method shows an example:
Private Sub SaveDocument(ByVal fileName As String)
Dim range As New TextRange(Me.RichTextBox1.Document.ContentStart,
Me.RichTextBox1.Document.ContentEnd)
Using documentStream As New FileStream(fileName,
FileMode.Create)
range.Save(documentStream, DataFormats.Xaml)
End Using
End Sub
In this case the document content is saved under the form of XAML content but you can still use the Text option to save such content as text, although this can cause a loss of formatting settings due to the restrictive conversion.
Implementing Spell Check
The RichTextBox control provides built-in spell check support. This can be enabled by setting the SpellCheck.IsEnabled property as follows:
<RichTextBox Name="RichTextBox1" SpellCheck.IsEnabled="True">
When enabled, when
the user types unrecognized words in the English grammar the words are
highlighted in red, and by right-clicking the highlighted word a list of
valid alternatives is suggested, similar to what happens in
applications such as Microsoft Word. Figure 3 shows how the spell check feature can help users to fix typos in their documents.