﻿Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=2.02
@EndOfDesignText@
'Version 1.00

' Events for IDE autocompletion 
#Event: CellClick(col As Int, row As Int)
#Event: CellLongClick(col As Int, row As Int)
#Event: HeaderClick(col As Int)
#Event: HeaderLongClick(col As Int)

' RaisesSynchronousEvents declarations for Debugging
#RaisesSynchronousEvents: CellClick
#RaisesSynchronousEvents: CellLongClick
#RaisesSynchronousEvents: HeaderClick
#RaisesSynchronousEvents: HeaderLongClick

'Custom properties for Designer
#DesignerProperty: Key: NumberOfColumns, DisplayName: Number of columns, FieldType: Int, DefaultValue: 4, Description: Number of columns in the table.
#DesignerProperty: Key: RowHeight, DisplayName: Row height, FieldType: Int, DefaultValue: 30, Description: Height of both header and cell rows.
#DesignerProperty: Key: HeaderColor, DisplayName: Header color, FieldType: Color, DefaultValue: 0xFF444444, Description: Header background color.
#DesignerProperty: Key: HeaderTextColor, DisplayName: Header text color, FieldType: Color, DefaultValue: 0xFFFFFFFF, Description: Header text color.
#DesignerProperty: Key: TableColor, DisplayName: Table color, FieldType: Color, DefaultValue: 0xfff8f8f8, Description: Table and grid background color.
#DesignerProperty: Key:	CellTextColor, DisplayName: Cell text color, FieldType: Color, DefaultValue: 0xFF000000, Description: Cell text color.
#DesignerProperty: Key:	EvenRowColor,  DisplayName: Even row color,  FieldType: Color, DefaultValue: 0xffe8e8e8, Description: Even numbered row background color.
#DesignerProperty: Key:	OddRowColor, DisplayName: Odd row color, FieldType: Color, DefaultValue: 0xffe8e8e8, Description: Odd numbered row background color.
#DesignerProperty: Key:	SelectedRowColor, DisplayName: Selected row color, FieldType: Color, DefaultValue: 0xffadd8e6, Description: Selected row background color.
#DesignerProperty: Key:	SelectedCellColor, DisplayName: Selected cell color, FieldType: Color, DefaultValue: 0xffb0e0e6, Description: Selected cell background color.
#DesignerProperty: Key: TextSize, DisplayName: Table text size, FieldType: Int, DefaultValue: 14, Description: Text size for headers and rows.
#DesignerProperty: Key: TextAlignment, DisplayName: Text alignment, FieldType: String, DefaultValue: CENTER, List: LEFT|CENTER|RIGHT, Description: The text alignment for both headers and cells.
#DesignerProperty: Key: SortEnabled, DisplayName: Sort Enable, FieldType: Boolean, DefaultValue: True, Description: Enable table sorting when a header is clicked.

' Table2D version 1.0

' History
' 1.0	Heavily plagiarised from Klaus et al TableV3_02 but simplified and with an appropriate set of defaults to suit BasicIDE use.


