After the last post I had some words on the screen. I now need to be able to split them into verses and edit them.
The whole point of a FlowDocument is that it flows. That is, it decides itself when to break into a new page. But I wanted to override this and insert page breaks at the end of each song verse. I spent hours googling and looking at the Microsoft examples One forum response to someone else’s similar requirement suggested that it couldn’t be done. I wasn’t prepared to accept that.
I eventually found that it could be done.
A flowdocument consists of a number of TextElement objects. TextElement is an abstract class and is the base for two abstract classes: Block and Inline. The most common derived class is Paragraph, which is derived from Block. Within a paragraph you can place Inline derived objects such as Span and TextBlock. A typical flowdocument is made up of a number of paragraphs. The flowdocument has a Blocks collection which has a number of methods for navigating through the document.
The Block derived class I needed was Section. A section contains one or more paragraphs. You don’t need a section but you can insert one if you want. What was important for is that section has a boolean property BreakPageBefore, so now I could insert a page break. So I tried the following code:
Section section = new Section();
section.BreakPageBefore = true;
rtb.Document.Blocks.Add(section);
It didn’t quite work.
This code adds the section, but it adds it to the end of the document, not at the position in the document where the caret is currently positioned. It also doesn’t move the caret to the new section.
That is OK if I just want to add a new verse at the end, so it is the start of something useful. So I will first deal with getting the insertion point into the new section. In order to add new text I need a paragraph to insert it into. When you press Enter while editing the RTB a new paragraph is inserted automatically. But it isn’t if you create it programmatically. So the first thing to do is to create a new paragraph and move the insertion point:
rtb.Document.Blocks.Add(new Paragraph());
EditingCommands.MoveDownByParagraph.Execute(null,rtb);
The first line creates a new paragraph. The second line is a built-in command. WPF has a number of these as I mentioned in a previous entry. The first argument, according to the documentation, is ignored by most commands. I haven’t come across a command yet which uses it, perhaps I will at some stage. It should be set to Null. The second argument is the object which will use the command. The object must implement the IInputElement, and Rich Textboxes do this. The RTB has implementations for a bundle of these commands. So, in effect, I have created a paragraph and moved the insertion point into the new paragraph.
The next problem is to insert a new section with a page break anywhere in an existing flowdocument. This took me a long time to find out how to do. The solution, when I eventually sorted it out, is very simple.
A flowdocument contains a lot of stuff other than plain text. That is the whole point of using a Rich TextBox. In a textbox you can set a selection point with texbox.SelectionStart. There are other methods such as Select, for selecting text using an integer for the start and another integer for the length. There is the SelectedText property and other methods as well. The whole point is that text is stored as a string which is a character array. But in the Rich Textbox there is other stuff in the string, such as pargraph and section tags, formatting tags etc. So using integers to move around text doesn’t work.
To resolve this issue MicroSoft uses a TextPointer object to represent position within a FlowDocument. The TextPointer will tell us where we are in the document:
//Get the caret position
Textpointer pointer = rtb.Selection.Start;
//Get the paragraph which contains the caret
Paragraph paragraph = pointer.Paragraph;
//Insert the new section after the current paragraph
rtb.Document.Blocks.InsertAfter(paragraph,section);
Almost done. To test all of this I put a button on the form which I clicked to insert a new verse. The button will remain, but we really need a keyboard accelerator. At the moment, to insert a new paragraph you just hit
private Dictionary
new Dictionary
When the Edit Control loads it calls a method to add gestures, in this case there is only one at the moment.
gestures.Add(new KeyGesture(Key.Enter,ModifierKeys.Control),AddNewVerse);
This line of code says that the Gesture compring the Control Key + the Enter Key will call the AddNewVerse method, which is the method which contained the earlier code for inserting paragraphs and sections.
I now need to hook this up to the KeyDown event of the Rich TextBox.
private void rtb_PreviewKeyDown(object sender, KeyEventArgs args)
{
foreach (KeyGesture gesture in gestures.Keys)
{
if (args.Handled) return;
if (gesture.Matches(null, args))
{
gestures[gesture](this, args);
args.Handled = true;
}
}
}
In this code args is of type KeyEventArgs. The code looks through the dictionary of key gestures seeking a match. If it finds a match it executes the method in the dictionary. It then sets the handled property to true so that it won’t do anything else. Otherwise it would raise the event for the Enter key and insert another paragraph. I could let it do that and not insert the paragraph myself in the AddNewVerse method. But if I add more gestures I could run into problems. I think it is safer to tell it explicitly what you want done.
And that is it for this time.