ReportViewer Control Binds to Multiple Generic Collections
Introduction
Microsoft has gone to great lengths to make it easy, less code, with more drag and drop, to bind Datasets to DataGridViews and ReportViewer controls. What if you use strongly typed collections versus Datasets though? And what if you want one ReportViewer Control to preview and print all your application’s reports versus using one ReportViewer control for each report of the 200 your Accounting system will use? This article specifically target Winforms aplication, but the crucial ReportDataSource class exists in the System.Webforms namespace so this idea should work very similar in web applications though I haven't had time to test this yet.
How to instantiate a Collection full of data
Visual Studio knows how datasets are populated, but may not know as much about your favorite ORMapper Collection type. If you create a new Data Source, but choose Data Source Type Object versus Database, at runtime the ReportViewer Control knows how to create a default constructor instance of that object which might be a collection especially since in reports we tend to often report many rows of data. Your collection may have a complex method for instantiation of filtered and sorted collection of some type of object. If you subclass that collection with a collection whose default constructor knows how to fill the contents you want and then bind to that instance, the ReportViewer displays it fine, but you may find yourself subclassing a lot of types that way.
Another way is to tell the ReportViewer the proper type of collection so that the proper set of data fields can be dragged onto the report form and then just before runtime, switch that empty instance of the collection out with a properly filtered and sorted collection with code like this:
Me.BindingSource1.DataSource = myDatasource
Me.ReportViewer1.RefreshReport()
This assumes your ReportViewer1 Control has a
System.Windows.Forms.BindingSource Control named BindingSource1 associated with
it where myDatasource was instantiated like this in LLBLGen Pro the popular
ORMapper.
Dim myDatasource as New CustomerCollection
Dim typeFilter as ipredicate = CustomerFields.Type = 1
Dim nameSort as isortexpression = New SortExpression
nameSort.add(sortclausefactory.create(CustomerFieldIndex.Name,Ascending))
Your collection’s instantiation would differ.
When I first tried this, no fields of interest showed up in my new Data Source for dragging onto the Report surface. This is when I learned the value of using Generic collections with code like this:
(this collection had every property that my report required in one file so no
relation needed to be supported, parent properties at top for header and child
properties at bottom which will go into a table of rows in the report)
Code Listing #1
Imports system.collections.generic
Public Class PoItems : Inherits List(Of PoItem)
‘ from the above line the Data Source gets drag/drop with the fields below
End Class
Public Class PoItem
' Parent properties
Dim _poID As Integer
Dim _vendor As String
Dim _vAddr1 As String
Dim _vAddr2 As String
Dim _vcsz As String
Dim _shipVia As String
Dim _shipName As String
Dim _shipAddr1 As String
Dim _shipAddr2 As String
Dim _scsz As String
Dim _orderDate As DateTime
Dim _received As Boolean
' Child properties
Dim _prodNo As String
Dim _description As String
Dim _quantity As Integer
Dim _rate As Decimal
Dim _retail As Decimal
Public Property POID() As Integer
Get
Return _poID
End Get
Set(ByVal value As Integer)
_poID = value
End Set
End Property
‘ a few lines of code are removed here though they are important for binding
.
.
.
Public Property Retail() As Decimal
Get
Return _retail
End Get
Set(ByVal value As Decimal)
_retail = value
End Set
End Property
End Class
How can we get one form with a ReportViewer Control to display 200 reports?
There are four things that must be coordinated to allow a
report to occur:
1) A ReportViewer control
2) A Report or .rdlc file which is xml
3) A Datasource like a Dataset or a Collection
4) A BindingSource
Because there are four things to bind together we need a special kind of glue. It is called the ReportDataSource Class. See how it connects everything together below.
Myproj_EsnItems is the Namespace=projectname of the
datasource,
and EsnItems is the class type.
In myproj.Report1.rdlc the projectname is myproj, Report1
is report name with rdlc being for
report description language, client version. The reports won't run without
proper spelling and case.
Code Listing#2
Public Class Form2
Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
If Utility.WhichReport = 1 Then
Dim rptSource1 As New Microsoft.Reporting.WinForms.ReportDataSource
rptSource1.name = "WindowsApplication1_EsnItems"
rptSource1.value = Me.BindingSource1
Me.ReportViewer1.LocalReport.DataSources.Add(rptSource1)
Me.ReportViewer1.LocalReport.ReportEmbeddedResource = "myproj.Report1.rdlc"
Dim eitems As New EsnItems
Dim eitem As New EsnItem
Dim eitem1 As New EsnItem
eitem.SerialNo = "778889"
eitem.ModelNo = "244444444"
eitem.OrderNo = "233333332"
eitems.Add(eitem)
eitem1.SerialNo = "99333333"
eitem1.ModelNo = "44444455555"
eitem1.OrderNo = "3222222223"
eitems.Add(eitem1)
Me.BindingSource1.DataSource = eitems
Else
Dim rptSource2 As New Microsoft.Reporting.WinForms.ReportDataSource
rptSource2.name = "WindowsApplication1_PoItems"
rptSource2.value = Me.BindingSource1
Me.ReportViewer1.LocalReport.DataSources.Add(rptSource2)
Me.ReportViewer1.LocalReport.ReportEmbeddedResource = "myproj.Report2.rdlc"
Dim items As New PoItems
Dim item As New PoItem
Dim item1 As New PoItem
item.ProdNo = "2222222"
item.Quantity = "2"
item.Retail = "66"
items.Add(item)
item1.ProdNo = "1111111"
item1.Quantity = "1"
item1.Retail = "55"
items.Add(item1)
Me.BindingSource1.DataSource = items
End If
Me.ReportViewer1.RefreshReport()
End Sub
End Class
Conclusion
For more information on the ReportViewer control click here.
Now we have a local Reporting system that previews, prints, et al supported solely by Visual Studio that we can have a lot of control over by manipulating the properties of it and any kind of Object can be our data.