Private Sub Class_Globals
	Private StringUtils1 As StringUtils
	Private SV As ScrollView2D
	Private Header As Panel
	Private TablePanel As Panel
	Private Callback As Object
	Private Event As String
	Private SelectedRow, SelectedColumn As Int
	Private DataList As List
	Private LabelsCache As List
	Private minVisibleRow, maxVisibleRow As Int
	Private visibleRows As Map
	Private NumberOfColumns, ColumnWidths() As Int
	Type RowCol (Row As Int, Col As Int)
	Private SelectedRowDrawables(), EvenRowDrawables(), OddRowDrawables() As Object
	Private SelectedCellDrawable As ColorDrawable
	Private ColumnSortAsNumber() As Boolean
	Private SortIndicator As Panel
	Private SortColumn As Int
	Private SortDirection As Boolean
	Private SortEnabled As Boolean = True
	
	'Table settings
	Public TextSize As Float
	Public CellTextColor, RowHeight, HeaderColor, HeaderTextColor, TableColor, EvenRowColor, OddRowColor, _ 
					SelectedRowColor, SelectedCellColor, Alignment As Int
	' default number of columns
	NumberOfColumns = 4
	' The row height for both table headers and rows
	RowHeight = 30dip
	' The background colour of the header titles
	HeaderColor = 0xff444444 ' Colors.DarkGray - much darker than Web DarkGray
	' The colour for the text in the header of the table	
	HeaderTextColor = Colors.White	
	' The background colour for the table - this shows as the colour of the grid in the table.
	TableColor = 0xfff8f8f8 ' Lighter than Colors.LightGray
	' The colour for the text in the rows of the table	
	CellTextColor = Colors.Black
	' The background colour for the even numbered rows of the table counting from 1
	EvenRowColor = 0xffe8e8e8  
	' The background colour for the odd numbered rows of the table countintg from 1
	OddRowColor =  0xffe8e8e8 
	' The background colour for the selected row of the table
	SelectedRowColor = 0xffadd8e6 ' Web LightBlue
	' The background colour for the selected cell of the table
	SelectedCellColor = 0xffb0e0e6 ' Web PowderBlue
	' The font size for both table headers and rows
	TextSize = 14
	' The text alignment of the text in both table headers and rows
	' Default is Gravity.CENTER. Use Gravity.LEFT or Gravity.RIGHT for other alignments.
	Alignment = Gravity.CENTER

End Sub

'Base - a panel that will be the parent For your custom view. The panel background And layout will be based on the values from the designer.
'Lbl - the purpose of the label is to hold all the text related properties. The label will not appear (unless you explicitly add it).
'Props - a Map with additional properties. The "activity" key holds a reference To the parent Activity object.
Public Sub DesignerCreateView (Base As Panel, Lbl As Label, Props As Map)
	TablePanel = Base
	NumberOfColumns = Props.Get("NumberOfColumns")
	RowHeight = DipToCurrent(Props.Get("RowHeight"))
	HeaderColor = Props.Get("HeaderColor")
	HeaderTextColor = Props.Get("HeaderTextColor")
	TableColor = Props.Get("TableColor")
	CellTextColor = Props.Get("CellTextColor")
	EvenRowColor = Props.Get("EvenRowColor")
	OddRowColor = Props.Get("OddRowColor")
	SelectedRowColor = Props.Get("SelectedRowColor")
	SelectedCellColor = Props.Get("SelectedCellColor")
	TextSize = Props.Get("TextSize")
	SortEnabled = Props.Get("SortEnabled")
	'sets the text Alignment property
	Select Props.Get("TextAlignment")
		Case "LEFT"
			Alignment = Bit.Or(Gravity.CENTER_VERTICAL, Gravity.LEFT)
		Case "CENTER"
			Alignment = Gravity.CENTER
		Case "RIGHT"
			Alignment = Bit.Or(Gravity.CENTER_VERTICAL, Gravity.RIGHT)
	End Select
	InitTable
End Sub


' PUBLIC SUBS AND PROPERTIES

' Initialize the Table with the owner module, event name and the number of columns specified.
Public Sub Initialize (CallbackModule As Object, EventName As String)
	Callback = CallbackModule
	Event = EventName
End Sub

' Add the table to a parent view of your choice (panel, activity, etc).
' Left, Top are the start point of the table in that view, width and height are the width and height of the table.
' This readies the table with column widths that will fill the width of the table with the number of columns specified.
' This will also initialise the table headers to a set of empty labels.
Public Sub AddToView(parent As Panel, Left As Int, Top As Int, Width As Int, Height As Int, Columns As Int)
''	AddToActivity(parent, Left, Top, Width, Height)
''End Sub
''
''' Add the table to an Activity.
''' Act is the Activity.
''Private Sub AddToActivity(Act As Activity, Left As Int, Top As Int, Width As Int, Height As Int)

	TablePanel.Initialize("")
	parent.AddView(TablePanel, Left, Top, Width, Height)
	NumberOfColumns = Columns
	Header.Initialize("")
	Header.Color = TableColor
	
	SortIndicator.Initialize("")

	SV.Initialize(Width, Height - RowHeight, "SV")
	SV.Panel.Color = TableColor
	SV.FadingEdges(False)
	
	TablePanel.AddView(Header, 0, 0 , Width, RowHeight)
	TablePanel.AddView(SV, 0, RowHeight , Width, Height - RowHeight)
	TablePanel.Color = TableColor
	InitTable
