Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating FlowDocuments using XML Literals and Embedded Expressions

0.00/5 (No votes)
14 Dec 2011 1  
How to create a FlowDocument using XML Literals and Embedded Expressions.

Screenshot_1.png

A UserControl displaying a FlowDocument created using XML literals

Introduction

In this article I will show how you can go about defining a FlowDocument using XML literals and embedded expressions. As XML literals are exclusive to VB.NET this article may not be palatable to some die-hard C-Sharpers. Hopefully, in the near future, C# will also have the benefit of XML literals. If your programming curiosity gets the better of you then please read on.

FlowDocument

A Flow Document is a document that has rich layout features. It can dynamically lay-out its content based on window size, device resolution, and other variables. Flow documents are mainly suitable for on-screen viewing and provide advanced document features like pagination, columns, search, viewing modes, and printing (search, viewing modes, and printing support are provided through the flow document containers).

The following XAML code creates a flow document, hosted by a FlowDocumentPageViewer, that displays some text:

<FlowDocumentPageViewer>
  <FlowDocument>
    <Paragraph>CodeProject: Your Development Resource</Paragraph>
  </FlowDocument>
</FlowDocumentPageViewer>

The result of the XAML code is:

Screenshot_2.png

You can read more on flow documents here.

XML Literals

An XML literal is XML code that is literally defined within your Visual Basic code. Using XML literals you can define XML documents and fragments directly within your code. The following example shows the creation of an XML document and XML element:

Dim cpDoc As XDocument = <?xml version="1.0"?>
                         <CodeProject>
                             <Member>
                                 <FirstName>Meshack</FirstName>
                                 <LastName>Musundi</LastName>
                             </Member>
                             <Member>
                                 <FirstName>Vee</FirstName>
                                 <LastName>Bee</LastName>
                             </Member>
                         </CodeProject>
                         
Dim cpElm As XElement = <CodeProject>
                            <Member>
                                <FirstName>Meshack</FirstName>
                                <LastName>Musundi</LastName>
                            </Member>
                            <Member>
                                <FirstName>Vee</FirstName>
                                <LastName>Bee</LastName>
                            </Member>
                        </CodeProject>

Embedded Expressions

Embedded expressions work in concert with XML literals and enable the creation of dynamic XML documents or XML fragments. Using embedded expressions XML nodes can be created at runtime using values from variables or LINQ queries. This is done through the use of the embedded expression operator <%= %>.

Creating an XML document using some variable values is as easy as:

Dim firstName As String = "Meshack"
Dim lastName As String = "Musundi"

Dim cpDoc As XDocument = <?xml version="1.0"?>
                         <CodeProject>
                             <Member>
                                 <FirstName><%= firstName %></FirstName>
                                 <LastName><%= lastName %></LastName>
                             </Member>
                         </CodeProject>

Creating an xml element using a LINQ query is as easy as:

Dim names() As String = {"Bill", "Meshack", "Vee"}

Dim cpElm As XElement = <CodeProject>
                            <%= From n In names Select _
                                <Member>
                                    <Name><%= n %></Name>
                                </Member> %>
                        </CodeProject>

The structure of the XML element generated above will be as follows:

<CodeProject>
  <Member>
    <Name>Bill</Name>
  </Member>
  <Member>
    <Name>Meshack</Name>
  </Member>
  <Member>
    <Name>Vee</Name>
  </Member>
</CodeProject>

XML Literals + Embedded Expressions = Dynamic FlowDocument

If you want to create a 'dynamic' flow document, one option would be to define the flow document programmatically. The other option would be to change portions of a flow document that is already defined in XAML. Using the latter approach, you would have to dig through the deeply nested content of the flow document in order to find the content you want to change. Unfortunately, both of these approaches are tedious and very unintuitive.

Using XML literals and embedded expressions, the process of creating a dynamic flow document can be more bearable. Consider that we intend to create a flow document whose final result will be as follows:

Screenshot_3.png

The data source for the flow document table will be a DataTable that is defined programmatically, to avoid having to create a database. The image displayed in the flow document will be from an application resource, Logo.png.

Screenshot_4.png

To create the DataTable, a method named AlbumTable() will create and return the required object.

Private Function AlbumTable() As DataTable
    Dim table As New DataTable

    table.Columns.Add("No.", GetType(Integer))
    table.Columns.Add("Album", GetType(String))
    table.Columns.Add("Artist", GetType(String))

    table.Rows.Add(1, "Let England Shake", "PJ Harvey")
    table.Rows.Add(2, "Bon Iver", "Bon Iver")
    table.Rows.Add(3, "21", "Adele")
    table.Rows.Add(4, "whokill", "tUnE-yArDs")
    table.Rows.Add(5, "Watch the Throne", "Jay-Z and Kanye West")
    table.Rows.Add(6, "The Whole Love", "Wilco")
    table.Rows.Add(7, "The English Riviera", "Metronomy")
    table.Rows.Add(8, "Skying", "The Horrors")
    table.Rows.Add(9, "House of Balloons", "The Weeknd")
    table.Rows.Add(10, "Helplessness Blues", "Fleet Foxes")

    Return (table)
