Crystal Reports Online Training

Learn Online, Anytime, Anywhere

Step-by-step online tutorials.

17.21 Dynamic Images

Printing Dynamic Images

I’ve received many email requests from people wanting to know how to print dynamic images on a report. If you’ve used previous versions of Crystal Reports with VB6, you know that there is a method that lets you load a new image onto the report for every record printed. Many people want that functionality back. Others want to have a field that serves as a link to an image. Rather than printing the link, they want the report to print the image that the link points to. Unfortunately, neither of these options is possible with .NET. There are only two ways to print dynamic images on a report. The first way is to store the image in a blob field in SQL Server or MS Access. Add this field to the report just like any other field and it is printed on the report.

If the images aren’t stored in the database table, they have to be located on the local computer or network drive. To print these files you have to create a DataTable in memory and load these images into each row of the DataTable.

I’m going to show you an example of how to implement the second option where the images are loaded into a datatable. But this example requires that you modify it for your particular circumstance. It’s not possible to illustrate all the ways you could add dynamic images to a report. More than likely you will have the file path stored in one of the fields in a table. I’m going to assume that is the case in this example and if you get your field names from somewhere else (e.g. hard-coded in the project, by reading the file list from the current directory, etc.), you can modify the example for your needs.

The overall process works as follows:

Load the existing dataset into memory. This is probably done by calling one of the FillDataSet() methods shown earlier.

Add a new column to the DataTable with a data type of Byte(). This column is where the image is stored.

Save this new table structure as an XML file using the DataSet.WriteXmlSchema() method (only do this the first time).

Create a new report using the schema file. Place all fields (including the image field) on the report.

When the application runs, read each record in the table and get the file path of the image. Use the file path to load the image into memory and save it to the new column of the row.

After looping through each row assign the DataTable’s DataSet to the ReportSource property of the report.

Preview or print the report.

Wow! That’s a lot of steps. I’m going to show you the final code that makes all this work and try to explain it as concisely and clearly as possible.

First of all, let’s create a new DataSet and load a DataTable. I’m going to make this simple by creating a new DataTable manually and populate it with a couple records. In your application, replace this code with a call to FillDataSet().