End Sub

Public Sub InitTable	
	Header.Initialize("")
	Header.Color = TableColor	
	
	SortIndicator.Initialize("")
	SV.Initialize(TablePanel.Width, TablePanel.Height - RowHeight, "SV")
	SV.Panel.Color = TableColor
	SV.FadingEdges(False)
	
	TablePanel.AddView(Header, 0, 0 , TablePanel.Width, RowHeight)
	TablePanel.AddView(SV, 0, RowHeight , TablePanel.Width, TablePanel.Height - RowHeight)
	TablePanel.Color = TableColor
	SetTableDefaults
	
End Sub	
	

' GENERAL TABLE MANIPULATIONS

'Clears the table ready for new data. 
Public Sub ClearAll
	ClearSort
	SV.Panel.RemoveAllViews
	SV.Panel.Height = 0
	innerClearAll
	SV.VerticalScrollPosition = 0
'	If visible Then
'		SV_ScrollChanged(0, 0)
'	End If
End Sub

' Clear the table and re-initialize ot empty with the specified number of columns
Public Sub ResizeTable(columns As Int)
	ClearAll
	NumberOfColumns = columns
	InitTable	
End Sub

' Clears the presently selected row indication and sets SelectedRow and SelectedColumn to -1
Public Sub ClearSelection
	'remove the color of previously selected row
	If SelectedRow > -1 Then
		If visibleRows.ContainsKey(SelectedRow) Then
			HideRow(SelectedRow)
			ShowRow(SelectedRow)
		End If
	End If
	SelectedRow = -1
	SelectedColumn = -1
End Sub

' Returns True if the specified row in the Table is visible.
Public Sub IsRowVisible(Row As Int) As Boolean
	Return Row < (SV.VerticalScrollPosition + SV.Height) / (RowHeight + 1) And _
		Row > SV.VerticalScrollPosition / RowHeight
End Sub

'Get the header titles as a String array.
Public Sub GetHeaders As String()
	Dim Titles(NumberOfColumns) As String
	For i = 0 To NumberOfColumns - 1
		Titles(i) = GetHeader(i)
	Next
	Return Titles
End Sub

'Makes the given row visible.
Public Sub JumpToRow(Row As Int)
	SV.VerticalScrollPosition = Row * RowHeight
End Sub
	
' Sets the individual column widths.
' Returns False if number of the column widths is not the same as the number of columns otherwise returns True.
Public Sub SetColumnWidths(Widths() As Int) As Boolean
	Dim v As View
	Dim SVwidth As Int = 0
	ColumnWidths = Widths
	Dim xpos As Int = 0
	If Widths.Length <> NumberOfColumns Then Return False
	For i = 0 To Widths.Length - 1
		v = Header.GetView(i)
		v.Width = Widths(i) - 1dip
		SVwidth = SVwidth + Widths(i)
		If i > 0 Then
			v.Left = xpos
			v.Width = ColumnWidths(i) - 1dip
		End If
		xpos = xpos + ColumnWidths(i)
	Next
	Dim lbls() As Label	
	For i = 0 To visibleRows.Size - 1
		lbls = visibleRows.GetValueAt(i)
		For lbl = 0 To lbls.Length - 1
			lbls(lbl).SetLayout(Header.GetView(lbl).Left, lbls(lbl).Top, Header.GetView(lbl).Width, RowHeight-1dip)
		Next
	Next
	SV.Panel.Width = SVwidth
	Header.Width = SVwidth
	SV_ScrollChanged(0, 0)
	Return True
End Sub

