Click here to Skip to main content
Click here to Skip to main content

The WinForms ReportViewer and Multiple Report Definition Files

, 20 Apr 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
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:

  1. 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().
  2. 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

richardw48

United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 20 Apr 2009
Article Copyright 2009 by richardw48
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid