Using Events
Add events to layout elements to provide custom event-driven behavior. Two common uses of events used while creating reports are the PlaceHolder event handler and the DocumentLayout class's ReportDataRequired event handler. The PlaceHolder class dynamically places items such as barcodes or images on a document, while the ReportDataRequired dynamically adds data to a document.
PlaceHolder
The PlaceHolder class adds items such as barcodes or images to a details section of a report based on the current layout data. For example, the following code demonstrates adding an event handler using the LaidOut event to dynamically place a barcode.
public void ReportExample()
{
// Create the document's layout from a DLEX template
DocumentLayout layout = new DocumentLayout("Placeholder.dlex");
// Retrieve the place holder and attach the event handler
PlaceHolder barcodePlaceHolder = (PlaceHolder)layout.GetElementById("BarcodePlaceholder");
barcodePlaceHolder.LaidOut += new PlaceHolderLaidOutEventHandler(BarcodePlaceHolder_LaidOut);
// Specify the data to be used
LayoutData layoutData = new LayoutData();
layoutData.Add("PreparedFor", "Alex Smith");
layoutData.Add("Orders", listOfOrders);
// Layout the document and save the PDF
Document document = layout.Layout(layoutData);
document.Draw(outputFilePath);
}
public void BarcodePlaceHolder_LaidOut(object sender, PlaceHolderLaidOutEventArgs e)
{
Code128 barcode = new Code128(e.LayoutWriter.Data["OrderID"].ToString(), 0, 0, e.ContentArea.Height);
barcode.X += (e.ContentArea.Width - barcode.GetSymbolWidth()) / 2;
barcode.ShowText = false;
e.ContentArea.Add(barcode);
}
Public Sub void PlaceHolderExample()
' Create the document's layout from a DLEX template
Dim MyLayout As DocumentLayout = New DocumentLayout("Placeholder.dlex")
' Retrieve the place holder And attach the event handler
Dim MyBarcodePlaceHolder As PlaceHolder = MyLayout.GetElementById("BarcodePlaceholder")
AddHandler MyBarcodePlaceHolder.LaidOut, AddressOf BarcodePlaceholder_LaidOut
' Specify the data to be used
Dim MyLayoutData As LayoutData = New LayoutData()
MyLayoutData.Add("PreparedFor", "Alex Smith")
MyLayoutData.Add("Orders", MyListOfOrders)
' Layout the document And save the PDF
Dim MyDocument As Document = MyLayout.Layout(MyLayoutData)
MyDocument.Draw(outputFilePath)
End Sub
Private Sub BarcodePlaceholder_LaidOut(ByVal sender As Object, ByVal e As PlaceHolderLaidOutEventArgs)
Dim MyBarcode As Code128 = New Code128(e.LayoutWriter.Data("OrderID").ToString(), 0, 0, e.ContentArea.Height)
MyBarcode.X += (e.ContentArea.Width - MyBarcode.GetSymbolWidth()) / 2
MyBarcode.ShowText = False
e.ContentArea.Add(MyBarcode)
End Sub
The sample code first retrieves the layout element with the id of BarcodePlaceholder
as a PlaceHolder. It then attaches the PlaceHolder_LaidOut callback function to the LaidOut event. Then, while the document is being created or "laid out", the event is called, and the code in the fired event creates the barcode and places it in the correct document location.
ReportDataRequired
The ReportDataRequired event handler triggers an event and executes a callback function every time a report or sub-report element is parsed in a DLEX file.
The following code assigns a callback named LayoutReport_ReportDataRequired
to the ReportDataRequired event.
DocumentLayout layoutReport = new DocumentLayout(@"..\..\SubReportWithData.dlex");
layoutReport.ReportDataRequired += LayoutReport_ReportDataRequired;
Dim layoutReport As DocumentLayout = New DocumentLayout("..\..\SubReportWithData.dlex")
AddHandler layoutReport.ReportDataRequired, AddressOf LayoutReport_ReportDataRequired
The ReportDataRequired event requires a callback function. When defining that function, it must have the same signature as the ReportDataRequired event's ReportDataRequiredEventHandler.
public delegate void ReportDataRequiredEventHandler(object sender, ReportDataRequiredEventArgs args);
Public Delegate Sub ReportDataRequiredEventHandler(ByVal sender As Object, ByVal args As ReportDataRequiredEventArgs)
For example, you might assign the following callback to the event.
private static void LayoutReport_ReportDataRequired(object sender, ReportDataRequiredEventArgs args)
Private Shared Sub LayoutReport_ReportDataRequired(ByVal sender As Object, ByVal args As ReportDataRequiredEventArgs)
This callback passes the sender and ReportDataRequiredEventArgs as parameters. The ReportDataRequiredEventArgs arguments consist of the DataProviderStack, ReportData, ElementId, and DataName.
ReportDataRequired and SQL Reports
Use the ReportDataRequired event handler combined with SQL to create data-driven reports and sub-reports. Understanding how to create an event-driven report can be tricky, so consider an example.
DLEX File
First, consider the following DLEX layout template.
<?xml version="1.0"?>
<document version="1.2" id="Document1" author="" keywords="" title="" subject="" xsi:schemaLocation="http://www.dynamicpdf.com http://www.dynamicpdf.com/schemas/DLEX12.xsd" xmlns="http://www.dynamicpdf.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<page id="Page1">
<recordBox font="Helvetica" fontSize="12" id="RecordBox6" x="412" y="0" width="100" height="14" dataFormat="" dataName="SUM(Amount,MainReport,SubReport)"/>
</page>
<report id="MainReport">
<template id="Template1"/>
<header height="50" id="Header1"/>
<detail height="71.5" id="Detail1">
<subReport dataName="Test" id="SubReport" x="0" y="0" width="512">
<header height="15" id="Header2">
<label font="Helvetica" fontSize="12" id="Label1" x="0" y="0" width="60.1" height="14" text="Customer:"/>
<recordBox font="Helvetica" fontSize="12" id="RecordBox1" x="60" y="0" width="452" height="14" dataFormat="" dataName="CompanyName"/>
</header>
<detail height="15" id="Detail2">
<recordBox font="Helvetica" fontSize="12" id="RecordBox3" x="412" y="0" width="100" height="14" align="right" dataFormat="" dataName="Amount"/>
<recordBox font="Helvetica" fontSize="12" id="RecordBox2" x="0" y="0" width="308" height="14" dataFormat="" dataName="ProductName"/>
<recordArea font="Helvetica" fontSize="12" id="RecordArea1" x="310" y="0" width="100" height="14">
<text>|#Amount#|</text>
</recordArea>
</detail>
<footer height="15" id="Footer2">
<recordBox font="Helvetica" fontSize="12" id="RecordBox4" x="412" y="0" width="100" height="14" align="right" dataFormat="" dataName="SUM(Amount)"/>
<recordArea font="Helvetica" fontSize="12" id="RecordArea2" x="310" y="-0.5" width="100" height="14">
<text>|#SUM(Amount)#|</text>
</recordArea>
</footer>
</subReport>
</detail>
<footer height="50" id="Footer1">
<recordBox font="Helvetica" fontSize="12" id="RecordBox5" x="412" y="0" width="100" height="14" dataFormat="" dataName="SUM(Amount,MainReport,SubReport)"/>
</footer>
</report>
</document>
The DLEX template consists of a report with the id of MainReport
and a nested sub-report with the id SubReport
. The main report is a company listing, while the sub-report is a record listing of the company's products and costs.
Report Document
Now consider the method that creates the DocumentLayout. It assigns a callback function named LayoutReport_ReportDataRequired to the ReportDataRequired event. This event is fired, and the callback is invoked for each occurrence of a report and subReport element in a DLEX template. In the example above, the event is fired twice, once for the report and once for the sub-report.
private static Document SubReportWithData()
{
// Create the document's layout from a DLEX template
DocumentLayout layoutReport = new DocumentLayout(@"..\..\SubReportWithData.dlex");
layoutReport.ReportDataRequired += LayoutReport_ReportDataRequired;
// Specify the data.
LayoutData layoutData = new LayoutData();
//layoutData.Add("OrderLessThan", 10252);
layoutData.Add("OrderLessThan", 10258);
// Layout the document and save the PDF
Document document = layoutReport.Layout(layoutData);
return document;
}
Private Shared Function SubReportWithData() As Document
' Create the document's layout from a DLEX template
Dim layoutReport As DocumentLayout = New DocumentLayout("..\..\SubReportWithData.dlex")
AddHandler layoutReport.ReportDataRequired, AddressOf LayoutReport_ReportDataRequired
' Specify the data.
Dim layoutData As LayoutData = New LayoutData()
'layoutData.Add("OrderLessThan", 10252)
layoutData.Add("OrderLessThan", 10258)
' Layout the document And save the PDF
Dim document As Document = layoutReport.Layout(layoutData)
Return document
End Function
Event Handler
A callback's implementation method is where the dynamic processing occurs that replaces the records in the DLEX template with data obtained from an SQL query.
private static void LayoutReport_ReportDataRequired(object sender, ReportDataRequiredEventArgs args) {
if (args.ElementId == "MainReport") // The ID of the report
{
string connectionString = "Data Source=(local);Initial Catalog=Northwind;Integrated Security=true";
string sqlString =
"SELECT OrderID, o.CustomerID, c.CompanyName, o.OrderDate " +
"FROM Orders o " +
"JOIN Customers c ON o.CustomerID = c.CustomerID " +
"WHERE o.OrderID < " + args.Data["OrderLessThan"];
SqlConnection connection = new SqlConnection(connectionString);
SqlCommand command = new SqlCommand(sqlString, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
args.ReportData = new DataReaderReportData(connection, reader);
}
else if (args.ElementId == "SubReport")
{
string connectionString = "Data Source=(local);Initial Catalog=Northwind;Integrated Security=true";
string sqlString =
"SELECT ProductName, Amount = Quantity * od.UnitPrice " +
"FROM [Northwind].[dbo].[Order Details] od " +
"JOIN Products p ON od.ProductID = p.ProductID " +
"WHERE OrderID = " + args.Data["OrderID"];
SqlConnection connection = new SqlConnection(connectionString);
SqlCommand command = new SqlCommand(sqlString, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
args.ReportData = new DataReaderReportData(connection, reader);
}
}
Private Shared Sub LayoutReport_ReportDataRequired(ByVal sender As Object, ByVal args As ReportDataRequiredEventArgs)
If args.ElementId = "MainReport" Then ' The Then ID Of the report
Dim connectionString As String = "Data Source=(local)Initial Catalog=NorthwindIntegrated Security=true"
Dim sqlString As String =
"SELECT OrderID, o.CustomerID, c.CompanyName, o.OrderDate " &
"FROM Orders o " &
"JOIN Customers c ON o.CustomerID = c.CustomerID " &
"WHERE o.OrderID < " & args.Data("OrderLessThan")
Dim connection As SqlConnection = New SqlConnection(connectionString)
Dim Command As SqlCommand = New SqlCommand(sqlString, connection)
connection.Open()
Dim reader As SqlDataReader = Command.ExecuteReader()
args.ReportData = New DataReaderReportData(connection, reader)
ElseIf args.ElementId = "SubReport" Then
Dim connectionString As String = "Data Source=(local)Initial Catalog=NorthwindIntegrated Security=true"
Dim sqlString As String =
"SELECT ProductName, Amount = Quantity * od.UnitPrice " &
"FROM [Northwind].[dbo].[Order Details] od " &
"JOIN Products p ON od.ProductID = p.ProductID " &
"WHERE OrderID = " & args.Data("OrderID")
Dim connection As SqlConnection = New SqlConnection(connectionString)
Dim Command As SqlCommand = New SqlCommand(sqlString, connection)
connection.Open()
Dim reader As SqlDataReader = Command.ExecuteReader()
args.ReportData = New DataReaderReportData(connection, reader)
End If
End Sub
The record processing is straightforward. Each record from the SQL dataset gets replaced by a record element in the DLEX file. However, in this example, the event handler is fired twice. The first time fired, it processes the main report by querying the data source for customer data. The second time fired, it processes the sub-report data. A complete PDF is created by applying the data from the two datasets to the DLEX template.