'Set the header titles.
'Returns False if the number of header items is not the same as number of columns otherwise returns True.
Public Sub SetHeaders(Titles() As String) As Boolean
	If  Titles.Length <> NumberOfColumns Then Return False
	Header.RemoveAllViews
	Dim xpos As Int = 0
	For i = 0 To NumberOfColumns - 1
		Dim l As Label
		l.Initialize("header")
		l.Gravity = Gravity.CENTER
		l.TextSize = TextSize
		l.Color = HeaderColor
		l.TextColor = HeaderTextColor
		l.Text = Titles(i)
		l.Tag = i
		Header.AddView(l, xpos, 0, ColumnWidths(i) - 1dip, RowHeight)
		xpos = xpos + ColumnWidths(i)
	Next
	Return True
End Sub


' CELL AND ROW VALUES

' Add a row of values to the table.
' Returns False if the number of items <> number of columns otherwise returns True.
Public Sub AddRow(Values() As String) As Boolean
	If Values.Length <> NumberOfColumns Then Return False
	DataList.Add(Values)
	Dim lastRow As Int
	lastRow = DataList.Size - 1
	If lastRow < (SV.VerticalScrollPosition + SV.Height) / RowHeight + 1 Then
		ShowRow(lastRow)
	End If
	SV.Panel.Height = DataList.Size * RowHeight
	Return True
End Sub

' Gets the value of the specified cell.
Public Sub GetCell(Col As Int, Row As Int) As String
	Dim values() As String
	values = DataList.Get(Row)
	Return values(Col)
End Sub

' Gets the value of the header for the specified column.
Public Sub GetHeader(Col As Int) As String
	Dim hdr As Label = Header.GetView(Col)
	Return hdr.Text
End Sub

' Gets the values of the given row as a String array.
Public Sub GetRow(Row As Int) As String()
	Dim values() As String
	values = DataList.Get(Row)
	Return values
End Sub

' Sets the value of the given cell.
Public Sub SetCell(Col As Int, Row As Int, Value As String)
	Dim values() As String
	values = DataList.Get(Row)
	values(Col) = Value
	If visibleRows.ContainsKey(Row) Then
		Dim lbls() As Label
		lbls = visibleRows.Get(Row)
		lbls(Col).Text = Value
	End If
End Sub

' Sets the values of the given row from the provided String array.
' Returns False if the number of columns <> Values.Length otherwise return True
Public Sub SetRow(Row As Int, Values() As String) As Boolean
	Dim rowvals() As String
	rowvals = DataList.Get(Row)
	If rowvals.Length <> Values.Length Then
		Return False
	End If
	For i = 0 To Values.Length - 1
		rowvals(i) = Values(i)
	Next
	If visibleRows.ContainsKey(Row) Then
		Dim lbls() As Label
		lbls = visibleRows.Get(Row)
		For i = 0 To Values.Length - 1
			lbls(i).Text = Values(i)
		Next
	End If
	Return True
End Sub

' Clear the previous table and loads the CSV file to the table.
' You should first add the Table to the activity before calling this method.
' SetHeaderTitles and SetColumnWidths need to be called after LoadTableFromCSV as this resets the table to the defaults.
Public Sub LoadTableFromCSV(Dir As String, Filename As String, HeadersExist As Boolean)
	Dim List1 As List
	Dim h() As String
	If HeadersExist Then
		Dim headers As List
		List1 = StringUtils1.LoadCSV2(Dir, Filename, ",", headers)
		Dim h(headers.Size) As String
		NumberOfColumns = headers.Size
		For i = 0 To headers.Size - 1
			h(i) = headers.Get(i)
		Next
	Else
		List1 = StringUtils1.LoadCSV(Dir, Filename, ",")
		Dim firstRow() As String
		firstRow = List1.Get(0)
		NumberOfColumns = firstRow.Length
	End If
	ResizeTable(NumberOfColumns)
	If HeadersExist Then
		SetHeaders(h)
	End If
	For i = 0 To List1.Size - 1
		Dim row() As String
		row = List1.Get(i)
		AddRow(row)
	Next
End Sub

