For some reason, I thought that this was going to be an easy area to work with. You know - open up some kind of built-in reader control, stuff a load of content from a Word document, HTML or RTF file or whatever into it and magically WPF would know how to display it. With all the zoom, pagination, search and multiple column features that I'd heard were available in WPF I'd anticipated an easy ride on this one.
It turns out that I was a bit naive. It's quite a lot more fiddly than I'd expected. But, as usual, with some effort and bit of research I did manage to get it to do pretty much what I wanted.
If you really need to get right down to the absolute lowest levels of text handling, you can do this using Glyph Runs. But bear in mind that when it first became available no less an authority than Charles Petzold described the documentation on low level text handling as "incomprehensible". If it was tough for him, no wonder it was next to impossible for me!
And to give you and idea of the amount of work involved, a simple "Hello World" display will take nearly 50 lines of code. So it's not something you'll want to take on lightly or do often, but if you really do need to get right down to the nitty-gritty at least you know you can.
The good news is that at a higher practical level you can create formatted text content that is very similar to HTML. In many cases you'll probably want to create your document outside of Visual Studio, and I'll cover some ways you can do this later.
The RichTextBox - WPF Style
If you want to allow your users to get their mitts on the content, for instance to add, edit or delete parts of it, then I think the WPF RichTextBox is going to be your weapon of choice.
So I'll start with that.
A Basic FlowDocument
The first document I've created here is in fact all written in XAML. You can create a FlowDocument within Visual Studio (although I'm not 100% sure if the Template is automatically available in the Express Edition). In any case, the markup required to create one is trivial*. You could in fact even roll your sleeves up and create it using Notepad - not that I'm recommending you inflict this on yourself!
The screenshot below shows an example document created in XAML as a FlowDocument. As you can see, it has different fonts, font colors, font sizes, font weights, etc.
* If you don't have access to the Template, just use a blank file and ensure that you have the first line of:
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
and the final line of markup as:
The actual markup I have used for the above example looks like this screenshot:
As you can see, it's familiar looking stuff, with Span, Paragraph, Bold, Italic, etc markup tags being used as required. There are many more tags you can use to create the effect you need right down to the smallest detail.
Does the text have to be in XAML format? Absolutely not - but it is the default. With a bit of work you can also fill the RichTextBox with RTF format (which I'll cover later) and you can save in XAML or RTF formats.
Also, if you are comfortable with HTML, there is at least one HTML to XAML converter out there. I tried the Microsoft one and it worked fine in most situations.
The RichTextBox will happily accept the usual system-wide shortcut keys, such as Ctrl-B for Bold, Ctrl-U for Underline and so on. You would have to build in additional UI if you want to allow users to change font settings - size, color, and so on, or to insert images. Adding images to the RichTextBox in WPF, by the way, is way easier than it used to be.
Actually, that's an incomplete statement, because the truth is that adding just about any control type to the FlowDocument (and therefore being displayed in the RichTextBox) is very easy. And as a control that can't be wired to events isn't a lot of use, you'll be pleased to know that doing this is also very easy. I'll be covering in one of the upcoming parts of this series.
For now though I'll just look at adding an image.
FlowDocument With An Image
The following screenshot shows a FlowDocument that has an image inserted:
One of the great advantages of using a FlowDocument, even inside a RichTextBox is that the content flows. That is, the layout and pagination adjusts automatically to provide the best view of the content as you change the size of the container. It's surprising just how quickly you come to take this for granted once you start using it, but it really is a big step forward.
The XAML for the FlowDocument is as follows:-
As you can see, the syntax is just the same as you would use to insert an image in a Window or a Page. I've enclosed it inside a Paragraph, but this isn't strictly necessary.
If you want more control over exactly where the image is placed in the context of the overall document, you have more choices. If you insert an image as a Figure, then you can set its position and also arrange for associated text to be displayed next to it or above or below it.
Populating the RichTextBox
So far, we've considered some basic FlowDocuments, looked at the XAML and viewed the resulting document. However, we still haven't seen how we pour that FlowDocument into the RichTextBox and so that's our next and final task for this Part.
The key tool for this task is the XAMLReader. This lives in the System.Windows.Markup Namespace and we'll be looking at it in deeper detail in future articles. So the first thing we need to do is to add the appropriate Imports statement for that Namespace.
There's a kind of a piggyback effect to the code I've used. First of all, it creates a FileStream to handle the stream reading. Then this FileStream is handed off to the XAMLReader, which will parse the XAML into a generic object. Then, in order to give the RichTextBox a document type that it understands, this object is cast to a FlowDocument.
Take a look at the code. It's not as complicated as I've probably made it sound!
Dim fs As FileStream
Private Sub RichTextBoxDemo_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
If File.Exists("FlowDocColored.xaml") Then
fs = New FileStream("FlowDocColored.xaml", FileMode.Open)
RTB1.Document = CType(XamlReader.Load(fs), FlowDocument)
MessageBox.Show("Cannot find the required file!")
You'll see that I've built in a bit of error checking for the existence of the required file; you could equally use a Try/Catch block for the same purpose. Also note that I included an Imports statement for System.IO for the FileStream. You can use this same kind of code logic to populate the RichTextBox with any other xaml file. However, if you want to use the Box to contain an RTF file then there is more work to do. But that's something I'll deal with in the next instalment.
I hope you've found this first part useful and interesting. I have barely scratched the surface of what you can do with document handling in WPF and by the end of this series I think you will agree that the choices are wide ranging and seriously useful.