WPF Flow Document For Beginners
As part of an article that I am creating for www.codeproject.com I decided to look into using the System.Windows.Documents namespace and have a look at seeing if I could make a semi-cool’ish looking document.
Now when you use FlowDocuments, there are several container WPF container controls which you may host a FlowDocument in. These WPF container controls vary in what they provide. Lets see the difference shall we
- FlowDocumentScrollViewer : Simply displays the entire document and provides a scroll bar. Like a web page
- FlowDocumentPageViewer : Shows document as individual pages, and allows user to adjust zoom level.
- FlowDocumentReader : Combines FlowDocumentScrollViewer and FlowDocumentPageViewer into a single control, and exposes text search facilities.
For example a FlowDocumentPageViewer is as shown below
For those of who have not come across the FlowDocument, here is a list of some of the things that can be done with it
- Allow paragraphing
- Allow anchoring of images
- Allow hyperlinks
- Allow text blocks
- Allow tables
- Allow subscript/superscript text
- Allow UIElements (such as Button etc etc)
- Allow text effects
Think of FlowDocument(s) as a mini desktop publishing type interface. Though I’m sure things like Quark are going to yield more flexability. Never the less, the results of FlowDocument(s) could be thought as be able to create that sort of page publishing type layout.
What I’m going to do now is show you how to create a few of the various FlowDocument elements both in XAML and in code as they are little different actually.
PARAGRAPH
In XAML
<!-- FLOW DOCUMENT VIEWER START -->
<FlowDocumentPageViewer x:Name="flowDocViewer" Margin="0,0,0,0" Background="#FF414141" Zoom="80" >
<!-- FLOW DOCUMENT START -->
<FlowDocument x:Name="flowDoc" Foreground="White" FontFamily="Arial" >
<Paragraph x:Name="para1" FontSize="11">
The following details have been obtained from Amazon to match your initial query.
Some of the returned values may have been empty, so have been ommitted from theresults shown here.
Also where there have been more than one value returned viathe Amazon Details, these to have been
omitted for the sake of keeping things simplefor this small demo application. Simple is good,
when trying to show how something works
</Paragraph>
</FlowDocument>
</FlowDocumentPageViewer>
In C# code behind
Paragraph paraHeader = new Paragraph();
paraHeader.FontSize = 12;
paraHeader.Foreground = headerBrsh;
paraHeader.FontWeight = FontWeights.Bold;
paraHeader.Inlines.Add(new Run("Paragraph Text"));
flowDoc.Blocks.Add(paraHeader);
HYPERLINKS
In XAML
<Paragraph FontSize=”11″>
<Hyperlink Click="hl_Click" NavigateUri="www.google.com">Click Here</Hyperlink>
</Paragraph>
In C# code behind
Paragraph paraValue = new Paragraph();Hyperlink hl = new Hyperlink(new Run("Click Here To View The Link Data")); hl.FontSize = 11;hl.NavigateUri = new Uri(nonNullprop.PropertyValue); hl.Click += new RoutedEventHandler(hl_Click);paraValue.Inlines.Add(hl);
flowDoc.Blocks.Add(paraValue);
EMBEDDING UI ELEMENTS
In XAML
<BlockUIContainer> <Button Width="60" Height="60" Click="Button_Click"> Click me </Button> </BlockUIContainer>
In C# code behind
BlockUIContainer uiCont = new BlockUIContainer(); Button b = new Button(); b.Width = b.Height = 60; b.Click += new RoutedEventHandler(Button_Click); b.Content = "Click me"; flowDoc.Blocks.Add(uiCont);
Of course this is only touching the surface of what can be done with FlowDocuments. But it gives you an idea of how flexable the formatting of documents is with WPF.
The screen shot below shows an example with some Paragraphs/Tables/Hyperlinks and UIElements in place




