'Saves the table to a CSV file.
'The saved CSV file will include the column header texts.
Public Sub SaveTableToCSV(Dir As String, Filename As String)
	Dim headers(NumberOfColumns) As String
	For i = 0 To headers.Length - 1
		Dim l As Label
		l = Header.GetView(i)
		headers(i) = l.Text
	Next
	StringUtils1.SaveCSV2(Dir, Filename, ",", DataList, headers)
End Sub


' TABLE SORTING


' Removes the sort indicator from the header and set SortedColumn to -1.
' This does not affect the present sort order of the table
Public Sub ClearSort
	SortIndicator.RemoveView
	SortColumn = -1
End Sub

' Get the sorttype of the specified column. False is a alphabetic, True is numeric.
' Columns are sorted alphabetically by default.
Public Sub GetColSortType(col As Int) As Boolean
	If col < NumberOfColumns Then
		Return ColumnSortAsNumber(col)
	Else
		Return False
	End If
End Sub

' Set the sorttype of the specified column. False is a alphabetic, True is numeric.
' Columns are sorted alphabetically by default.
Public Sub SetColSortType(col As Int, numeric As Boolean)
	If col < NumberOfColumns Then
		ColumnSortAsNumber(col) = numeric
	End If
End Sub

' Sort the table by column number and direction.
' The table is sorted numerically if GetColSortType(column) is True.
' If the numeric sort fails then a string sort will be performed instead.
' col is the column number starting with 0.
' ascending is the sort direction. True = ascending, False = descending.
Public Sub SortTable(col As Int, ascending As Boolean)
	ClearSelection
	If ColumnSortAsNumber(col) Then
		Dim success As Boolean = SelectionSortNum(col, ascending)
		If Not(success) Then
			SelectionSort(col, ascending)
		End If
	Else
		SelectionSort(col, ascending)
	End If
	ShowSortedHeader(col, ascending)
	Refresh
End Sub

' Returns the number of the presently sorted column or -1 if the table is not sorted
Public Sub SortedColumn As Int
	Return SortColumn
End Sub

' Returns the present sort direction of the table.
' True is ascending, False is Descending.
' This value is valid only if SortedColumn is greater than -1
Public Sub SortedDirection As Boolean
	Return SortDirection
End Sub


' PUBLIC PROPERTIES

' Gets the data in the table as a List of String arrays each of which holds the values of a row in the table.
' When getting the data it is deep copied so both the List and the arrays in the List are all unique objects.
' Sets the data in the table by copying the row data from the specified List. Each row length must match the number of columns.
' When setting the data it is deep copied so the data arrays in the Table are all unique objects.
Public Sub getData As List
	Dim rows As List
	rows.Initialize
	For Each row() As String In DataList
		Dim newrow(row.Length) As String
		For i = 0 To row.Length - 1
			newrow(i) = row(i)
		Next
		rows.Add(newrow)
	Next
	Return rows
End Sub
Public Sub setData(rows As List)
	ClearAll
	For Each row() As String In rows
		Dim newrow(row.Length) As String
		For i = 0 To row.Length - 1
			newrow(i) = row(i)
		Next			
		If row.Length = NumberOfColumns Then AddRow(newrow)	
	Next
End Sub

Public Sub getEnabled As Boolean
	Return TablePanel.Enabled
End Sub
Public Sub setEnabled(enabled As Boolean)
	TablePanel.Enabled = enabled
End Sub

' Gets or sets whether the table sorts itself when a column header is pressed
Public Sub getEnableSort As Boolean
	Return SortEnabled
End Sub
Public Sub setEnableSort(enabled As Boolean)
	SortEnabled = enabled
End Sub

Public Sub getHeight As Int
	Return TablePanel.Height
End Sub
Public Sub setHeight(height As Int)
	TablePanel.Height = height
	SV.Height = height - RowHeight
End Sub

Public Sub getLeft As Int
	Return TablePanel.Left
End Sub
Public Sub setLeft(left As Int)
	TablePanel.Left = left
End Sub

Public Sub getColumnCount As Int
	Return NumberOfColumns
End Sub