Listing 17-19. Printing dynamic images with a DataSet object.
[VB.NET]
Dim MyDataSet As New DataSet
Dim MyDataTable As DataTable
Dim MyDataRow As DataRow
Dim MyDataColumn As DataColumn
MyDataTable = New DataTable("ImageTable")
' Create first column
MyDataColumn = New DataColumn("PicNumber", GetType(System.Int32))
MyDataTable.Columns.Add(MyDataColumn)
'Field that points to the image file
MyDataColumn = New DataColumn("ImagePath", GetType(System.String))
MyDataTable.Columns.Add(MyDataColumn)
'Populate the table with dummy data
'Make sure your C Drive has two files called Image1.jpg and Image2.jpg
MyDataRow = MyDataTable.NewRow()
MyDataRow("PicNumber") = 1
MyDataRow("ImagePath") = "C:\Image1.jpg"
MyDataTable.Rows.Add(MyDataRow)
MyDataRow = MyDataTable.NewRow()
MyDataRow("PicNumber") = 2
MyDataRow("ImagePath") = "C:\Image2.jpg"
MyDataTable.Rows.Add(MyDataRow)
MyDataSet.Tables.Add(MyDataTable)
'Add the image column to the table - See Listing 17-19.
AddImageColumn(MyDataTable)
'Only do this when you first design the report
MyDataSet.WriteXmlSchema("ImageTable.xsd")
'Open the report and preview it
Dim MyReport as New CrystalReport1
MyReport.SetDataSource(MyDataSet)
CrystalReportViewer1.ReportSource = MyReport
[C#]
public void LoginToTables(string UserId, string Password, string ServerName, string DatabaseName)
{
CrystalReport1 MyReport = new CrystalReport1();
CrystalDecisions.Shared.TableLogOnInfo MyLogonInfo;
CrystalDecisions.Shared.ConnectionInfo MyConnectionInfo = new CrystalDecisions.Shared.ConnectionInfo();
if (ServerName != "")
{
MyConnectionInfo.ServerName = ServerName;
MyConnectionInfo.DatabaseName = DatabaseName;
}
MyConnectionInfo.UserID = UserId;
MyConnectionInfo.Password = Password;
foreach (CrystalDecisions.CrystalReports.Engine.Table MyTable in MyReport.Database.Tables)
{
MyLogonInfo = MyTable.LogOnInfo;
MyLogonInfo.ConnectionInfo = MyConnectionInfo;
MyTable.ApplyLogOnInfo(MyLogonInfo);
//Note: The next line is only necessary for SQL Server
if (ServerName != "")
{
MyTable.Location = MyTable.Location.Substring(MyTable.Location.LastIndexOf(".")+1);
}
}
crystalReportViewer1.ReportSource = MyReport;
}

This code creates a DataTable with two columns: PicNumber and ImagePath. The ImagePath column points to the location of the image. I put two images on the root directory of the C drive.

The next step adds a column to the table to hold the image. The following code is in a generic method so you can easily drop it into your own project with no modifications.

Listing 17-20. Create a table column for the image.
[VB.NET]
Public Shared Sub AddImageColumn(ByVal MyDataTable As DataTable, ByVal FieldName As String)
'Create the column to hold the binary image
Dim MyDataColumn As DataColumn = New DataColumn(FieldName, GetType(System.Byte()))
MyDataTable.Columns.Add(MyDataColumn)
End Sub
[C#]
public void AddImageColumn(DataTable MyDataTable, string FieldName)
{
//Create the column to hold the binary image
DataColumn MyDataColumn = new DataColumn(FieldName, Type.GetType("System.Byte[]"));
MyDataTable.Columns.Add(MyDataColumn);
}

This procedure takes a DataTable object and the name of the column that holds the binary image. It creates a new column of type Byte().

After adding the new column, write the XML schema file and build the report using this schema file. Don’t forget to comment out this line after you create the schema file!

Even though the new column is of type Byte(), when this is saved to an XML schema file the column data type is automatically changed to base64Binary. If you are creating the DataSet manually with the IDE you have to specify the data type to be base64Binary.

Once the report is designed, open the DataTable again and load in the images.

Listing 17-21. Process each row in the table.
[VB.NET]
Public Shared Sub LoadAllImages(ByVal MyDataTable As DataTable, ByVal FilePathField As String, ByVal ImageField As String)
'Loop through all the rows and load the images
For Each MyDataRow As DataRow In MyDataTable.Rows
LoadImage(MyDataRow, ImageField, MyDataRow.Item(FilePathField))
Next
End Sub
[C#]
public void LoadAllImages(DataTable MyDataTable, string FilePathField, string ImageField)
{
//Loop through the rows and load the images
foreach(DataRow MyDataRow in MyDataTable.Rows)
{
LoadImage(MyDataRow, ImageField, MyDataRow[FilePathField].ToString());
}
}

The LoadAllImages() procedure loops through each row in the DataTable and calls the LoadImage() procedure to load a single image. Pass the LoadAllImages() procedure the DataTable object, the name of the column that has the image location, and the name of the column that will hold the binary image.

Listing 17-22. Load a single image into a DataRow.
[VB.NET]
Public Shared Sub LoadImage(ByVal MyDataRow As System.data.DataRow, ByVal FilePath As String, ByVal ImageField As String)
Dim fs As New System.IO.FileStream(FilePath, IO.FileMode.Open, System.IO.FileAccess.Read)
Dim Image(fs.Length) As Byte
fs.Read(Image, 0, fs.Length)
fs.Close()
MyDataRow.Item(ImageField) = Image
End Sub
[C#]
public void LoadImage(DataRow MyDataRow, string ImageField, string FilePath)
{
System.IO.FileStream fs = new System.IO.FileStream(FilePath, System.IO.FileMode.Open,
System.IO.FileAccess.Read);
Byte[] Image = new Byte[fs.Length];
fs.Read(Image, 0, (int)fs.Length);
fs.Close();
MyDataRow[ImageField] = Image;
}

The LoadImage() procedure creates a new FileStream object and loads the image into a Byte array. Then it saves this Byte array to the DataRow in the column.

Once all the rows have been updated with their appropriate image, pass the DataSet to the report and preview or print it.