Table of Contents

Virtual datasets (VB.Net / netframework)

Note

This demo is available in your FlexCel installation at <FlexCel Install Folder>\samples\vb\VS2022\netframework\20.Reports\96.Virtual Datasets and also at https:​//​github.​com/​tmssoftware/​TMS-​FlexCel.​NET-​demos/​tree/​master/​vb/​VS2022/​netframework/​Modules/​20.​Reports/​96.​Virtual Datasets

Overview

Normally FlexCelReport uses DataTables for its data. On reporting we can see DataTables as "overcharged" arrays, with added functionality like filtering or sorting. They are also really powerful and fast, so using them is normally the best option. But in some cases you might have very large bussiness objects and would like to use them directly without copying them first into a DataTable. On those cases, you can create your own VirtualDataset and VirtualDatasetState descendants to do this task.

This is an advanced topic. Remember to read the [Appendix: Virtual DataSets on](~/guides/reports-developer-guide.md#appendix:-virtual-datasets -on) the Reports developer guide.

Concepts

  • How to create a VirtualDataset descendant to encapsulate an arbitrary object and make a report with it. Two descendants are shown, one very simple with the minimum functionality needed, and other that fully implements all the features.

  • As you can see on the ComplexVirtualArrayDataSource example, to provide all the functionality you do not need to override too many methods. But you should implement very efficient methods. If you do not, probably the performance dumping everything to a DataSet would be faster.

  • How to create "infographics". this is based on the article: http://www.juiceanalytics.com/weblog/?p=236

Files

AssemblyInfo.vb

Imports System.Reflection
Imports System.Runtime.CompilerServices

'
' General Information about an assembly is controlled through the following 
' set of attributes. Change these attribute values to modify the information
' associated with an assembly.
'
<Assembly: AssemblyTitle("")>
<Assembly: AssemblyDescription("")>
<Assembly: AssemblyConfiguration("")>
<Assembly: AssemblyCompany("")>
<Assembly: AssemblyProduct("")>
<Assembly: AssemblyCopyright("(c) 2002 - 2014 TMS Software")>
<Assembly: AssemblyTrademark("")>
<Assembly: AssemblyCulture("")>

'
' Version information for an assembly consists of the following four values:
'
'      Major Version
'      Minor Version 
'      Build Number
'      Revision
'
' You can specify all the values or you can default the Revision and Build Numbers 
' by using the '*' as shown below:

<Assembly: AssemblyVersion("6.2.1.0")>

'
' In order to sign your assembly you must specify a key to use. Refer to the 
' Microsoft .NET Framework documentation for more information on assembly signing.
'
' Use the attributes below to control which key is used for signing. 
'
' Notes: 
'   (*) If no key is specified, the assembly is not signed.
'   (*) KeyName refers to a key that has been installed in the Crypto Service
'       Provider (CSP) on your machine. KeyFile refers to a file which contains
'       a key.
'   (*) If the KeyFile and the KeyName values are both specified, the 
'       following processing occurs:
'       (1) If the KeyName can be found in the CSP, that key is used.
'       (2) If the KeyName does not exist and the KeyFile does exist, the key 
'           in the KeyFile is installed into the CSP and used.
'   (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
'       When specifying the KeyFile, the location of the KeyFile should be
'       relative to the project output directory which is
'       %Project Directory%\obj\<configuration>. For example, if your KeyFile is
'       located in the project directory, you would specify the AssemblyKeyFile 
'       attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
'   (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
'       documentation for more information on this.
'
<Assembly: AssemblyDelaySign(False)>
<Assembly: AssemblyKeyFile("")>
<Assembly: AssemblyKeyName("")>

ComplexVirtualArrayDataSource.vb

Imports System.Globalization

Imports FlexCel.Report
Imports System.Collections


Namespace VirtualDatasets
	''' <summary>
	''' This class implements the complete functionality needed to run a FlexCelReport from an array of objects.
	''' Sorting/Filtering on the config sheet is allowed for this datasource, but they are not implemented on an efficient way.
	''' If you do not have an efficient way to do those things (using indexes) and you plan to do them, you will probably get better performance 
	''' using Datasets.
	''' </summary>
	Public Class ComplexVirtualArrayDataSource
		Inherits VirtualDataTable

		#Region "Private variables"
		Private FData()() As Object
		Private FColumnCaptions() As String
		#End Region

		#Region "Constructors"
		Public Sub New(ByVal aCreatedBy As VirtualDataTable, ByVal aData()() As Object, ByVal aColumnCaptions() As String, ByVal aTableName As String)
			MyBase.New(aTableName, aCreatedBy)
			FData = aData
			FColumnCaptions = aColumnCaptions
		End Sub
		#End Region

		#Region "Columns"
		Public Overrides ReadOnly Property ColumnCount() As Integer
			Get
				Return FColumnCaptions.Length
			End Get
		End Property

		Public Overrides Function GetColumn(ByVal columnName As String) As Integer
			'not very optimized method, but this is just a demo.
			If columnName Is Nothing Then
				Return -1
			End If
			columnName = columnName.Trim()
			For i As Integer = 0 To FColumnCaptions.Length - 1
				If String.Compare(FColumnCaptions(i), columnName, True) = 0 Then
					Return i
				End If
			Next i
			Return -1
		End Function

		Public Overrides Function GetColumnName(ByVal columnIndex As Integer) As String
			Return FColumnCaptions(columnIndex)
		End Function

		Public Overrides Function GetColumnCaption(ByVal columnIndex As Integer) As String
			Return FColumnCaptions(columnIndex)
		End Function

		#End Region

		#Region "Settings"
		Public Overrides ReadOnly Property Locale() As System.Globalization.CultureInfo
			Get
				Return CultureInfo.CurrentCulture
			End Get
		End Property
		#End Region

		#Region "Create State"
		Public Overrides Function CreateState(ByVal sort As String, ByVal masterDetailLinks() As TMasterDetailLink, ByVal splitLink As TSplitLink) As VirtualDataTableState
			Return New ComplexVirtualArrayDataSourceState(Me, sort, masterDetailLinks, splitLink)
		End Function

		#End Region

		#Region "Data"
		Public ReadOnly Property Data() As Object()()
			Get
				Return FData
			End Get
		End Property

		Public Overrides Function FilterData(ByVal newDataName As String, ByVal rowFilter As String) As VirtualDataTable
			If rowFilter Is Nothing OrElse rowFilter.Length = 0 Then
				Return New ComplexVirtualArrayDataSource(Me, FData, FColumnCaptions, newDataName) 'no need to copy the data since it is invariant.
			End If

			Dim Relationship As RelationshipType = RelationshipType.Equals
			'on this demo we will only support filters of the type: "field = value", "field > value" or "field < value"
			Dim filteredData() As String = rowFilter.Split("="c)
			If filteredData Is Nothing OrElse filteredData.Length <> 2 Then
				filteredData = rowFilter.Split("<"c)
				If filteredData Is Nothing OrElse filteredData.Length <> 2 Then
					filteredData = rowFilter.Split(">"c)
					If filteredData Is Nothing OrElse filteredData.Length <> 2 Then
						Throw New Exception("Filter """ & rowFilter & """ is invalid. The dataset """ & TableName & """ only supports filters of the type ""field =/>/< value"".")
					Else
						Relationship = RelationshipType.BiggerThan
					End If
				Else
					Relationship = RelationshipType.LessThan
				End If

			End If

			Dim ColIndex As Integer = GetColumn(filteredData(0).Trim())
			If ColIndex < 0 Then
				Throw New Exception("Filter """ & rowFilter & """ is invalid. Can not find column """ & filteredData(0).Trim() & """")
			End If

			'Remember, this is only a demo to show what to do in this event. This code is not good code to use on 
			'a real application!. You should use some index here to find the data or this would crawl on large objects.

			Dim Result As New List(Of Object())()
			Dim SearchValue As String = filteredData(1).Trim()
			Dim Searchfloat As Double = 0
			If Relationship <> RelationshipType.Equals Then 'when relationship is not equals, we will assume columns are numbers. This is because we do not have any type definition for our columns on this simple example.
				Searchfloat = Convert.ToDouble(filteredData(1).Trim())
			End If

			For Each Row As Object() In Data
				Select Case Relationship
					Case RelationshipType.Equals
						If Convert.ToString(Row(ColIndex)) = SearchValue Then
							Result.Add(Row) 'remember, data is invariant, so we do not need to clone Row.
						End If
					Case RelationshipType.LessThan
						If Convert.ToDouble(Row(ColIndex)) < Searchfloat Then
							Result.Add(Row) 'remember, data is invariant, so we do not need to clone Row.
						End If
					Case RelationshipType.BiggerThan
						If Convert.ToDouble(Row(ColIndex)) > Searchfloat Then
							Result.Add(Row) 'remember, data is invariant, so we do not need to clone Row.
						End If
				End Select
			Next Row

			Return New ComplexVirtualArrayDataSource(Me, Result.ToArray(), FColumnCaptions, newDataName)

		End Function

		Public Overrides Function GetDistinct(ByVal newDataName As String, ByVal filterFields() As Integer) As VirtualDataTable
			If filterFields Is Nothing OrElse filterFields.Length = 0 Then
				Return New ComplexVirtualArrayDataSource(Me, FData, FColumnCaptions, newDataName) 'no need to copy the data since it is invariant.
			End If

			Dim Result As New Dictionary(Of Object() , Object())()
			Dim Keys(filterFields.Length - 1) As Object
			For Each Row As Object() In FData
				For i As Integer = 0 To filterFields.Length - 1
					Keys(i) = Row(filterFields(i))
				Next i
				Result(Keys) = Keys
			Next Row

			Dim R(Data.Length - 1)() As Object
			Result.Keys.CopyTo(R, 0)

			Dim NewColumnCaptions(filterFields.Length - 1) As String
			For i As Integer = 0 To filterFields.Length - 1
				NewColumnCaptions(i) = FColumnCaptions(filterFields(i))
			Next i
			Return New ComplexVirtualArrayDataSource(Me, R, NewColumnCaptions, newDataName)

		End Function

		#End Region

	End Class

	Public Class ComplexVirtualArrayDataSourceState
		Inherits VirtualDataTableState

		#Region "Privates"
		Private SortedData()() As Object
		Private FilteredData As List(Of Object())
		#End Region

		#Region "Constructors"
		Public Sub New(ByVal aTableData As ComplexVirtualArrayDataSource, ByVal sort As String, ByVal masterDetailLinks() As TMasterDetailLink, ByVal splitLink As TSplitLink)
			MyBase.New(aTableData)
			If sort Is Nothing OrElse sort.Trim().Length = 0 Then
				SortedData = aTableData.Data 'no need to clone, this is invariant.
			Else
				SortedData = CType(aTableData.Data.Clone(), Object()())
				Dim sortcolumn As Integer = aTableData.GetColumn(sort)
				If sortcolumn < 0 Then
					Throw New Exception("Can not find column """ & sort & """ in dataset """ & TableName)
				End If
				Array.Sort(SortedData, New ArrayComparer(sortcolumn))
			End If


			'here we should use the data in masterdetaillinks and splitlink to create indexes to make the FilteredrowCount and MoveMasterRecord methods faster.
			'on this demo we are not going to do it.
			If (masterDetailLinks IsNot Nothing AndAlso masterDetailLinks.Length > 0) OrElse splitLink IsNot Nothing Then
				FilteredData = New List(Of Object())()
			End If
		End Sub
		#End Region

		#Region "Data"
		Private ReadOnly Property Data() As Object()()
			Get
				Return CType(TableData, SimpleVirtualArrayDataSource).Data
			End Get
		End Property

		''' <summary>
		''' Remember that this method should be fast!
		''' </summary>
		Public Overrides ReadOnly Property RowCount() As Integer
			Get
				If FilteredData Is Nothing Then
					Return SortedData.Length
				End If
				Return FilteredData.Count
			End Get
		End Property

		Public Overrides Function GetValue(ByVal column As Integer) As Object
			If FilteredData Is Nothing Then
				Return SortedData(Position)(column)
			End If
			Return CType(FilteredData(Position), Object())(column)
		End Function

		Public Overrides Function GetValue(ByVal row As Integer, ByVal column As Integer) As Object
			If FilteredData Is Nothing Then
				Return SortedData(row)(column)
			End If
			Return CType(FilteredData(row), Object())(column)
		End Function
		#End Region

		#Region "Move"
		Public Overrides Sub MoveFirst()
			'No need to do anything in an array, since "Position" is moved for us. 
			'If we had an IEnumerator for example as data backend, we would reset it here.
		End Sub

		Public Overrides Sub MoveNext()
			'No need to do anything in an array, since "Position" is moved for us. 
			'If we had an IEnumerator for example as data backend, we would move it here.
		End Sub

		#End Region


		#Region "Relationships"
		Public Overrides Sub MoveMasterRecord(ByVal masterDetailLinks() As TMasterDetailLink, ByVal splitLink As TSplitLink)
			'Here we need to modify FilteredData to contain only those records on FilteredData that are visible on this state.
			'If we created indexes on this method constructor's, this can be done fast.

			'on this example, as always, we will use a very slow method. The idea of this demo is not to demostrate how to do efficient code
			' (you probably have efficient methods on the bussines objects you are wrapping), but how to override the methods.

			If FilteredData Is Nothing Then 'this dataset is not on master-detail relationship and does not have any split relationship either.
				Return
			End If


			FilteredData.Clear()
			Dim ChildColumn(masterDetailLinks.Length - 1) As Integer
			For i As Integer = 0 To masterDetailLinks.Length - 1
				ChildColumn(i) = TableData.GetColumn(masterDetailLinks(i).ChildFieldName)
			Next i

			Dim SplitPos As Integer = 0
			Dim StartRow As Integer = 0
			If splitLink IsNot Nothing Then
				StartRow = splitLink.SplitCount * splitLink.ParentDataSource.Position
			End If

			For r As Integer = 0 To SortedData.Length - 1
				Dim Row() As Object = SortedData(r)
				Dim RowApplies As Boolean = True
				For i As Integer = 0 To masterDetailLinks.Length - 1
					Dim key As Object = masterDetailLinks(i).ParentDataSource.GetValue(masterDetailLinks(i).ParentField)
					If Convert.ToString(Row(ChildColumn(i))) <> Convert.ToString(key) Then
						RowApplies = False
						Exit For
					End If
				Next i

				If Not RowApplies Then 'the row does not fit this master detail relationship.
					Continue For
				End If

				SplitPos += 1
				If SplitPos <= StartRow Then 'we are not filling the correct split slot.
					Continue For
				End If

				FilteredData.Add(Row)
				If splitLink IsNot Nothing AndAlso FilteredData.Count >= splitLink.SplitCount Then
					Return
				End If
			Next r


		End Sub

		Public Overrides Function FilteredRowCount(ByVal masterDetailLinks() As TMasterDetailLink) As Integer
			Dim Result As Integer = 0

			Dim ChildColumn(masterDetailLinks.Length - 1) As Integer
			For i As Integer = 0 To masterDetailLinks.Length - 1
				ChildColumn(i) = TableData.GetColumn(masterDetailLinks(i).ChildFieldName)
			Next i

			For r As Integer = 0 To SortedData.Length - 1
				Dim Row() As Object = SortedData(r)
				Dim RowApplies As Boolean = True
				For i As Integer = 0 To masterDetailLinks.Length - 1
					Dim key As Object = masterDetailLinks(i).ParentDataSource.GetValue(masterDetailLinks(i).ParentField)
					If Convert.ToString(Row(ChildColumn(i))) <> Convert.ToString(key) Then
						RowApplies = False
						Exit For
					End If
				Next i

				If Not RowApplies Then 'the row does not fit this master detail relationship.
					Continue For
				End If

				Result += 1
			Next r

			Return Result

		End Function


		#End Region

	End Class

	Friend Enum RelationshipType
		Equals
		LessThan
		BiggerThan
	End Enum

	Public Class ArrayComparer
		Implements IComparer(Of Object())

		Private Column As Integer
		Private Shared Comparer As New CaseInsensitiveComparer()

		Public Sub New(ByVal aColumn As Integer)
			Column = aColumn
		End Sub

		Private Function IComparerGeneric_Compare(ByVal x() As Object, ByVal y() As Object) As Integer Implements IComparer(Of Object()).Compare
			Return (Comparer.Compare(x(Column), y(Column)))
		End Function

	End Class


End Namespace

Form1.Designer.vb

Imports System.Collections
Imports System.ComponentModel
Imports System.IO
Imports System.Reflection
Imports System.Resources
Imports System.Globalization
Imports FlexCel.Core
Imports FlexCel.XlsAdapter
Imports FlexCel.Report
Namespace VirtualDatasets
	Partial Public Class mainForm
		Inherits System.Windows.Forms.Form

		Private WithEvents button1 As System.Windows.Forms.Button
		Private saveFileDialog1 As System.Windows.Forms.SaveFileDialog
		Private label1 As System.Windows.Forms.Label
		Private WithEvents btnCancel As System.Windows.Forms.Button
		''' <summary>
		''' Required designer variable.
		''' </summary>
		Private components As System.ComponentModel.Container = Nothing

		''' <summary>
		''' Clean up any resources being used.
		''' </summary>
		Protected Overrides Sub Dispose(ByVal disposing As Boolean)
			If disposing Then
				If components IsNot Nothing Then
					components.Dispose()
				End If
			End If
			MyBase.Dispose(disposing)
		End Sub

		#Region "Windows Form Designer generated code"
		''' <summary>
		''' Required method for Designer support - do not modify
		''' the contents of this method with the code editor.
		''' </summary>
		Private Sub InitializeComponent()
			Me.button1 = New System.Windows.Forms.Button()
			Me.saveFileDialog1 = New System.Windows.Forms.SaveFileDialog()
			Me.label1 = New System.Windows.Forms.Label()
			Me.btnCancel = New System.Windows.Forms.Button()
			Me.SuspendLayout()
			' 
			' button1
			' 
			Me.button1.Anchor = (CType((System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles))
			Me.button1.BackColor = System.Drawing.Color.Green
			Me.button1.ForeColor = System.Drawing.Color.White
			Me.button1.Location = New System.Drawing.Point(96, 80)
			Me.button1.Name = "button1"
			Me.button1.Size = New System.Drawing.Size(112, 23)
			Me.button1.TabIndex = 0
			Me.button1.Text = "GO!"
			Me.button1.UseVisualStyleBackColor = False
'			Me.button1.Click += New System.EventHandler(Me.button1_Click)
			' 
			' saveFileDialog1
			' 
			Me.saveFileDialog1.Filter = "Excel Files|*.xls;*.xlsx;*.xlsm|Excel 97/2003|*.xls|Excel 2007|*.xlsx;*.xlsm|All files|*.*"
			Me.saveFileDialog1.RestoreDirectory = True
			' 
			' label1
			' 
			Me.label1.Location = New System.Drawing.Point(24, 24)
			Me.label1.Name = "label1"
			Me.label1.Size = New System.Drawing.Size(288, 24)
			Me.label1.TabIndex = 2
			Me.label1.Text = "A demo on how to use arbitrary objects on a report. "
			' 
			' btnCancel
			' 
			Me.btnCancel.Anchor = (CType((System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles))
			Me.btnCancel.BackColor = System.Drawing.Color.FromArgb((CInt((CByte(192)))), (CInt((CByte(0)))), (CInt((CByte(0)))))
			Me.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel
			Me.btnCancel.ForeColor = System.Drawing.Color.White
			Me.btnCancel.Location = New System.Drawing.Point(216, 80)
			Me.btnCancel.Name = "btnCancel"
			Me.btnCancel.Size = New System.Drawing.Size(112, 23)
			Me.btnCancel.TabIndex = 3
			Me.btnCancel.Text = "Cancel"
			Me.btnCancel.UseVisualStyleBackColor = False
'			Me.btnCancel.Click += New System.EventHandler(Me.btnCancel_Click)
			' 
			' mainForm
			' 
			Me.AutoScaleDimensions = New System.Drawing.SizeF(6F, 13F)
			Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
			Me.ClientSize = New System.Drawing.Size(360, 130)
			Me.Controls.Add(Me.btnCancel)
			Me.Controls.Add(Me.label1)
			Me.Controls.Add(Me.button1)
			Me.Name = "mainForm"
			Me.Text = "Virtual Datasets"
			Me.ResumeLayout(False)

		End Sub
		#End Region
	End Class
End Namespace


Form1.vb

Imports System.Collections
Imports System.ComponentModel
Imports System.IO
Imports System.Reflection
Imports System.Resources
Imports System.Globalization
Imports FlexCel.Core
Imports FlexCel.XlsAdapter
Imports FlexCel.Report


Namespace VirtualDatasets
	Partial Public Class mainForm
		Inherits System.Windows.Forms.Form

		Public Sub New()
			InitializeComponent()
		End Sub

		Private Sub button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles button1.Click
			AutoRun()
		End Sub

		Public Sub AutoRun()
			Dim DataPath As String = Path.Combine(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), ".."), "..") & Path.DirectorySeparatorChar

			If saveFileDialog1.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
				Dim SimpleData()() As Object = LoadDataSet(Path.Combine(DataPath, "Countries.txt"))
				Dim SimpleTable As New SimpleVirtualArrayDataSource(Nothing, SimpleData, New String() { "Rank", "Country", "Area", "Date" }, "SimpleTable")

				Using genericReport As New FlexCelReport(True)
					genericReport.AddTable("SimpleData", SimpleTable)

					Dim Complex1()() As Object = LoadDataSet(Path.Combine(DataPath, "Countries.txt"))
					Dim ComplexAreas As New ComplexVirtualArrayDataSource(Nothing, Complex1, New String() { "Rank", "Country", "Area", "Date" }, "ComplexAreas")
					Dim Complex2()() As Object = LoadDataSet(Path.Combine(DataPath, "Populations.txt"))
					Dim ComplexPopulations As New ComplexVirtualArrayDataSource(Nothing, Complex2, New String() { "Rank", "Country", "Population", "Date" }, "ComplexPopulations")

					genericReport.AddTable("ComplexAreas", ComplexAreas, TDisposeMode.DisposeAfterRun)
					genericReport.AddTable("ComplexPopulations", ComplexPopulations, TDisposeMode.DisposeAfterRun)



					genericReport.Run(Path.Combine(DataPath, "Virtual Datasets.template.xls"), saveFileDialog1.FileName)
				End Using

				If MessageBox.Show("Do you want to open the generated file?", "Confirm", MessageBoxButtons.YesNo) = System.Windows.Forms.DialogResult.Yes Then
					Process.Start(saveFileDialog1.FileName)
				End If
			End If
		End Sub


		Private Sub btnCancel_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnCancel.Click
			Close()
		End Sub

		Private Function LoadDataSet(ByVal filename As String) As Object()()
			'Let's create some bussiness object with random data.

			Dim Result As New ArrayList()
			Using sr As New StreamReader(Path.GetFullPath(filename))
				Dim line As String
				line = sr.ReadLine()
				Do While line IsNot Nothing
					Dim fields() As String = line.Split(ControlChars.Tab)
					'Zero validation here since this is a demo and will use always the same data. On a real app you should not expect your data to play nice
					Dim f(fields.Length - 1) As Object
					Dim s As String = TryCast(fields(0), String)
					f(0) = Convert.ToInt64(s)
					f(1) = fields(1)
					s = TryCast(fields(2), String)
					f(2) = CObj(Convert.ToInt64(s.Replace(",", "")))
					f(3) = fields(3)
					Result.Add(f)
					line = sr.ReadLine()
				Loop
			End Using

			Return CType(Result.ToArray(GetType(Object())), Object()())
		End Function
	End Class

End Namespace

Program.vb

Namespace VirtualDatasets
	Friend NotInheritable Class Program

		Private Sub New()
		End Sub

		''' <summary>
		''' The main entry point for the application.
		''' </summary>
		<STAThread> _
		Shared Sub Main()
			Application.EnableVisualStyles()
			Application.SetCompatibleTextRenderingDefault(False)
			Application.Run(New mainForm())
		End Sub
	End Class
End Namespace

SimpleVirtualArrayDataSource.vb

Imports System.Globalization

Imports FlexCel.Report

Namespace VirtualDatasets
	''' <summary>
	''' This class implements the minimum functionality needed to run a FlexCelReport from an array of objects.
	''' No Sorting/Filtering on the config sheet is allowed for this datasource, and you can not use it on master detail relationships.
	''' </summary>
	Public Class SimpleVirtualArrayDataSource
		Inherits VirtualDataTable

		#Region "Private variables"
		Private FData()() As Object
		Private FColumnCaptions() As String
		#End Region

		#Region "Constructors"
		Public Sub New(ByVal aCreatedBy As VirtualDataTable, ByVal aData()() As Object, ByVal aColumnCaptions() As String, ByVal aTableName As String)
			MyBase.New(aTableName, aCreatedBy)
			FData = aData
			FColumnCaptions = aColumnCaptions
		End Sub
		#End Region

		#Region "Columns"
		Public Overrides ReadOnly Property ColumnCount() As Integer
			Get
				Return FColumnCaptions.Length
			End Get
		End Property

		Public Overrides Function GetColumn(ByVal columnName As String) As Integer
			'not very optimized method, but this is just a demo.
			If columnName Is Nothing Then
				Return -1
			End If
			columnName = columnName.Trim()
			For i As Integer = 0 To FColumnCaptions.Length - 1
				If String.Compare(FColumnCaptions(i), columnName, True) = 0 Then
					Return i
				End If
			Next i
			Return -1
		End Function

		Public Overrides Function GetColumnName(ByVal columnIndex As Integer) As String
			Return FColumnCaptions(columnIndex)
		End Function

		Public Overrides Function GetColumnCaption(ByVal columnIndex As Integer) As String
			Return FColumnCaptions(columnIndex)
		End Function

		#End Region

		#Region "Settings"
		Public Overrides ReadOnly Property Locale() As System.Globalization.CultureInfo
			Get
				Return CultureInfo.CurrentCulture
			End Get
		End Property
		#End Region

		#Region "Create State"
		Public Overrides Function CreateState(ByVal sort As String, ByVal masterDetailLinks() As TMasterDetailLink, ByVal splitLink As TSplitLink) As VirtualDataTableState
			Return New SimpleVirtualArrayDataSourceState(Me)
		End Function

		#End Region

		#Region "Data"
		Public ReadOnly Property Data() As Object()()
			Get
				Return FData
			End Get
		End Property
		#End Region

		#Region "Filter"
		''' <summary>
		''' Even when we don't implement filter, we need to return a new instance when rowFilter is null.
		''' </summary>
		''' <param name="newDataName"></param>
		''' <param name="rowFilter"></param>
		''' <returns></returns>
		Public Overrides Function FilterData(ByVal newDataName As String, ByVal rowFilter As String) As VirtualDataTable
			If String.IsNullOrEmpty(rowFilter) Then
				Return New SimpleVirtualArrayDataSource(Me, Data, FColumnCaptions, TableName)
			End If
			Return MyBase.FilterData(newDataName, rowFilter)
		End Function
		#End Region

	End Class

	Public Class SimpleVirtualArrayDataSourceState
		Inherits VirtualDataTableState

		#Region "Constructors"
		Public Sub New(ByVal aTableData As SimpleVirtualArrayDataSource)
			MyBase.New(aTableData)
		End Sub
		#End Region

		#Region "Data"
		Private ReadOnly Property Data() As Object()()
			Get
				Return CType(TableData, SimpleVirtualArrayDataSource).Data
			End Get
		End Property

		''' <summary>
		''' Remember that this method should be fast!
		''' </summary>
		Public Overrides ReadOnly Property RowCount() As Integer
			Get
				Return Data.Length
			End Get
		End Property

		Public Overrides Function GetValue(ByVal column As Integer) As Object
			Return Data(Position)(column)
		End Function

		Public Overrides Function GetValue(ByVal row As Integer, ByVal column As Integer) As Object
			Return Data(row)(column)
		End Function

		Public Overrides Sub MoveFirst()
			'No need to do anything in an array, since "Position" is moved for us. 
			'If we had an IEnumerator for example as data backend, we would reset it here.
		End Sub

		Public Overrides Sub MoveNext()
			'No need to do anything in an array, since "Position" is moved for us. 
			'If we had an IEnumerator for example as data backend, we would move it here.
		End Sub

		#End Region

	End Class

End Namespace