Public Sub getRowCount As Int
	Return DataList.Size
End Sub

Public Sub getTag As String
	Return TablePanel.Tag
End Sub
Public Sub setTag(tag As String)
	TablePanel.Tag = tag
End Sub

Public Sub getTop As Int
	Return TablePanel.Top
End Sub
Public Sub setTop(top As Int)
	TablePanel.Top = top
End Sub

' Get or set the width if the table.
' If width is set larger than the width of the content of the Table the rightmost column will be expanded to fit the new width.
Public Sub getWidth As Int
	Return TablePanel.Width
End Sub
Public Sub setWidth(width As Int)
	TablePanel.Width = width
	SV.Width = width
	' adjust last column size to fill view if now to small
	If SV.Panel.Width < width Then
		Dim hsize As Int = 0
		For i = 0 To ColumnWidths.Length -1
			hsize = hsize + ColumnWidths(i)
		Next
		Dim add As Int = width - hsize
		ColumnWidths(i - 1) = ColumnWidths(i - 1) + add
		SetColumnWidths(ColumnWidths)		
	End If	
End Sub

Public Sub getVisible As Boolean
	Return TablePanel.Visible
End Sub
Public Sub setVisible(visible As Boolean)
	TablePanel.Visible = visible
End Sub


' PRIVATE SUBS

' Redraw the visible part of the table
Public Sub Refresh
	SV_ScrollChanged(SV.HorizontalScrollPosition,SV.VerticalScrollPosition) ' this strange call will set min/max visible area	
	For i = minVisibleRow To maxVisibleRow ' hide all visible rows
		HideRow(i)
		ShowRow(i)
 	Next
End Sub

Private Sub SetTableDefaults
	' Set default widths to just fill the ScrollView
	Dim ColumnWidth As Int = SV.Width / NumberOfColumns
	Dim ColumnWidths(NumberOfColumns) As Int
	Dim Headers(NumberOfColumns) As String
	Dim ColumnSortAsNumber(NumberOfColumns) As Boolean
	Dim xpos As Int = 0
	For i = 0 To NumberOfColumns -1
		ColumnWidths(i) = ColumnWidth
		xpos = xpos + ColumnWidth
		Headers(i) = ""
		ColumnSortAsNumber(i) = False
	Next
	' remove any cumulative effect of rounding the column width to an Int
	If xpos < SV.Width And NumberOfColumns > 0 Then
		ColumnWidths(i-1) = ColumnWidths(i-1) + (SV.Width - xpos)
	End If
	ClearAll
	SetHeaders(Headers)
	SetColumnWidths(ColumnWidths)
	SortColumn = -1
End Sub

Private Sub innerClearAll
	Dim EvenRowDrawables(NumberOfColumns) As Object
	Dim OddRowDrawables(NumberOfColumns) As Object
	Dim SelectedRowDrawables(NumberOfColumns) As Object
	SelectedCellDrawable.Initialize(SelectedCellColor, 0)
	For i = 0 To NumberOfColumns - 1
		' we need a separate drawable for each label sa they seem to be sized to the label
		Dim EvenDrawable, OddDrawable, SelectedRowDrawable As ColorDrawable
		EvenDrawable.Initialize(EvenRowColor, 0)
		OddDrawable.Initialize(OddRowColor, 0)
		SelectedRowDrawable.Initialize(SelectedRowColor, 0)
		EvenRowDrawables(i) = EvenDrawable
		OddRowDrawables(i) = OddDrawable
		SelectedRowDrawables(i) = SelectedRowDrawable
	Next
	SelectedRow = -1
	SelectedColumn = -1
	minVisibleRow = -1
	maxVisibleRow = 0
	DataList.Initialize
	LabelsCache.Initialize
	visibleRows.Initialize
	For i = 1 To 80 'fill the cache to avoid delay on the first touch
		LabelsCache.Add(CreateNewLabels)
	Next
End Sub

