The Rich Text Box

I want to edit my songs, and in the editing I want to put some formatting. The Rich Text Box is the control to do this. Even in Winforms, the Rich Text Box would be the way to go. But in WPF it behaves differently.

The first thing that you notice is that the Rich Text Box (RTB from now on) does not have a text property. It contains a Flowdocument. A flowdocument is a new concept in WPF. It represents a document which can flow, naturally enough. It will take up all the available space on a page and flow onto extra pages. This, of course, all depends upon the size of the pages. A flow document is read with either a FlowDocumentReader, a FlowDocumentPageViewer or a FlowDocumentScrollViewer. The FlowDocumentPageViewer shows the document page by page. The FlowDocumentScrollViewer shows the entire document in continuous scrolling mode. The FlowDocumentReader allows you to choose either of these two modes or two pages at a time. With each of these you can adjust the zoom on the page. So they are fairly flexible.

A FlowDocument is just a piece of xaml code. The text is contained within paragraph tags in the xaml.

The nice thing about the RTB is that it has all the editing functions built in. Many of these are already hooked up to keyboard shortcuts. Placing the cursor over a word and hitting Ctrl+B will make the entire word Bold. EditingCommands has more methods than you would ever want to use. There is really very little need to add much, or anything, in order to have a fully functioning editor. WPF uses commands to attach behaviour to controls. There is also the standard events, and another new concept, triggers. We will look at these in a later post when we get to wire everything up.

One thing which did arise as a result of playing with this stuff was the question of how I would arrange and store the data. I had earlier said that I would prefer to store the data in a database. My original thinking was that I would store an entity called song, and a related entity verse. A song could have one or many verses. In that scenario it made sense to use a database.

Upon reflection I am close to abandoning that idea. I am thinking that each song would contain all its lyrics in one xaml file. If that is the case then the database would only have one table and I am starting to think that there is no need for it.

I may still change my mind about the song and verses, we will see how it pans out. But for, since I have the database set up I am using it, but will probably abandon it later. To read the flowdocument from the database I need a memory stream. If I go with a file system instead of a database I will just use a file stream. So there is no big change.

One area where I did run into trouble was getting the flowdocument from the RTB. This bit is easy:
FlowDocument doc = rtb.Document;
No problem, I now have the flowdocument contained within the RTB. But what can I do with it. I can’t cast it to a string to save it, and if I want to save it to a file then I am out of luck. After a bit of searching on the web I found that I could do this:

TextRange range = new TextRange(rtb.Document.ContentStart,rtb.Document.ContentEnd);
MemoryStream stream = new MemoryStream();
range.Save(stream,DataFormats.Xaml);

In this case the range is from the beginning of the document to the end. range.Save saves the range into the new stream using the DataFormats provided. There are a number of DataFormats, including Xaml, XamlPackage and Rtf. However, doing this introduces a lot of unnecessary cruft into the string. I then found a much simpler way to save:
string lyrics = XamlWriter.Save(rtb.Document);
And this only saves the flowdocument itself into the string. There are overloads so you can save to a stream, for example a file stream. I found doing this that I was able to do away with my memorystream altogether.

Reading the flowdocument from the database was nearly as easy:

XmlTextReader reader = new XmlTextReader(new StringReader(song.Lyrics));
FlowDocument doc = (FlowDocument) XamlReader.Load(xmlTextReader);
rtb.Document = (FlowDocument)XamlReader.Load(reader);

So using the database is easy. Whether I will keep it is another question.

The next step is parsing the flowdocument to split it up into verses. That is a job for tomorrow.