MindFusion.Reporting for Silverlight Programmer's Guide
Tutorial 2: Binding to a Data Source

This tutorial will demonstrate how to create and bind DataRange objects manually and how to create master-detail relationship between data-ranges representing related tables. The following steps are explained in more details below:

Prerequisites

This tutorial continues from where Tutorial 1: Getting Started has ended. It is therefore assumed that we have an existing application with a single data-bound report in it and a window with a set up ReportViewer that displays an instance of this report.

1. Nesting DataRange objects for master-detail implementation

We create a new DataRange object within the report's existing XAML declaration in a way similar to the one used in Tutorial 1. The new DataRange object is bound to the Categories data and contains two elements - a Label and a Picture, bound respectively to the CategoryName and Picture fields in the Categories table. The XAML code below illustrates the code of the new DataRange object.

XAML  Copy Code

...
<r:DataRange Location="0,0" Size="100%,150">
  <r:DataRange.ItemTemplate>
    <DataTemplate>
      <r:ItemContainer>
        <r:Label Location="10,10" Size="190,20" Text="[CategoryName]" />
        <r:Picture Location="60%,10" Size="38%,135" Image="{Binding Converter={StaticResource imageConverter}, ConverterParameter=Picture}" />
      </r:ItemContainer>
    </DataTemplate>
  </r:DataRange.ItemTemplate>
</r:DataRange>
...

Notice in the code above that the image uses a converter (defined as static resource) in order to bind to the Picture subelement of the XElement representing the category in the data source. The source code of this converter could look similar to the following:

C#  Copy Code