Private Sub SV_ScrollChanged(PosX As Int, PosY As Int)
	Dim currentMin, currentMax As Int
	currentMin = Max(0, PosY / RowHeight - 30)
	currentMax = Min(DataList.Size - 1, (PosY + SV.Height) / RowHeight + 30)
	If minVisibleRow > -1 Then
		If minVisibleRow < currentMin Then
			'need to hide the upper rows
			For I = minVisibleRow To Min(currentMin - 1, maxVisibleRow)
				HideRow(I)
			Next
		Else If minVisibleRow > currentMin Then
			'need to show the upper rows
			For I = currentMin To Min(minVisibleRow - 1, currentMax)
				ShowRow(I)
			Next
		End If
		If maxVisibleRow > currentMax Then
			'need to hide the lower rows
			For I = maxVisibleRow To Max(currentMax + 1, minVisibleRow) Step -1
				HideRow(I)
			Next
		Else If maxVisibleRow < currentMax Then
			'need to show the lower rows
			For I = currentMax To Max(maxVisibleRow + 1, currentMin) Step -1
				ShowRow(I)
			Next
		End If
	End If
	minVisibleRow = currentMin
	maxVisibleRow = currentMax
	Header.Left = -PosX
End Sub

Private Sub ShowRow(row As Int)
	If visibleRows.ContainsKey(row) Then Return
	Dim lbls() As Label
	Dim values() As String
	lbls = GetLabels(row)
	values = DataList.Get(row)
	visibleRows.Put(row, lbls)
	Dim rowColor() As Object
	If row = SelectedRow Then
		rowColor = SelectedRowDrawables
	Else If row Mod 2 = 0 Then
		rowColor = OddRowDrawables ' the user sees column 0 as column 1 which is odd
	Else
		rowColor = EvenRowDrawables
	End If
	For i = 0 To lbls.Length - 1
		SV.Panel.AddView(lbls(i), Header.GetView(i).Left, row * RowHeight, Header.GetView(i).Width, RowHeight - 1dip)
		lbls(i).Text = values(i)
		If i = SelectedColumn And row = SelectedRow Then
			lbls(i).Background = SelectedCellDrawable
		Else
			lbls(i).Background = rowColor(i)
		End If
	Next
End Sub

Private Sub HideRow (Row As Int)
	Dim lbls() As Label
	lbls = visibleRows.Get(Row)
	If lbls = Null Then 
		Return
	End If
	For i = 0 To lbls.Length - 1	
		lbls(i).RemoveView
	Next
	visibleRows.Remove(Row)
	LabelsCache.Add(lbls)
End Sub

Private Sub GetLabels(Row As Int) As Label()
	Dim lbls() As Label
	If LabelsCache.Size > 0 Then
		lbls = LabelsCache.Get(LabelsCache.Size - 1)
		LabelsCache.RemoveAt(LabelsCache.Size - 1)
	Else
		lbls = CreateNewLabels
	End If
	For I = 0 To lbls.Length - 1
		Dim rc As RowCol
		rc = lbls(I).Tag
		rc.Row = Row
	Next
	Return lbls
End Sub

Private Sub CreateNewLabels As Label()
	Dim lbls(NumberOfColumns) As Label
	For I = 0 To NumberOfColumns - 1
		Dim rc As RowCol
		rc.Col = I
		Dim l As Label
		l.Initialize("cell")
		l.Gravity = Alignment
		l.TextSize = TextSize
		l.TextColor = CellTextColor
		l.Tag = rc
		lbls(I) = l
	Next
	Return lbls
End Sub

Private Sub Cell_Click
	Dim rc As RowCol
	Dim l As Label
	l = Sender
	rc = l.Tag
	SelectRow(rc.Col, rc.Row)
	If SubExists(Callback, Event & "_CellClick") Then
		CallSub3(Callback, Event & "_CellClick", rc.Col, rc.Row)
	End If
End Sub

Private Sub Cell_LongClick
	Dim rc As RowCol
	Dim l As Label
	l = Sender
	rc = l.Tag
	SelectRow(rc.Col, rc.Row)
	If SubExists(Callback, Event & "_CellLongClick") Then
		CallSub3(Callback, Event & "_CellLongClick", rc.Col, rc.Row)
	End If
End Sub

Private Sub Header_Click
	Dim l As Label
	Dim col As Int
	l = Sender
	col = l.Tag
	If SortEnabled Then
		If col = SortedColumn Then
			If SortedDirection Then
				SortTable(col, False)
			Else
				SortTable(col, True)
			End If
		Else
			SortTable(col, True)
		End If
	End If
	If SubExists(Callback, Event & "_HeaderClick") Then
		CallSub2(Callback, Event & "_HeaderClick", col)
	End If
End Sub

Private Sub Header_LongClick
	Dim l As Label
	Dim col As Int
	l = Sender
	col = l.Tag
	If SubExists(Callback, Event & "_HeaderLongClick") Then
		CallSub2(Callback, Event & "_HeaderLongClick", col)
	End If
End Sub

Private Sub SelectRow(Col As Int, Row As Int)
	' ShowRow uses SelectedRow to highlight it so we must change it before calling
	Dim prev As Int
	prev = SelectedRow
	SelectedRow = Row
	SelectedColumn = Col
	'remove the color of the previously selected row
	If prev > -1 Then
		If visibleRows.ContainsKey(prev) Then
			HideRow(prev)
			ShowRow(prev)
		End If
	End If
	For Col = 0 To NumberOfColumns - 1
		If visibleRows.ContainsKey(SelectedRow) Then
			HideRow(SelectedRow)
			ShowRow(SelectedRow)
		End If
	Next
End Sub


' Table Sorting Code
' http://www.basic4ppc.com/forum/basic4android-getting-started-tutorials/8548-sorting-algorithms-teaching-basic4android.html#post47730

Private Sub SelectionSort (col As Int, ascending As Boolean) As Boolean
	Dim minIndex As Int
	For i = 0 To DataList.Size- 1
		minIndex = i
		For j = i + 1 To DataList.Size - 1
			Dim values1() As String = DataList.get(j)
			Dim s1 As String = values1(col)
			Dim values2() As String = DataList.get(minIndex)
			Dim s2 As String = values2(col)
			If (ascending) Then
				If s1.CompareTo(s2) < 0 Then minIndex = j
			Else
				If s1.CompareTo(s2) > 0 Then minIndex = j
			End If
		Next
		SwapList(i, minIndex)
	Next
	Return True
End Sub

' return False if the sort fails
Private Sub SelectionSortNum (col As Int,ascending As Boolean) As Boolean
	Dim minIndex As Int
	Try
		For i = 0 To DataList.Size- 1
			minIndex = i
			For j = i + 1 To DataList.Size - 1
				Dim values1() As String = DataList.get(j)
				Dim s1 As Double = values1(col)
				Dim values2() As String = DataList.get(minIndex)
				Dim s2 As Double = values2(col)
				If (ascending) Then
					If s1 < s2 Then minIndex = j
				Else
					If s1 > s2 Then minIndex = j
				End If
			Next
			SwapList(i, minIndex)
		Next
	Catch
		Return False
	End Try
	Return True
End Sub

Public Sub SwapList(index1 As Int, index2 As Int)
	Dim temp As Object
	temp = DataList.get(index1)
	DataList.set(index1,DataList.get(index2))
	DataList.set(index2,temp)
End Sub

Private Sub ShowSortedHeader(col As Int, ascending As Boolean)
	Dim ll As Int = 40
	SortIndicator.RemoveView
	If (ascending) Then
		SortIndicator.SetBackgroundImage(LoadBitmapSample(File.DirAssets, "sort_asc.png", ll, ll))
	Else
		SortIndicator.SetBackgroundImage(LoadBitmapSample(File.DirAssets, "sort_desc.png", ll, ll))
	End If
	Dim v As View
	v = Header.GetView(col)
	Dim left As Int ' calculate left
	Dim top As Int ' calculate top
	left = v.Left + v.Width - ll
	top = v.Top + v.Height - ll
	Header.AddView(SortIndicator, left, top, ll, ll)
	SortColumn = col
	SortDirection = ascending
End Sub

