Microsoft has gone to great lengths to make it easy, requiring
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 instead of using one ReportViewer control for each
report of the 200 your accounting system will use?
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 instead of Database, at runtime,
the ReportViewer Control will know how to create a default constructor instance
of that object, which might be a collection, especially since in reports we often
tend to report many rows of data. Your collection may have a complex method for
instantiation of a 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. Then, just before runtime, switch that empty instance of the
collection out with a properly filtered and sorted collection with code like
Me.BindingSource1.DataSource = myDatasource
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
Dim myDatasource as New CustomerCollection
Dim typeFilter as ipredicate = CustomerFields.Type = 1
Dim nameSort as isortexpression = New SortExpression
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 in Code Listing #1.
Note: This collection had every property that my report required in one file,
so no relation needed to be supported; parent properties are at the top for the
header, and child properties,which will go into a table of rows in the report, are
at the bottom.
Code Listing #1
Public Class PoItems : Inherits List(Of PoItem) ' from the above line the Data Source gets drag/drop with the fields below
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
<span lang=PT-BR>Dim _description As String</span>
<span lang=PT-BR> Dim _quantity As Integer</span>
<span lang=PT-BR> Dim _rate As Decimal</span>
<span lang=PT-BR> Dim _retail As Decimal</span>
<span lang=PT-BR> </span>
<span lang=PT-BR> </span>Public Property POID() As Integer
Set(ByVal value As Integer)
_poID = value
‘ a few lines of code are removed here though they are important for binding
Public Property Retail() As Decimal
Set(ByVal value As Decimal)
_retail = value
How Can We Get One Form With a ReportViewer Control to Display
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.
Note the string: myProj_EsnItems is constructed with exact
case like this:
Similarly, "myProj.Report1.rdlc" is constructed from exact case with ProjectName.ReportName.
If these strings do not use exact case spelling, the reports
will not run.
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 = "myProj_EsnItems"
rptSource1.value = Me.BindingSource1
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"
eitem1.SerialNo = "99333333"
eitem1.ModelNo = "44444455555"
eitem1.OrderNo = "3222222223"
Me.BindingSource1.DataSource = eitems
Dim rptSource2 As New Microsoft.Reporting.WinForms.ReportDataSource
rptSource2.name = "myProj_PoItems"
rptSource2.value = Me.BindingSource1
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"
item1.ProdNo = "1111111"
item1.Quantity = "1"
item1.Retail = "55"
Me.BindingSource1.DataSource = items
You can get the source code from the above code listing from
Microsoft has told me that this type of code only works for
Windows and not with the Web version of these classes. So for web versions,
create multiple ReportViewer controls on one form with only one visible at a
time for multiple form reporting; otherwise just use one form per report.
Now we have a local reporting system that previews, prints,
etc., supported solely by Visual Studio that we can have a lot of control over
by manipulating the its properties, and any kind of Object can be our data.