Daniel Vaughan said
am January 8 2008 @ 5:02 am
Yes, I like the abstraction to html-esque tags, rather than explicitly laying out text boxes etc. Makes building code generators much easier, among other things.
That screen looks slick…eagerly awaiting your next article.
Daniel
sacha said
am January 8 2008 @ 9:25 am
Yeah I think flow documents are pretty cool.
My new article should be up this week at some point.
I just want to try and fix something to do with AWS beeing annoying.
Grrr.
I am beginning to regret using that. Its flaky
Cornel said
am January 9 2008 @ 8:09 am
Hi,
Nice article!
What’s the relationship between BlockUIContainer and button? The button shouldn’t be added to the BlockUIContainer or something?
Thanks
sacha said
am January 9 2008 @ 8:38 am
Thanks Cornel
Basically to host a UIElement in a FlowDocument, it needs to be added to a BlockUIContainer.
Trying to add a UIElement such as button straight into a Paragraph isnt allowed.
You must wrap it in a BlockUIContainer.
Hope this answers you query
Cornel said
am January 10 2008 @ 9:11 am
I’m using .NET 3.5.
I made a test project and in constructor I added the following code:
BlockUIContainer uiCont = new BlockUIContainer();
Button b = new Button();
b.Width = b.Height = 60;
b.Content = “Click me”;
flowDoc.Document.Blocks.Add(uiCont);
“flowDoc.Document.Blocks.Add(uiCont);” crashes my test app with the following message:
EventType clr20r3, P1 test.exe, P2 0.0.0.0, P3 4785e052, P4 presentationframework, P5 3.0.0.0, P6 470bc696, P7 660b, P8 e1, P9 system.windows.markup.xamlparse, P10 NIL.
flowDoc is a FlowDocumentReader control.
sacha said
am January 10 2008 @ 9:44 am
You need to make sure that you add the BlockUIContainer to an actual FlowDocument. Not a FlowDocumentReader. Have a look at the following XAML.
<!– FLOW DOCUMENT VIEWER START –>
<FlowDocumentPageViewer x:Name=”flowDocViewer” Margin=”0,0,0,0″ Background=”#FF414141″ Zoom=”80″ >
<!– FLOW DOCUMENT START –>
<FlowDocument x:Name=”flowDoc” Foreground=”White” FontFamily=”Arial” />
</FlowDocumentPageViewer>
Then if you have some XAML like this you will be able to add a new BlockUIContainer like you show
BlockUIContainer uiCont = new BlockUIContainer();
Button b = new Button();
b.Width = b.Height = 60;
b.Content = “Click me”;
flowDoc.Document.Blocks.Add(uiCont);
I know this works, as I have a test app which I am just about to publish at codeproject which is setup like this, and it works just fine.
I think your problem is that your trying to add a BlockUIContainer to a FlowDocumentReader and not a FlowDocument control.
Carlos de Luna Sáenz said
am May 15 2008 @ 3:01 pm
I have the following code:
public class IUSHyperlink:Hyperlink
{
public IUSHyperlink()
{
this.ToolTip = “Esta liga abrirá una ventana interna”;
this.Click += new RoutedEventHandler(MyHyperlink_RequestNavigate);
}
void MyHyperlink_RequestNavigate(object sender, RoutedEventArgs e)
{
MessageBox.Show(”Liga con “+this.Tag+”Activada”);
NavigationWindow window = new NavigationWindow();
// window.Source = e.Uri;
window.Show();
}
}
}
But guess what!!!
the click does no tdo anithing, the hyperlink is shown but no response neither to click CTRL+click or SHIFT+Click..
Any ideas??
sacha said
am May 15 2008 @ 3:35 pm
I would try and put it in the XAML like
xmlns:local=”clr-namespace:yournamspace”
this will give you the event in the window that contains the FlowDocument.
Thats what Id try
Carlos de Luna Sáenz said
am May 15 2008 @ 4:22 pm
mmm…… that would be fine if the link i am “drawing” could be static-generated, but is dinamically via:
private IUSHyperlink creaLiga(RelacionTO liga, string contenido)
{
RelacionFraseTesisTO tesis = null;
RelacionFraseArticulosTO articulos = null;
FachadaBusquedaTradicionalClient fachadaBusqueda = new FachadaBusquedaTradicionalClient();
IUSHyperlink ligaNueva = new IUSHyperlink();
ligaNueva.Inlines.Add(new Run(contenido));
ligaNueva.IsEnabled = true;
switch (liga.Tipo)
{
case “0″:
articulos = fachadaBusqueda.getRelacionesFrasesArticulos(Int32.Parse(liga.Ius),
Int32.Parse(liga.IdRel))[0];
ligaNueva.Tag = “ventanaEmergente(” + articulos.IdLey +
“,” + articulos.IdArt + “,” + articulos.IdRef + “);”;
break;
case “1″:
tesis = fachadaBusqueda.getRelacionesFrasesTesis(Int32.Parse(liga.Ius),
Int32.Parse(liga.IdRel))[0];
ligaNueva.Tag = “Tesis(” + tesis.Ius + “)”;
break;
case “2″:
tesis = fachadaBusqueda.getRelacionesFrasesTesis(Int32.Parse(liga.Ius),
Int32.Parse(liga.IdRel))[0];
ligaNueva.Tag = “Ejecutoria(” + tesis.IdLink + “)”;
break;
case “3″:
tesis = fachadaBusqueda.getRelacionesFrasesTesis(Int32.Parse(liga.Ius),
Int32.Parse(liga.IdRel))[0];
ligaNueva.Tag = “Votos(” + tesis.IdLink + “)”;
break;
default:
tesis = fachadaBusqueda.getRelacionesFrasesTesis(Int32.Parse(liga.Ius),
Int32.Parse(liga.IdRel))[0];
ligaNueva.Tag= “Acuerdos(” + tesis.IdLink + “)”;
break;
}
fachadaBusqueda.Close();
return ligaNueva;
}
sacha said
am May 15 2008 @ 4:47 pm
Mmm Ok, yeah in that case Id be doing just what you are doing, experimenting. Could you not have a DP in there that is set in code behind in the window
Carlos de Luna Sáenz said
am May 15 2008 @ 5:20 pm
DP??? Data Processing??? i don’t thing so, i subclassed HyperLink inorder to process the click for that specific click management and do all the process in the new class, wich i thing is the best practice.
Greetings
sacha said
am May 15 2008 @ 5:45 pm
No I mean Dependecny Property, so you could set that these Props it window, and still use the subclass in the XAML.
Another option is to try and pass all the parameters in via XAML, have a look at
http://www.beacosta.com/blog/?m=200603
Which shows how to pass params in XAML.
I cant see why you cant just use data binding also, if you bind the link to some DP and then just update the DP with the value of your dynamically created link, it should all be cool
Carlos de Luna Sáenz said
am May 15 2008 @ 6:50 pm
Stupidity can be my mistake… but getting back to the basics…
EVERY time you have a hyperlink inside a RichTextEditor, or any other viewer -i guess- you must set the IsDocumentEnabledProperty as true, then the document will be enabled (wow!!), if not the default value of false will disable the doc, and -of course- the links, it doesn’t matter if the IsEnabled porperty is set on the document nor link, so one must check all the “line” for the IsEnabled path PLUS the IsDocumentEnabled one of the document container.
Sorry for the interruption, now get back to the usual bloging reading…:D
Greetings and thanks for the advices…
sacha barber said
am May 16 2008 @ 7:02 am
Ha Ha, now thats funny. I didnt know that, so we both learnt from that. Cool
Pavel @ xooChat Live Help said
am June 3 2008 @ 5:09 am
I’m developing a kinda chat application and I stumbled upon a problem of correctly displaying chat history. First, I found you chat sample on Codeproject, but it didn’t help much, since you are displaying history in a plain TextBox. But I need RichTextBox.
Now I found this article and it’s superior. Exactly what I need. Especially code snippets on how to create document elements programmatically. Thanks so much.
sacha said
am June 3 2008 @ 5:47 am
Cool
This other article may be useful to you also http://www.codeproject.com/KB/IP/WCFWPFChatRoot.aspx
Pavel @ xooChat Live Help said
am June 10 2008 @ 5:18 am
@sacha
Yes, I found this app as well. But this article turns out to be the most helpful.
sacha barber said
am June 10 2008 @ 7:12 am
Thanks Pavel
Joss Attridge said
am August 30 2008 @ 8:59 pm
Nice article – Section on EMBEDDING UI ELEMENTS is missing the code to add the ui element to the BlocklUiContainer. Should include uiCont.Child = b;
The example works then
Joss.
Pramod.P.V said
am March 17 2009 @ 12:05 pm
Sacha.. Nice article..
I have a situation where i need to be able to print the document. Now the document must always be of a fixed size. However on screen i would like to have flow documents facilities. what wuold be the best way to approach this.
grkowalski said
am January 22 2010 @ 6:16 am
Thank you for your article. I am just starting out with flow documents and I am struggling with how to add a hyperlink that jumps to specific section within a document, and to a specific within another xaml flow doc. The flow documents are read in by a WPF/C# app that displays the xaml flow document (which serves as a help page).
sacha said
am January 22 2010 @ 8:36 am
I have to say that is not something I have done before, So I am afraid its google time for you.