public class ImageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var xelement = value as XElement;
        if (xelement != null)
        {
            if (parameter is string)
            {
                var parameterValue = xelement.Element(parameter.ToString()).Value;
                byte[] rawData = System.Convert.FromBase64String(parameterValue);
                MemoryStream stream = new MemoryStream(rawData);
                BitmapImage image = new BitmapImage();
                image.SetSource(stream);
                return image;
            }
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Visual Basic  Copy Code

Public Class ImageConverter
    Implements IValueConverter

    Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.Convert

        If (TypeOf value Is XElement) Then

            Dim xelement = CType(value, XElement)
            If (xelement IsNot Nothing) Then

                If (TypeOf parameter Is String) Then

                    Dim parameterValue = xelement.Element(parameter.ToString()).Value
                    Dim rawData = System.Convert.FromBase64String(parameterValue)
                    Dim stream As New MemoryStream(rawData)
                    Dim image As New BitmapImage()
                    image.SetSource(stream)
                    Return image

                End If

            End If

        End If

        Return Nothing

    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IValueConverter.ConvertBack

        Throw New NotImplementedException()

    End Function

End Class

Now we want to embed the old DataRange, showing products, within the newly created DataRange, showing categories and we want to establish a master-detail relationship between the two data ranges so that the result displays products, grouped by category. Here is the new XAML code of the report illustrating the embedded data ranges.

XAML  Copy Code

<r:Report x:Key="myReport">
  <r:Page>
    <r:DataRange Location="0,0" Size="100%,150">
      <r:DataRange.ItemTemplate>
        <DataTemplate>
          <r:ItemContainer>
            <r:Label Location="10,10" Size="190,20" Text="[CategoryName]" />
            <r:Picture Location="60%,10" Size="38%,135" Image="{Binding Converter={StaticResource imageConverter}, ConverterParameter=Picture}" />

            <r:DataRange Location="0,75" Size="60%,20" Name="ProductsRange">
              <r:DataRange.HeaderTemplate>
                <DataTemplate>
                  <r:ItemContainer Size="100%,20">
                    <r:Label Text="ProductID" Location="0%,0" Size="20%,20" />
                    <r:Label Text="ProductName" Location="20%,0" Size="20%,20" />
                    <r:Label Text="QuantityPerUnit" Location="40%,0" Size="20%,20" />
                    <r:Label Text="UnitPrice" Location="60%,0" Size="20%,20" />
                    <r:Label Text="UnitsInStock" Location="80%,0" Size="20%,20" />
                  </r:ItemContainer>
                </DataTemplate>
              </r:DataRange.HeaderTemplate>
              <r:DataRange.ItemTemplate>
                <DataTemplate>
                  <r:ItemContainer>
                    <r:Label Text="[ProductID]" Location="0%,0" Size="20%,20" />
                    <r:Label Text="[ProductName]" Location="20%,0" Size="20%,20" />
                    <r:Label Text="[QuantityPerUnit]" Location="40%,0" Size="20%,20" />
                    <r:Label Text="[UnitPrice]" Location="60%,0" Size="20%,20" />
                    <r:Label Text="[UnitsInStock]" Location="80%,0" Size="20%,20" />
                  </r:ItemContainer>
                </DataTemplate>
              </r:DataRange.ItemTemplate>
            </r:DataRange>

          </r:ItemContainer>
        </DataTemplate>
      </r:DataRange.ItemTemplate>
    </r:DataRange>
  </r:Page>
</r:Report>

Note that the old DataRange (the one showing products) has had its data source removed. We will supply the data through the QueryDetails event depending on the category each instance of this DataRange belongs to. Note also that the products range now has a name associated with it. This will help us identify the range within the QueryDetails event handler as illustrated later in this topic. Finally, note that the Location of the embedded DataRange has been modified so that it is positioned correctly in its parent and its size is reduced to 60% so that it doesn't overlap with the image to the right.

Navigate to the page's constructor in the code behind and add the code necessary to populate the categories from the XML data source. Additionally, expose the existing products IEnumerable as a class field because it will be needed in the QueryDetails event handler. The following code (new code highlighted in bold) demonstrates this:

C#  Copy Code

document = XDocument.Load("Data/nwind.xml");

var categories = document.Elements("dataroot").Elements("Categories");
products = document.Elements("dataroot").Elements("Products");

myReport.DataContext = categories;
...

private IEnumerable<XElement> products;

Visual Basic  Copy Code

Dim document = XDocument.Load("Data/nwind.xml")

Dim categories = document.Elements("dataroot").Elements("Categories")
products = document.Elements("dataroot").Elements("Products")

myReport.DataContext = categories
...

Private products As IEnumerable(Of XElement)

2. Providing details in a master-detail relationship

To provide a master-detail implementation for the two data ranges, we need to handle the QueryDetails event on the Report class. Keep in mind that the event must be handled before the Run method of the Report is invoked. The following code illustrates how the event handler should look.

C#  Copy Code

void myReport_QueryDetails(object sender, QueryDetailsEventArgs e)
{
    DataRange dataRange = sender as DataRange;
    if (dataRange != null)
    {
        if (dataRange.Name == "ProductsRange")
        {
            var category = e.MasterRow as XElement;

            e.Details = products.Where(
                element => element.Element("CategoryID").Value == category.Element("CategoryID").Value);
        }
    }
}

Visual Basic  Copy Code

Sub myReport_QueryDetails(ByVal sender As Object, ByVal e As QueryDetailsEventArgs)

    Dim dataRange As DataRange = sender
    If (Not dataRange Is Nothing) Then

        If (dataRange.Name = "ProductsRange") Then

            Dim category = CType(e.MasterRow, XElement)

            e.Details = _
                From product In products _
                Where product.Element("CategoryID").Value = category.Element("CategoryID").Value

        End If

    End If

End Sub

The code above inspects the report element, which requests details. This is usually a DataRange or a PieChart object. Then, it identifies the object by inspecting its Name. If the object is the one we are expecting, we provide the necessary details by matching the CategoryID of the products in the database against the ID of the category associated with the data range.

 Note

Do not forget to include the System.Linq namespace in order the above code to compile successfully.

3. Running the application

Running the application will yield a result similar to the one illustrated by the following image: