The WinForms ReportViewer and Multiple Report Definition Files






4.33/5 (2 votes)
All the information you need to get the WinForms ReportViewer to display a report in a .rdlc file.
Introduction
Have you ever grappled with the WinForms ReportViewer
in Visual Studio to get it to display a report other than the one that was designed with it using the wizard? This article shows you how to programmatically set up the ReportViewer
to display a report identified in a user-selected (.rdlc) report definition file.
Imagine that you have created a WinForms application in which you've designed a form to display pre-defined reports, and you want to give the user the option of selecting which report to display. This is mostly straightforward if your reports are included as an embedded resource within your application, because all the data sources, table definitions, SELECT
queries, and DATA are readily available within the application code. However, because the report definitions are embedded within your application executable, editing existing reports or adding new ones mean that you have to rebuild the executable and distribute it to all the users of your application. What if all you had to do was to send, instead, a new .rdlc file to be saved into a specified network folder called 'MyApp_Reports', and hey-presto, the report becomes immediately available in your application.
Using the code
OK, let's see how this can be accomplished. You need to create a new form that contains a Splitter
control, a ListView
control, and a ReportViewer
control. Put the ListView
into the left pane of the Splitter
and the ReportViewer
into the right pane. Set the 'Dock
' of both the ListView
and the ReportViewer to 'Fill
'. Now, in the code-behind of the form, add the following:
Imports System.Xml
Imports System.IO
Imports System.Data.SqlClient.SqlDataAdapter
Public Class Form1
'This gets the path to the folder where the report .rdlc files are stored
Dim mReportsFolder As String = My.Settings.ReportsFolder
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
'get a list of all the .rdlc files in the 'Reports' folder
Dim ReportsList() As String = _
Directory.GetFiles(mReportsFolder, "*.rdlc")
'this variable will hold the name of the report without the .rdlc extension
Dim sRptName As String
'for each of the files present in the folder add
'their names and paths into the listview control
For Each sRptPath As String In ReportsList
sRptName = Path.GetFileNameWithoutExtension(sRptPath)
Me.lvReports.Items.Add(sRptName, sRptPath)
Next
End Sub
If you were to run your application at this point, you should see the list view control populated with the names of the report files present in the chosen folder. We now want the user to be able to click on one of the report names in the list view and for our application to display the selected report in the report viewer. To do this, we need to respond to the ListView
's Click
event or the SelectedIndexChanged
event. I've chosen the latter; so in your code, add the following:
Private Sub lvReports_SelectedIndexChanged(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles lvReports.SelectedIndexChanged
If Me.lvReports.SelectedItems.Count = 0 Then Exit Sub
'ideally you would set the MultiSelect property of the Listview
'control to 'False' so the user can only select one report
'at a time but here I just get the first item
Dim lvi As ListViewItem = Me.lvReports.SelectedItems(0)
'my listview control sometimes fires this
'event when no item is selected, weird!
If lvi Is Nothing Then Exit Sub
'just in case there's a problem getting the data for a report
'I've used this boolean variable to allow us
'to skip the rendering of the report
Dim bSkipReport As Boolean = False
'let's now get the path to the report that the user selected
Dim rptPath As String = lvi.ImageKey
'Becasue the .rdlc report definition file is in fact an XML file
'we can now just get the bits we need to display the report,
'so let's load the file into an Xml document
Dim rptXML As New XmlDocument
rptXML.Load(sRptPath)
'get the connection string. Notice that the element
'is named 'ConnectString' not 'ConnectionString'
Dim sRptConnectionString As String = _
rptXML.GetElementsByTagName("ConnectString")(0).InnerText
'the SELECT command 's also available in the following element
Dim sRptCommandText As String = _
rptXML.GetElementsByTagName("CommandText")(0).InnerText
'the report needs to get its data from a pre-defined
'table that matches the SELECT command above
'but more about that in a minute. For now let's get its name
Dim sRptTableName As String = _
rptXML.GetElementsByTagName("rd:TableName")(0).InnerText
'the above table is contained within a dataset and is referenced by the report
'using the following format 'DataSetname_TableName'
Dim sRptDataSet_TableName As String = _
rptXML.GetElementsByTagName("DataSetName")(0).InnerText
'and the name of the dataset is...
Dim sDatasetName As String = _
rptXML.GetElementsByTagName("rd:DataSetName")(0).InnerText
'within the .rdlc file is the definition of the structure of the table
'in the form of a list of table columns or database fields
'so this next code fragment gets the field list
'and creates a new table having the relevant columns
Dim xmlFields As XmlNodeList = _
rptXML.GetElementsByTagName("Field")
Dim dt As New DataTable
dt.TableName = sRptTableName
For Each fld As XmlNode In xmlFields
Dim sFldName As String = fld.ChildNodes(0).InnerText
Dim sFldType As String = fld.ChildNodes(1).InnerText
Dim fldType As System.Type = Type.GetType(sFldType)
dt.Columns.Add(sFldName, fldType)
Next
'we've now finished with the xml file so we can dispose of it
rptXML = Nothing
'now we need to create the report's dataset and add the table to it
Dim mDataSet As DataSet = New DataSet(sDatasetName)
mDataSet.Tables.Add(dt)
'ok we're nearly there. All that's left to do is:
' - get the data into the table;
' - create the report's datasource; and
' - assign it to the report.
'but before we do that we clear out any previoulsy assigned datasource
Me.rptVwr.LocalReport.DataSources.Clear()
'and rest the ReportViewer to it's default settings
Me.rptVwr.Reset()
'now we create an SQL DataAdapter using
'the commandtext and connection from earlier
Try
Dim da As New SqlClient.SqlDataAdapter(sRptCommandText, _
sRptConnectionString)
da.Fill(mDataSet.Tables(sRptTableName))
Catch ex As Exception
MessageBox.Show("There was a problem getting the data for this report", _
"Report Data Error", _
MessageBoxButtons.OK, MessageBoxIcon.Warning)
bSkipReport = True
End Try
'we use a Try-catch block to trap an error getting the data
'if everything is OK let's go ahead and populate the report
If Not bSkipReport Then
'create a new report datasource
Dim rs As New Microsoft.Reporting.WinForms.ReportDataSource(_
sRptDataSet_TableName, mDataSet.Tables(sRptTableName))
'and assign it to the ReportViewer control
Me.rptVwr.LocalReport.DataSources.Add(rs)
'now assign the report definition file to the ReportViewer
Me.rptVwr.LocalReport.ReportPath = sRptPath
Me.rptVwr.LocalReport.DisplayName = _
Path.GetFileNameWithoutExtension(sRptPath)
'and finally get it to display the report and its data
Me.rptVwr.RefreshReport()
End If
End Sub
End Class
Points of interest
Two things:
- Always remember to issue the
ReportViewer.Reset
command before loading a new report file because theReportViewer
appears to retain the name of the previous data source even after callingMe.rptVwr.LocalReport.DataSources.Clear()
. - The .rdlc files contain two different elements named 'DataSet'. This is allowed in XML because the elements are children of different parent elements. Anyway, one
dataset
element references the container dataset, while the other is used as a 'pointer' to the table. This was initially quite confusing, and caused a lot of head scratching!
History
- Version 0.1 - 20/4/2009.