AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=762&pId=-1
Binding the ReportViewer Control to Multiple Generic Collections
page
by Terry Voss
Feedback
Average Rating: 
Views (Total / Last 10 Days): 44121/ 65

 

Introduction 

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?
ReportViewer Source    

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 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 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

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
  <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
    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.

Note the string: myProj_EsnItems is constructed with exact case like this:

ProjectName_ClassName.


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.

 

Code Listing#2

 
Public Class Form2
 
  Private Sub Form2_Load(ByVal sender As System.ObjectByVal 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.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 = "myProj_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

You can get the source code from the above code listing from this article's download.

Conclusion

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.

 


Product Spotlight
Product Spotlight 

©Copyright 1998-2021 ASPAlliance.com  |  Page Processed at 2021-02-25 2:27:47 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search