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
Dim mReportsFolder As String = My.Settings.ReportsFolder
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Dim ReportsList() As String = _
Directory.GetFiles(mReportsFolder, "*.rdlc")
Dim sRptName As String
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
Dim lvi As ListViewItem = Me.lvReports.SelectedItems(0)
If lvi Is Nothing Then Exit Sub
Dim bSkipReport As Boolean = False
Dim rptPath As String = lvi.ImageKey
Dim rptXML As New XmlDocument
rptXML.Load(sRptPath)
Dim sRptConnectionString As String = _
rptXML.GetElementsByTagName("ConnectString")(0).InnerText
Dim sRptCommandText As String = _
rptXML.GetElementsByTagName("CommandText")(0).InnerText
Dim sRptTableName As String = _
rptXML.GetElementsByTagName("rd:TableName")(0).InnerText
Dim sRptDataSet_TableName As String = _
rptXML.GetElementsByTagName("DataSetName")(0).InnerText
Dim sDatasetName As String = _
rptXML.GetElementsByTagName("rd:DataSetName")(0).InnerText
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
rptXML = Nothing
Dim mDataSet As DataSet = New DataSet(sDatasetName)
mDataSet.Tables.Add(dt)
Me.rptVwr.LocalReport.DataSources.Clear()
Me.rptVwr.Reset()
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
If Not bSkipReport Then
Dim rs As New Microsoft.Reporting.WinForms.ReportDataSource(_
sRptDataSet_TableName, mDataSet.Tables(sRptTableName))
Me.rptVwr.LocalReport.DataSources.Add(rs)
Me.rptVwr.LocalReport.ReportPath = sRptPath
Me.rptVwr.LocalReport.DisplayName = _
Path.GetFileNameWithoutExtension(sRptPath)
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 the ReportViewer
appears to retain the name of the previous data source even after calling Me.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