End Function

We can then create an XML element, using an XML literal with the required embedded expressions, before converting it into a FlowDocument which is used to set the Document property of a FlowDocumentPageViewer.

Private Sub ShowReport(ByVal albumTable As DataTable)

    Dim doc = <FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                  FontFamily="Myriad Pro" FontSize="14" ColumnWidth="594">
                  <!--Logo-->
                  <BlockUIContainer><Image Source="Logo.png" Width="380" Height="78"/></BlockUIContainer>

                  <Paragraph Margin="10" FontWeight="SemiBold">
                      Top Albums of 2011 Report
                  </Paragraph>

                  <Table Margin="10" BorderThickness="1" BorderBrush="Gainsboro">
                      <Table.Columns>
                          <TableColumn Width="*"/>
                          <TableColumn Width="5*"/>
                          <TableColumn Width="5*"/>
                      </Table.Columns>

                      <TableRowGroup>
                          <TableRow Background="Crimson" Foreground="White" FontWeight="SemiBold">
                              <TableCell><Paragraph Margin="4">No.</Paragraph></TableCell>
                              <TableCell><Paragraph Margin="4">Album</Paragraph></TableCell>
                              <TableCell><Paragraph Margin="4">Artist</Paragraph></TableCell>
                          </TableRow>

                          <!--Data rows-->
                          <%= From alb In albumTable.Rows Select _
                              <TableRow xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
                                  <TableCell BorderThickness="1" BorderBrush="Gainsboro">
                                      <Paragraph Margin="4"><%= alb(0) %></Paragraph>
                                  </TableCell>
                                  <TableCell BorderThickness="1" BorderBrush="Gainsboro">
                                      <Paragraph Foreground="MidnightBlue" Margin="4"><%= alb(1) %></Paragraph>
                                  </TableCell>
                                  <TableCell BorderThickness="1" BorderBrush="Gainsboro">
                                      <Paragraph Foreground="Gray" Margin="4"><%= alb(2) %></Paragraph>
                                  </TableCell>
                              </TableRow> %>
                      </TableRowGroup>
                  </Table>
              </FlowDocument>

    Dim docString As String = doc.ToString()

    Dim flowDoc As FlowDocument = XamlReader.Load(New XmlTextReader(New StringReader(docString)))
    FlowDocPgVwr.Document = flowDoc

End Sub
    
Private Sub MainWindow_Loaded(ByVal sender As Object, _
                              ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
    ShowReport(AlbumTable)
End Sub

One thing I noted with this approach is that you have to specify the XML namespace in the first element following the Select statement of the LINQ query. If you don't specify the namespace, a namespace will be specified automatically but with a null value. This will result in a XamlParseException when attempting to convert docString to XAML.

Another thing to take-note-of/remember is the fact that XAML is case-sensitive. If you forget to capitalize the element names appropriately, e.g., <Tablerow> instead of <TableRow>, a XamlParseException will be generated. This approach is also not WYSIWYG so you have to run the application to get a look at what exactly is the end result. Despite this, you can still get the required results if you have a good understanding of how to create flow documents.

Data Binding

From the example shown previously, it is notable that this approach creates the possibility of having a data-bindable like flow document. You could nest a flow document container in a UserControl and define a dependency property of the suitable data type. This is the approach used in the application whose screenshot is shown at the beginning of this article.

Public Property Table As DataTable
    Get
        Return GetValue(TableProperty)
    End Get

    Set(ByVal value As DataTable)
        SetValue(TableProperty, value)
    End Set
End Property

Public Shared ReadOnly TableProperty As DependencyProperty = _
                       DependencyProperty.Register("Table", _
                       GetType(DataTable), GetType(ReportViewer), _
                       New FrameworkPropertyMetadata(New PropertyChangedCallback( _
                                                              AddressOf TableChanged)))

Private Shared Sub TableChanged(ByVal sender As Object, _
        ByVal e As DependencyPropertyChangedEventArgs)
    Dim newTable As DataTable = CType(e.NewValue, DataTable)
    CType(sender, ReportViewer).ShowReport(newTable)
End Sub

If this approach doesn't seem suitable, you could opt to follow the approach described here.

Conclusion

That's it. I hope that you picked up something useful from this article.

History

  • 13th Dec., 2011: Initial post.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here