Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[object] Table


  • Please log in to reply
20 replies to this topic
Learning one
  • Members
  • 1483 posts
  • Last active: Jan 02 2016 02:30 PM
  • Joined: 04 Apr 2009
Latest work. New methods:
ToListView(), Search(), DeleteRow(), new MatchTypes in search methods (RegEx, contains, contains+, exact, exact+), LastFound property, etc. ...
.... I should start writing documentation :roll:
;===oTable testing area=================================================================
Variable =		; column names are specified as first row
(
First name%A_Tab%Last name%A_Tab%Occupation%A_Tab%Notes
Jack%A_Tab%Gates%A_Tab%Driver%A_Tab%
Mark%A_Tab%Weber%A_Tab%Student%A_Tab%His father is a driver.
Jim%A_Tab%Tucker%A_Tab%Driver%A_Tab%
Jill%A_Tab%Lochte%A_Tab%Artist%A_Tab%
Jim%A_Tab%Tucker%A_Tab%Driver%A_Tab%
Jessica%A_Tab%Hickman%A_Tab%Student%A_Tab%
Mary%A_Tab%Jones%A_Tab%Teacher%A_Tab%Her favorite song is "D r i v e r".
Lenny%A_Tab%Stark%A_Tab%Driver%A_Tab%
Jack%A_Tab%Black%A_Tab%Actor%A_Tab%His wife is artist.
Tony%A_Tab%Jackman%A_Tab%Surfer%A_Tab%
Jonny%A_Tab%Poor%A_Tab%Beggar%A_Tab%
Scott%A_Tab%Jenstan%A_Tab%Teacher%A_Tab%Lives in New York.
)

; create simple gui for testing
Gui 1: Add, ListView, x5 y5 w500 h300 vGuiFoundList, First name|Last name|Occupation|Notes
Gui 1: Show, w510 h310 hide

oTable := Table_ObjCreate(Variable)	; create table object
Variable = 	; not longer needed


;MsgBox % oTable.3.2	; get value from [3. row, 2. column]
;oTable.1.1 := "Bobby"	; set [1. row, 1. column] to "Bobby"

;MsgBox % oTable.ToString()	; convert whole table object to string
;MsgBox % oTable.3.ToString()	; convert 3. row object to string
;MsgBox % oTable.11.ToString("#")	; convert 11. row object to string but use custom delimiter
;MsgBox % oTable.MaxIndex()	; get number of rows (in future maybe: oTable.Count)


;=== Adding row ===
oTable.AddRow("Joe", "Newman", "Kiteboarder", "Freestyle & Wave", "Do not store not existing column!")	; add row


;=== Deleting row ===
;oTable.DeleteRow("Jim", "Tucker", "Driver")	; delete row (identified by its fields)
;oTable.Remove(3)	; delete row (identified by its number)


;=== Simple search ===
; Search "Last name" column for containing "man" string
;MsgBox % oTable.SearchColumn("Last name", "man").ToString() 	;  convert search results to string


;=== Complexs search ===
; step 1: search "Occupation" and "Notes" columns for containing "Driver" or "artist" strings. "|" is query delimiter.
; step 2: search that search result again: search "First name" column for containing "J" string
;oFound := oTable.SearchColumn("Occupation|Notes", "Driver|artist")		; store search results as object
;oFound2 := oFound.SearchColumn("First name", "J")		; search oFound (second search filter)

; or shorter:	oFound2 := oTable.SearchColumn("Occupation|Notes", "Driver|artist").SearchColumn("First name", "J")	; etc. --> multiple filters

;MsgBox % oFound2.ToString()		; convert search results to string 
;MsgBox % oFound2.2.ToString()	; convert 2. row from search results to string 
;MsgBox % oFound2.1.3	; get [1. row, 3. column] field from search results
;MsgBox % oFound2.MaxIndex()	; get number of found rows - from oFound2 (in future maybe: oFound2.Count)



;=== RegEx search ===	and LastFound property
; Search for all last names starting with "J" and ending with "an"
oFound3 := oTable.SearchColumn("Last name", "^J.*an$", "RegEx")	; store search results as object
MsgBox % oFound3.ToString()	; convert search results to string
MsgBox % oFound3.LastFound	; each time after calling search method, row numbers of found rows are stored in LastFound key/property - read only.



;=== To ListView demonstration ===
F1::	; shows whole oTable in ListView
LV_Delete()	; empty ListView
oTable.ToListView()
Gui 1: Show
Return


F2::	; finds all drivers and shows search results in ListView
LV_Delete()	; empty ListView
oTable.SearchColumn("Occupation", "driver").ToListView()
Gui 1: Show
Return


F3::	; Do a RegEx search and show search results in ListView
LV_Delete()	; empty ListView
oFound := oTable.SearchColumn("Last name", "^J.*an$", "RegEx") ; Search for all last names starting with "J" and ending with "an".
oFound.ToListView()
Gui 1: Show
;MsgBox, 64, Numbers of found rows in latest search, % oFound.LastFound
;MsgBox, 64, oFound to string, % oFound.ToString()
Return

F4::	; search whole oTable for containing string "new" and show search results in ListView
LV_Delete()	; empty ListView
oTable.Search("New").ToListView()
;oTable.1.ToListView()	; adds 1. row from oTable to ListView
Gui 1: Show
return








;===oTable Functions====================================================================
Table_Version() {
	return 0.09
}

Table_ObjCreate(InputVariable, ColumnsDelimiter="`t", RowsDelimiter= "`n") {	; not finished
	
	static TableBase := Table_Base("SearchColumn Search ToString ToListView NewFromScheme AddRow DeleteRow DeleteRowAll Col2Num ", "Table_mTable_")
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")
	
	oTable := Object("base", Tablebase, "ColumnsDelimiter", ColumnsDelimiter, "RowsDelimiter", RowsDelimiter)
	oColumnNames := Object()
	
	Loop, parse, InputVariable, %RowsDelimiter%
	{
		CurRow := A_LoopField
		if A_index = 1	; column names are specified as first row
		{
			Loop, parse, CurRow, %ColumnsDelimiter%
			{
				oColumnNames.Insert(A_LoopField)
				ColumnsCount++
			}
			oTable.ColumnNames := oColumnNames
			continue
		}
		RowNum := A_index-1
		%RowNum% := Object("base", RowBase, "table", oTable)
		
		StringSplit, field, CurRow, %ColumnsDelimiter%
		Loop, %ColumnsCount%
		%RowNum%.Insert(field%A_Index%)
		Loop, %ColumnsCount%
		field%A_Index% =
		
		oTable.Insert(%RowNum%)
	}
	return oTable
}

Table_Base(list, prefix) {
	base := Object()
	Loop Parse, list, %A_Space%
	base[A_LoopField] := prefix A_LoopField
	Return base
}

;====== oTable methods ======
Table_mTable_NewFromScheme(oTable) {	; creates new empty table from table object template
	oNewTable := Object("base", oTable.base, "ColumnsDelimiter", oTable.ColumnsDelimiter
		, "RowsDelimiter", oTable.RowsDelimiter, "ColumnNames", oTable.ColumnNames)
	return oNewTable
}

Table_mTable_ToString(oTable, ColumnsDelimiter="", RowsDelimiter= "") {		; converts table object to string
	if ColumnsDelimiter =
	ColumnsDelimiter := oTable.ColumnsDelimiter
	if RowsDelimiter =
	RowsDelimiter := oTable.RowsDelimiter

	For k,v in oTable
	{
		if k is not integer	;Rows are integers.
		continue
		For k2,v2 in oTable[k]
		RowString .= v2 ColumnsDelimiter
		RowString := RTrim(RowString, ColumnsDelimiter)
		TableString .= RowString RowsDelimiter
		RowString =
	}
	return RTrim(TableString, RowsDelimiter)
}

Table_mTable_ToListView(oTable) {	; adds table object to ListView

	For k,v in oTable
	{
		if k is not integer	;Rows are integers.
		continue
		For k2,v2 in oTable[k]
		{
			f%A_index% := v2
			TotalFields ++
		}	
		LV_Add("",f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30)
		Loop, %TotalFields%
		f%A_index% =
		TotalFields =
	}
}

Table_mTable_SearchColumn(oTable, ColumnsToSearch, StringsToSearch, MatchType="contains") {	; performs search through columns
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")
	
	oFound := oTable.NewFromScheme()	; create empty table from oTable template
	oFound.LastFound := ""
	ColumnsToSearch := oTable.Col2Num(ColumnsToSearch)
	
	if MatchType in contains,contains+,exact,exact+
	{
		StringReplace, StringsToSearch, StringsToSearch, `,, `,`,, all
		StringReplace, StringsToSearch, StringsToSearch, |, `,, all
	}
	
	if (MatchType = "exact+" or MatchType = "contains+")
	Table_RemoveWhitespaces(StringsToSearch)	;  space, tab,  newline and carriage return  are not relevant for match (like "\s" in RegEx)

	; About the coding style below: clumsy, longer way - but better performance for large tables - less "if evaluations" in loop.
	if MatchType = contains
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				if CurField contains %StringsToSearch%
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	else if (MatchType = "contains+")	; space, tab,  newline and carriage return  are not relevant for match
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField], Table_RemoveWhitespaces(CurField)
				if CurField contains %StringsToSearch%
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	else if MatchType = exact
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				if CurField in %StringsToSearch%
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	else if (MatchType = "exact+")	; space, tab,  newline and carriage return  are not relevant for match
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField], Table_RemoveWhitespaces(CurField)
				if CurField in %StringsToSearch%
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	else if MatchType = RegEx
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				if RegExMatch(CurField, StringsToSearch)	; StringsToSearch = NeedleRegEx
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	oFound.LastFound := LTrim(oFound.LastFound, ",")	; stores row numbers of found rows in latest search
	return oFound
}

Table_mTable_Search(oTable, StringsToSearch, MatchType="contains") {	; performs search through whole table
	; quick improvisation or good permanent solution? Looks like #2. Will see...
	For k,v in oTable.ColumnNames
	ColumnsToSearch .= v "|"
	ColumnsToSearch := RTrim(ColumnsToSearch,"|")
	return oTable.SearchColumn(ColumnsToSearch, StringsToSearch, MatchType)
}

Table_mTable_AddRow(oTable,Fields*) {	; adds new row to table
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")

	NewRowNum := oTable.MaxIndex() + 1
	%NewRowNum% := Object("base", RowBase, "table", oTable)
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		%NewRowNum%.Insert(v)
	}
	oTable.Insert(%NewRowNum%)
	return NewRowNum
}

Table_mTable_Col2Num(oTable, ColumnsToSearch) {		; converts column name(s) to column number(s)
	StringReplace, ColumnsToSearch, ColumnsToSearch, `,, `,`,, all
	StringReplace, ColumnsToSearch, ColumnsToSearch, |, `,, all
	For k,v in oTable.ColumnNames
	{
		if v in %ColumnsToSearch%
		Found .= k "|"
	}
	return RTrim(Found, "|")
}


Table_mTable_DeleteRow(oTable, Fields*) {	; deletes first matching row from table. (Identification by contents of fields)
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		RowStringToSearch .= v
	}
	For k,v in oTable
	{
		if k is not integer	;Rows are integers.
		continue
		For k2,v2 in oTable[k]
		RowString .= v2
		
		if (RowString = RowStringToSearch)
		{
			oTable.Remove(k)
			return 1
		}
		RowString =
	}
}

Table_mTable_DeleteRowAll(oTable, Fields*) {	; deletes all matching rows from table. (Identification by contents of fields)
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		RowStringToSearch .= v
	}
	For k,v in oTable
	{
		if k is not integer	;Rows are integers.
		continue
		For k2,v2 in oTable[k]
		RowString .= v2
		if (RowString = RowStringToSearch)
		{
			KeysToDelete .= k "|"
			DeletedCount++
		}
		RowString =
	}
	KeysToDelete := RTrim(KeysToDelete, "|")
	Loop, Parse, KeysToDelete, |
	oTable.Remove(A_LoopField-A_index+1)
	return DeletedCount
}



;====== oRow methods ======
Table_mRow_ToString(oRow, ColumnsDelimiter="") {	; converts row object to string
	if ColumnsDelimiter =
	ColumnsDelimiter := oRow.table.ColumnsDelimiter
	For k,v in oRow
	{
		if k is not integer	; field keys are integers.
		continue
		RowString .= v ColumnsDelimiter
	}
	return RTrim(RowString, ColumnsDelimiter)
}

Table_mRow_ToListView(oRow) {	; adds row object to ListView
	For k,v in oRow
	{
		if k is not integer	; field keys are integers.
		continue
		f%k% := v
	}
	LV_Add("",f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30)
}
	

;====== Shared, other ======
Table_RemoveWhitespaces(ByRef String) {	; removes spaces, tabs,  newlines and carriage returns from string
	StringReplace, String, String, %A_Space%,, all
	StringReplace, String, String, %A_Tab%,, all
	StringReplace, String, String, `n,, all
	StringReplace, String, String, `r,, all
}
Edit: methods Search() and SearchColumn() will be merged in one, keeping all posibilities and easy syntax

Learning one
  • Members
  • 1483 posts
  • Last active: Jan 02 2016 02:30 PM
  • Joined: 04 Apr 2009
Latest work - version 0.11.
;===oTable testing area=================================================================
Variable =		; column names are specified as first row
(
First name%A_Tab%Last name%A_Tab%Occupation%A_Tab%Notes
Jack%A_Tab%Gates%A_Tab%Driver%A_Tab%
Mark%A_Tab%Weber%A_Tab%Student%A_Tab%His father is a driver.
Jim%A_Tab%Tucker%A_Tab%Driver%A_Tab%
Jill%A_Tab%Lochte%A_Tab%Artist%A_Tab%Jack's sister.
Jessica%A_Tab%Hickman%A_Tab%Student%A_Tab%
Mary%A_Tab%Jones%A_Tab%Teacher%A_Tab%Her favorite song is "D r i v e r".
Lenny%A_Tab%Stark%A_Tab%Driver%A_Tab%
Jack%A_Tab%Black%A_Tab%Actor%A_Tab%His wife is artist.
Tony%A_Tab%Jackman%A_Tab%Surfer%A_Tab%
Jonny%A_Tab%Poor%A_Tab%Beggar%A_Tab%
Scott%A_Tab%Jenstan%A_Tab%Teacher%A_Tab%Lives in New York.
)


oTable := Table_ObjCreate(Variable)	; create table object
Variable = 	; not longer needed

; create simple gui for testing
Gui 1: Add, ListView, x5 y5 w500 h300 grid, First name|Last name|Occupation|Notes
Gui 1: Show, w510 h310 hide



;=== Field management ===
;MsgBox % oTable.3.2	; get value from [3. row, 2. column]
;oTable.1.1 := "Bobby"	; set [1. row, 1. column] to "Bobby"


;=== Converting to numbers ===
;MsgBox % oTable.Row2Num("Jonny", "Poor", "Beggar")	; get number of row whose fields are: Jonny, Poor, Beggar. (Identification by fields)
;MsgBox % oTable.Col2Num("Occupation")	; get number of "Occupation" column
;MsgBox % oTable.Col2Num("First name|Notes")	; get numbers of "First name" and "Notes" columns


;=== ToString method ===
;MsgBox % oTable.ToString()	; convert whole table object to string
;MsgBox % oTable.3.ToString()	; convert 3. row object to string
;MsgBox % oTable.11.ToString("#")	; convert 11. row object to string but use custom delimiter


;=== MaxIndex (Count) ===
;MsgBox % oTable.MaxIndex()	; get total number of rows (in future maybe: oTable.Count)
;MsgBox % oTable.ColumnNames.MaxIndex()	; get total number of columns


;=== Row management ===
oTable.AddRow("Joe", "Newman", "Kiteboarder", "Freestyle & Wave")	; add row (to the bottom)
;oTable.InsertRow(2 ,"Mike", "Insertovich", "Actor")	; inserts new row number 2.
;oTable.ModifyRow(3 ,"Sergey", "Modifysky", "Actor")	; modify row number 3.
;oTable.ModifyRow(0 ,"Chris", "Allman", "Actor")	; modify all existing rows
;oTable.DeleteRow(2)	; delete 2. row
;oTable.DeleteRow()		; delete last row
;oTable.DeleteRow(0)	; delete all rows


;=== Searching ===
;MsgBox % oTable.Search("Occupation", "Driver").ToString() 	; search Occupation column for containing string "driver"
;MsgBox % oTable.Search("Occupation|Notes", "Driver").ToString() 	; search Occupation and Notes columns for containing string "driver"
;MsgBox % oTable.Search("", "Driver", "containing+").ToString() 	; search whole table (all columns) for containing string "driver" but ignore withespaces
;MsgBox % oTable.Search("Last name", "^J.*an$", "RegEx").ToString() ; Search for all last names starting with "J" and ending with "an".
;MsgBox % oTable.Search("First name", "ny", "EndingWith").ToString() 	;  search first names ending with "ny"
;MsgBox % oTable.Search("Last name", "ja|bla", "StartingWith").ToString() 	;  search last names starting with "ja" or "bla"
;MsgBox % oTable.Search("", "Jack", "exactly").ToString() 	; search whole table (all columns) for string "Jack" (not containing, but exactly)


;=== Multiple filters search ===
; step 1: search "Occupation" and "Notes" columns for containing "Driver" or "artist" strings. "|" is query delimiter.
; step 2: search that search result again: search "First name" column for containing "J" string
;oFound := oTable.Search("Occupation|Notes", "Driver|artist")		; store search results as object
;oFound2 := oFound.Search("First name", "J")		; search oFound (second search filter)

; or shorter:	oFound2 := oTable.Search("Occupation|Notes", "Driver|artist").Search("First name", "J")	; etc. --> multiple filters

;MsgBox % oFound2.ToString()		; convert search results to string 
;MsgBox % oFound2.2.ToString()	; convert 2. row from search results to string 
;MsgBox % oFound2.1.3	; get [1. row, 3. column] field from search results
;MsgBox % oFound2.MaxIndex()	; get number of found rows - from oFound2 (in future maybe: oFound2.Count)


;=== RegEx search ===	and LastFound property
; Search for all last names starting with "J" and ending with "an"
;oFound3 := oTable.Search("Last name", "^J.*an$", "RegEx")	; store search results as object
;MsgBox % oFound3.ToString()	; convert search results to string
;MsgBox % oFound3.LastFound	; each time after calling search method, row numbers of found rows are stored in LastFound key/property - read only.


;=== ToListView method ===
F1::	; shows whole oTable in ListView
LV_Delete()	; empty ListView
oTable.ToListView()
Gui 1: Show
Return


F2::	; finds all drivers and shows search results in ListView
LV_Delete()	; empty ListView
oTable.Search("Occupation", "driver").ToListView()
Gui 1: Show
Return


F3::	; Do a RegEx search and show search results in ListView
LV_Delete()	; empty ListView
oFound := oTable.Search("Last name", "^J.*an$", "RegEx") ; Search for all last names starting with "J" and ending with "an".
oFound.ToListView()
Gui 1: Show
;MsgBox, 64, Numbers of found rows in latest search, % oFound.LastFound	; LastFound property
;MsgBox, 64, oFound to string, % oFound.ToString()
Return


F4::	; search whole table (all columns) for containing string "new" and show search results in ListView
LV_Delete()	; empty ListView
oTable.Search("","New").ToListView()	; empty 1. parameter means: search through whole table (all columns)
;oTable.1.ToListView()	; adds 1. row from oTable to ListView
Gui 1: Show
return








;===oTable Functions====================================================================
Table_Version() {
	return 0.11
}

Table_ObjCreate(InputVariable, ColumnsDelimiter="`t", RowsDelimiter= "`n") {	; not finished
	
	static TableBase := Table_Base("Search ToString ToListView NewFromScheme AddRow InsertRow ModifyRow DeleteRow Col2Num Row2Num", "Table_mTable_")
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")
	
	oTable := Object("base", Tablebase, "ColumnsDelimiter", ColumnsDelimiter, "RowsDelimiter", RowsDelimiter)
	oColumnNames := Object()
	
	Loop, parse, InputVariable, %RowsDelimiter%
	{
		CurRow := A_LoopField
		if A_index = 1	; column names are specified as first row
		{
			Loop, parse, CurRow, %ColumnsDelimiter%
			{
				oColumnNames.Insert(A_LoopField)
				ColumnsCount++
			}
			oTable.ColumnNames := oColumnNames
			continue
		}
		RowNum := A_index-1
		%RowNum% := Object("base", RowBase, "table", oTable)
		
		StringSplit, field, CurRow, %ColumnsDelimiter%
		Loop, %ColumnsCount%
		%RowNum%.Insert(field%A_Index%)
		Loop, %ColumnsCount%
		field%A_Index% =
		
		oTable.Insert(%RowNum%)
	}
	return oTable
}

Table_Base(list, prefix) {
	base := Object()
	Loop Parse, list, %A_Space%
	base[A_LoopField] := prefix A_LoopField
	Return base
}

;====== oTable methods ======
Table_mTable_AddRow(oTable,Fields*) {	; adds new row to table (to the bottom)
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")

	NewRowNum := oTable.MaxIndex() + 1
	%NewRowNum% := Object("base", RowBase, "table", oTable)
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		%NewRowNum%.Insert(v)
	}
	oTable.Insert(%NewRowNum%)
	return NewRowNum
}

Table_mTable_InsertRow(oTable, NewRowNum ,Fields*) {	; inserts new row in table
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")
	
	if NewRowNum = 0	; not allowed
	return
	
	if (NewRowNum > oTable.MaxIndex())
	NewRowNum := oTable.MaxIndex() + 1	; add as last row
	
	%NewRowNum% := Object("base", RowBase, "table", oTable)
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		%NewRowNum%.Insert(v)
	}
	oTable.Insert(NewRowNum, %NewRowNum%)
}

Table_mTable_ModifyRow(oTable, RowToModifyNumber, Fields*) {	; modifies row(s)
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")

	oModifyedRow := Object("base", RowBase, "table", oTable)
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		oModifyedRow.Insert(v)
	}
	if RowToModifyNumber = 0	; modify all existing rows
	{
		Loop, % oTable.MaxIndex()
		oTable[A_Index] := oModifyedRow
	}
	else	; modify specified row
	oTable[RowToModifyNumber] := oModifyedRow
}

Table_mTable_DeleteRow(oTable, RowToDeleteNumber="") {	; deletes row(s)
	if (RowToDeleteNumber = 0)	; delete all existing rows
	oTable.Remove(1, oTable.MaxIndex())
	Else if (RowToDeleteNumber = "")	; delete last row
	oTable.Remove()
	else	; delete specified row
	oTable.Remove(RowToDeleteNumber)
}



Table_mTable_Col2Num(oTable, ColumnsToSearch) {		; converts column name(s) to column number(s)
	StringReplace, ColumnsToSearch, ColumnsToSearch, `,, `,`,, all
	StringReplace, ColumnsToSearch, ColumnsToSearch, |, `,, all
	For k,v in oTable.ColumnNames
	{
		if v in %ColumnsToSearch%
		Found .= k "|"
	}
	return RTrim(Found, "|")
}

Table_mTable_Row2Num(oTable, Fields*) {	; converts row identified by its fields to number. First matching.
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		RowStringToSearch .= v
	}
	For k,v in oTable
	{
		if k is not integer	;Rows are integers.
		continue
		For k2,v2 in oTable[k]
		RowString .= v2
		
		if (RowString = RowStringToSearch)
		return k
		
		RowString =
	}
}

Table_mTable_NewFromScheme(oTable) {	; creates new empty table from table object template
	oNewTable := Object("base", oTable.base, "ColumnsDelimiter", oTable.ColumnsDelimiter
		, "RowsDelimiter", oTable.RowsDelimiter, "ColumnNames", oTable.ColumnNames)
	return oNewTable
}



Table_mTable_Search(oTable, ColumnsToSearch, StringsToSearch, MatchType="containing") {	; performs search through columns or whole table
	/* Parameters:
	ColumnsToSearch		"|" delimited list of columns to search. If empty (""), search through whole table (all columns.)
	StringsToSearch		"|" delimited list of strings to search except in RegEx MatchType.
	MatchType			Containing, Exactly, StartingWith, EndingWith, RegEx,
						Containing+, Exactly+, StartingWith+, EndingWith+
						"+" suffix means more permissive match type where spaces, tabs, newlines and carriage returns are not relevant for match.
	*/
		
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")
	oFound := oTable.NewFromScheme()	; create empty table from oTable template

	If (ColumnsToSearch = "")		; search through all columns - whole table.
	{
		For k,v in oTable.ColumnNames
		ColumnsToSearch .= A_Index "|"
		ColumnsToSearch := RTrim(ColumnsToSearch,"|")
	}
	else	; search through specified columns
	ColumnsToSearch := oTable.Col2Num(ColumnsToSearch)
	
	If (SubStr(MatchType,0) = "+")	; more permissive match type where spaces, tabs, newlines and carriage returns are not relevant for match.
	{
		IgnoreWhitespaces = 1
		StringTrimRight, MatchType, MatchType, 1
		Table_RemoveWhitespaces(StringsToSearch)	;  like RegExReplace(StringsToSearch, "\s")
	}
	
	if MatchType in Containing,Exactly	; prepare matchlist
	{
		StringReplace, StringsToSearch, StringsToSearch, `,, `,`,, all
		StringReplace, StringsToSearch, StringsToSearch, |, `,, all
	}
	
	if MatchType = Containing
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				if IgnoreWhitespaces
				Table_RemoveWhitespaces(CurField)
				if CurField contains %StringsToSearch%
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	else if MatchType = Exactly
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				if IgnoreWhitespaces
				Table_RemoveWhitespaces(CurField)
				if CurField in %StringsToSearch%
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	else if MatchType = StartingWith
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				if IgnoreWhitespaces
				Table_RemoveWhitespaces(CurField)
				Loop, parse, StringsToSearch, "|"
				{
					if (SubStr(CurField,1,StrLen(A_LoopField)) = A_LoopField)
					{
						%k% := Object("base", RowBase, "table", oTable)
						For	k2,v2 in oTable[k]
						%k%.Insert(v2)
						oFound.Insert(%k%)
						oFound.LastFound := oFound.LastFound "," k
					}
				}
			}
		}
	}
	else if MatchType = EndingWith
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				if IgnoreWhitespaces
				Table_RemoveWhitespaces(CurField)
				Loop, parse, StringsToSearch, "|"
				{
					if StrLen(A_LoopField) = 1
					SubCurField := SubStr(CurField,0)
					else
					SubCurField := SubStr(CurField, - (StrLen(A_LoopField) - 1))
					if (SubCurField = A_LoopField)
					{
						%k% := Object("base", RowBase, "table", oTable)
						For	k2,v2 in oTable[k]
						%k%.Insert(v2)
						oFound.Insert(%k%)
						oFound.LastFound := oFound.LastFound "," k
					}
				}
			}
		}
	}
	else if MatchType = RegEx
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				if RegExMatch(CurField, StringsToSearch)	; StringsToSearch = NeedleRegEx
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	
	oFound.LastFound := LTrim(oFound.LastFound, ",")	; stores row numbers of found rows in latest search
	return oFound
}

Table_mTable_ToString(oTable, ColumnsDelimiter="", RowsDelimiter= "") {		; converts table object to string
	if ColumnsDelimiter =
	ColumnsDelimiter := oTable.ColumnsDelimiter
	if RowsDelimiter =
	RowsDelimiter := oTable.RowsDelimiter

	For k,v in oTable
	{
		if k is not integer	;Rows are integers.
		continue
		For k2,v2 in oTable[k]
		RowString .= v2 ColumnsDelimiter
		RowString := RTrim(RowString, ColumnsDelimiter)
		TableString .= RowString RowsDelimiter
		RowString =
	}
	return RTrim(TableString, RowsDelimiter)
}

Table_mTable_ToListView(oTable) {	; puts table object to ListView

	For k,v in oTable
	{
		if k is not integer	;Rows are integers.
		continue
		For k2,v2 in oTable[k]
		{
			f%A_index% := v2
			TotalFields ++
		}	
		LV_Add("",f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30)
		Loop, %TotalFields%
		f%A_index% =
		TotalFields =
	}
}

;====== oRow methods ======
Table_mRow_ToString(oRow, ColumnsDelimiter="") {	; converts row object to string
	if ColumnsDelimiter =
	ColumnsDelimiter := oRow.table.ColumnsDelimiter
	For k,v in oRow
	{
		if k is not integer	; field keys are integers.
		continue
		RowString .= v ColumnsDelimiter
	}
	return RTrim(RowString, ColumnsDelimiter)
}

Table_mRow_ToListView(oRow) {	; puts row object to ListView
	For k,v in oRow
	{
		if k is not integer	; field keys are integers.
		continue
		f%k% := v
	}
	LV_Add("",f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30)
}
	

;====== Shared, other ======
Table_RemoveWhitespaces(ByRef String) {	; removes spaces, tabs,  newlines and carriage returns from string. Like RegExReplace(String, "\s")
	StringReplace, String, String, %A_Space%,, all
	StringReplace, String, String, %A_Tab%,, all
	StringReplace, String, String, `n,, all
	StringReplace, String, String, `r,, all
}


Learning one
  • Members
  • 1483 posts
  • Last active: Jan 02 2016 02:30 PM
  • Joined: 04 Apr 2009
Latest work - version 0.18. Although not completely finished, [object] Table is usable now.
;===oTable testing area=================================================================
Variable =		; column names are specified as first row
(
First name%A_Tab%Last name%A_Tab%Occupation%A_Tab%Notes
Jack%A_Tab%Gates%A_Tab%Driver%A_Tab%
Mark%A_Tab%Weber%A_Tab%Student%A_Tab%His father is a driver.
Jim%A_Tab%Tucker%A_Tab%Driver%A_Tab%
Jill%A_Tab%Lochte%A_Tab%Artist%A_Tab%Jack's sister.
Jessica%A_Tab%Hickman%A_Tab%Student%A_Tab%
Mary%A_Tab%Jones%A_Tab%Teacher%A_Tab%Her favorite song is "D r i v e r".
Lenny%A_Tab%Stark%A_Tab%Driver%A_Tab%
Jack%A_Tab%Black%A_Tab%Actor%A_Tab%His wife is artist.
Tony%A_Tab%Jackman%A_Tab%Surfer%A_Tab%
Jonny%A_Tab%Poor%A_Tab%Beggar%A_Tab%
Scott%A_Tab%Jenstan%A_Tab%Teacher%A_Tab%Lives in New York.
)


oTable := Table_ObjCreate(Variable)	; creates table object from variable
Variable = 	; not longer needed



; create simple gui for testing
Gui 1: Add, ListView, x5 y5 w500 h300 grid, % oTable.HeaderToString("|")	; converts table's header (first row) to string.
Gui 1: Show, w510 h310 hide




;=== Field management ===
;MsgBox % oTable.3.2	; get value from [3. row, 2. column]
;oTable.1.1 := "Bobby"	; set [1. row, 1. column] to "Bobby"


;=== Converting to numbers ===
;MsgBox % oTable.Row2Num("Jonny", "Poor", "Beggar")	; get number of row whose fields are: Jonny, Poor, Beggar. (Identification by fields)
;MsgBox % oTable.Col2Num("Occupation")	; get number of "Occupation" column
;MsgBox % oTable.Col2Num("First name|Notes")	; get numbers of "First name" and "Notes" columns


;=== ToString method ===
;MsgBox % oTable.ToString()	; convert whole table object to string
;MsgBox % oTable.3.ToString()	; convert 3. row object to string
;MsgBox % oTable.11.ToString("#")	; convert 11. row object to string but use custom delimiter
;MsgBox % oTable.HeaderToString()	; converts table's header (first row) to string


;=== MaxIndex (Count) ===
;MsgBox % oTable.MaxIndex()	; get total number of rows (in future maybe: oTable.Count)
;MsgBox % oTable.ColumnNames.MaxIndex()	; get total number of columns


;=== Row management ===
oTable.AddRow("Joe", "Newman", "Kiteboarder", "Freestyle & Wave")	; add row (to the bottom)
;oTable.InsertRow(2 ,"Mike", "Insertovich", "Actor")	; inserts new row number 2.
;oTable.ModifyRow(3 ,"Sergey", "Modifysky", "Actor")	; modify row number 3.
;oTable.ModifyRow(0 ,"Chris", "Allman", "Actor")	; modify all existing rows
;oTable.DeleteRow(2)	; delete 2. row
;oTable.DeleteRow()		; delete last row
;oTable.DeleteRow(0)	; delete all rows


;=== Searching ===
;MsgBox % oTable.Search("Occupation", "Driver").ToString() 	; search Occupation column for containing string "driver"
;MsgBox % oTable.Search("Occupation|Notes", "Driver").ToString() 	; search Occupation and Notes columns for containing string "driver"
;MsgBox % oTable.Search("", "Driver", "containing+").ToString() 	; search whole table (all columns) for containing string "driver" but ignore withespaces
;MsgBox % oTable.Search("Last name", "^J.*an$", "RegEx").ToString() ; Search for all last names starting with "J" and ending with "an".
;MsgBox % oTable.Search("First name", "ny", "EndingWith").ToString() 	;  search first names ending with "ny"
;MsgBox % oTable.Search("Last name", "ja|bla", "StartingWith").ToString() 	;  search last names starting with "ja" or "bla"
;MsgBox % oTable.Search("", "Jack", "exactly").ToString() 	; search whole table (all columns) for string "Jack" (not containing, but exactly)


;=== Multiple filters search ===
; step 1: search "Occupation" and "Notes" columns for containing "Driver" or "artist" strings. "|" is query delimiter.
; step 2: search that search result again: search "First name" column for containing "J" string
;oFound := oTable.Search("Occupation|Notes", "Driver|artist")		; store search results as object
;oFound2 := oFound.Search("First name", "J")		; search oFound (second search filter)

; or shorter:	oFound2 := oTable.Search("Occupation|Notes", "Driver|artist").Search("First name", "J")	; etc. --> multiple filters

;MsgBox % oFound2.ToString()		; convert search results to string 
;MsgBox % oFound2.2.ToString()	; convert 2. row from search results to string 
;MsgBox % oFound2.1.3	; get [1. row, 3. column] field from search results
;MsgBox % oFound2.MaxIndex()	; get number of found rows - from oFound2 (in future maybe: oFound2.Count)


;=== RegEx search ===	and LastFound property
; Search for all last names starting with "J" and ending with "an"
;oFound3 := oTable.Search("Last name", "^J.*an$", "RegEx")	; store search results as object
;MsgBox % oFound3.ToString()	; convert search results to string
;MsgBox % oFound3.LastFound	; each time after calling search method, row numbers of found rows are stored in LastFound key/property - read only.


;=== Massive StringReplace ===
;oTable.StringReplace("y", "X", ".", " :)")	; replaces "param1" with "param2", "param3" with "param4" (etc.) in all fields in table object.


;=== Interacting with files ===
;oTable := Table_ObjCreate(A_ScriptDir "\Table file.txt")	; creates table object from constructor file
;oTable.Save()	converts table object to string and saves it to its constructor file. Use only if oTable is created from file.
;oTable.SaveAs(A_ScriptDir "\Table file.txt")		; converts table object to string and saves it to specified file.
;oTable.Open()	; opens table's constructor file in Notepad if it exists
;oTable.Reload()	; reads table's constructor file again and reconstructs table object if constructor file exists

;oTable.SaveAs(A_ScriptDir "\Table file.txt","|","#")		; converts table object to string and saves it to specified file but uses custom delimiters.
; When constructing table from such file, must specify custom delimiters -->  oTable := Table_ObjCreate(A_ScriptDir "\Table file.txt","|","#")




;=== ToListView method ===
F1::	; shows whole oTable in ListView
LV_Delete()	; empty ListView
oTable.ToListView()
Gui 1: Show
Return


F2::	; finds all drivers and shows search results in ListView
LV_Delete()	; empty ListView
oTable.Search("Occupation", "driver").ToListView()
Gui 1: Show
Return


F3::	; Do a RegEx search and show search results in ListView
LV_Delete()	; empty ListView
oFound := oTable.Search("Last name", "^J.*an$", "RegEx") ; Search for all last names starting with "J" and ending with "an".
oFound.ToListView()
Gui 1: Show
;MsgBox, 64, Numbers of found rows in latest search, % oFound.LastFound	; LastFound property
;MsgBox, 64, oFound to string, % oFound.ToString()
Return


F4::	; search whole table (all columns) for containing string "new" and show search results in ListView
LV_Delete()	; empty ListView
oTable.Search("","New").ToListView()	; empty 1. parameter means: search through whole table (all columns)
;oTable.1.ToListView()	; adds 1. row from oTable to ListView
Gui 1: Show
return



/*	
;=== oTable - ListView interaction ===
d::oTable.LVDelete()	; delete selected row
a::oTable.LVAdd("Mia","Addstan","Driver")	; add new row

e::		; edit selected row
CurFields =
oCurFields := oTable.LVModify1()	; returns row's to modify fields. Must be called prior to oTable.LVModify2()
for k,v in oCurFields
CurFields .= v "|"
CurFields := RTrim(CurFields,"|")

if (CurFields = "")	; nothing selected
return

InputBox, NewRow,, Current Fields:  %CurFields%,,,,,,,,%CurFields%
if ErrorLevel
return

oNewFields := SplitToObj(NewRow)
oTable.LVModify2(oNewFields)	; modifies row in ListView and oTable
return
*/









;===oTable Functions====================================================================
Table_Version() {
	return 0.18
}

Table_ObjCreate(InputVariableOfFile, ColumnsDelimiter="`t", RowsDelimiter= "`n") {
	
	static TableBase := Table_Base("HeaderToString StringReplace Search ToString ToListView NewFromScheme AddRow InsertRow ModifyRow DeleteRow Col2Num Row2Num Open Save SaveAs Reload Row2NumL LVSelInfo LVDelete LVAdd LVModify1 LVModify2", "Table_mTable_")
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")
	
	oTable := Object("base", Tablebase, "ColumnsDelimiter", ColumnsDelimiter, "RowsDelimiter", RowsDelimiter)
	oColumnNames := Object()
	
	IfExist, %InputVariableOfFile%	; construct table from file
	{
		oFile := FileOpen(InputVariableOfFile, "r `n", "UTF-8")	; hard coded or option...?
		InputVariable := oFile.Read()
		oFile.Close()
		oTable.FilePath := InputVariableOfFile	; store constructor file's FilePath
	}
	else	; construct table from variable
	InputVariable := InputVariableOfFile
	
	Loop, parse, InputVariable, %RowsDelimiter%
	{
		CurRow := A_LoopField
		if A_index = 1	; column names are specified as first row
		{
			Loop, parse, CurRow, %ColumnsDelimiter%
			{
				oColumnNames.Insert(A_LoopField)
				ColumnsCount++
			}
			oTable.ColumnNames := oColumnNames
			continue
		}
		RowNum := A_index-1
		%RowNum% := Object("base", RowBase, "table", oTable)
		
		StringSplit, field, CurRow, %ColumnsDelimiter%
		Loop, %ColumnsCount%
		%RowNum%.Insert(field%A_Index%)
		Loop, %ColumnsCount%
		field%A_Index% =
		
		oTable.Insert(%RowNum%)
	}
	return oTable
}

Table_Base(list, prefix) {
	base := Object()
	Loop Parse, list, %A_Space%
	base[A_LoopField] := prefix A_LoopField
	Return base
}

;====== oTable methods ======
Table_mTable_StringReplace(oTable,params*) { ; replaces "param1" with "param2", "param3" with "param4" (etc.) in all fields in table object
	For k,v in oTable
	{
		if k is not integer	;Rows are integers.
		continue
		For k2,v2 in oTable[k]	; v2 = field
		{
			c := 0
			For k3,v3 in params
			{
				c++
				if c = 1
				{
					st := v3
					continue
				}
				rt := v3, c := 0
				StringReplace, v2, v2, %st%, %rt%, all
			}
			oTable[k][k2] := v2
		}
	}
}

Table_mTable_Open(oTable) {	; opens table's constructor file in Notepad if it exists
	FilePath := oTable.FilePath
	IfExist, %FilePath%
	Run, notepad "%FilePath%"
}

Table_mTable_Save(oTable) {	; converts table object to string and saves it to its constructor file. Use only if oTable is created from file.
	FilePath := oTable.FilePath		; automatically stored when creating oTable from file
	if (FilePath = "")
	return
	ColumnsDelimiter := oTable.ColumnsDelimiter, RowsDelimiter := oTable.RowsDelimiter
	oTable.SaveAs(FilePath)
	return 1
}

Table_mTable_SaveAs(oTable, FilePath, ColumnsDelimiter="", RowsDelimiter= "") {	; converts table object to string and saves it to specified file
	if ColumnsDelimiter = 
	ColumnsDelimiter := oTable.ColumnsDelimiter
	if RowsDelimiter =
	RowsDelimiter := oTable.RowsDelimiter
	
	FileContents := oTable.HeaderToString(ColumnsDelimiter) RowsDelimiter oTable.ToString(ColumnsDelimiter, RowsDelimiter)
	
	oFile := FileOpen(FilePath, "w `n", "UTF-8")	; creates a new file, overwriting any existing file.
	oFile.Write(FileContents)
	oFile.Close()
}

Table_mTable_Reload(ByRef oTable) {	; reads table's constructor file again and reconstructs table object if constructor file exists
	static TableBase := Table_Base("HeaderToString StringReplace Search ToString ToListView NewFromScheme AddRow InsertRow ModifyRow DeleteRow Col2Num Row2Num Open Save SaveAs Reload Row2NumL LVSelInfo LVDelete LVAdd LVModify1 LVModify2", "Table_mTable_")
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")
	
	FilePath := oTable.FilePath, ColumnsDelimiter := oTable.ColumnsDelimiter, RowsDelimiter := oTable.RowsDelimiter	; collect info from old table object
	IfNotExist, %FilePath%	; if constructor file doesn't exist
	return	; return and leave old table object as is
	
	oTable =	; destroy old table object
	
	; reconstruct table object
	oTable := Object("base", Tablebase, "ColumnsDelimiter", ColumnsDelimiter, "RowsDelimiter", RowsDelimiter)
	oColumnNames := Object()
	
	oTable.FilePath := FilePath
	oFile := FileOpen(FilePath, "r `n", "UTF-8")
	InputVariable := oFile.Read()
	oFile.Close()
	
	Loop, parse, InputVariable, %RowsDelimiter%
	{
		CurRow := A_LoopField
		if A_index = 1	; column names are specified as first row
		{
			Loop, parse, CurRow, %ColumnsDelimiter%
			{
				oColumnNames.Insert(A_LoopField)
				ColumnsCount++
			}
			oTable.ColumnNames := oColumnNames
			continue
		}
		RowNum := A_index-1
		%RowNum% := Object("base", RowBase, "table", oTable)
		
		StringSplit, field, CurRow, %ColumnsDelimiter%
		Loop, %ColumnsCount%
		%RowNum%.Insert(field%A_Index%)
		Loop, %ColumnsCount%
		field%A_Index% =
		
		oTable.Insert(%RowNum%)
	}
	return oTable	; not necessary as first param is ByRef, but keep it
}

Table_mTable_AddRow(oTable,Fields*) {	; adds new row to table (to the bottom)
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")

	NewRowNum := oTable.MaxIndex() + 1
	%NewRowNum% := Object("base", RowBase, "table", oTable)
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		%NewRowNum%.Insert(v)
	}
	oTable.Insert(%NewRowNum%)
	return NewRowNum
}

Table_mTable_InsertRow(oTable, NewRowNum ,Fields*) {	; inserts new row in table
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")
	
	if NewRowNum = 0	; not allowed
	return
	
	if (NewRowNum > oTable.MaxIndex())
	NewRowNum := oTable.MaxIndex() + 1	; add as last row
	
	%NewRowNum% := Object("base", RowBase, "table", oTable)
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		%NewRowNum%.Insert(v)
	}
	oTable.Insert(NewRowNum, %NewRowNum%)
}

Table_mTable_ModifyRow(oTable, RowToModifyNumber, Fields*) {	; modifies row(s)
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")

	oModifyedRow := Object("base", RowBase, "table", oTable)
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		oModifyedRow.Insert(v)
	}
	if RowToModifyNumber = 0	; modify all existing rows
	{
		Loop, % oTable.MaxIndex()
		oTable[A_Index] := oModifyedRow
	}
	else	; modify specified row
	oTable[RowToModifyNumber] := oModifyedRow
}

Table_mTable_DeleteRow(oTable, RowToDeleteNumber="") {	; deletes row(s)
	if (RowToDeleteNumber = 0)	; delete all existing rows
	{
		oTable.Remove(1, oTable.MaxIndex())
		return
	}
	Else if (RowToDeleteNumber = "")	; delete last row
	{
		oDeletedRow := Object()
		LastRN := oTable.MaxIndex()
		oDeletedRow := oTable[LastRN]
		oTable.Remove(LastRN)
		return oDeletedRow
	}
	else	; delete specified row
	{
		oDeletedRow := Object()
		oDeletedRow := oTable[RowToDeleteNumber]
		oTable.Remove(RowToDeleteNumber)
		return oDeletedRow
	}
}



Table_mTable_Col2Num(oTable, ColumnsToSearch) {		; converts column name(s) to column number(s)
	StringReplace, ColumnsToSearch, ColumnsToSearch, `,, `,`,, all
	StringReplace, ColumnsToSearch, ColumnsToSearch, |, `,, all
	For k,v in oTable.ColumnNames
	{
		if v in %ColumnsToSearch%
		Found .= k "|"
	}
	return RTrim(Found, "|")
}

Table_mTable_Row2Num(oTable, Fields*) {	; converts row identified by its fields to number. First matching.
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		RowStringToSearch .= v
	}
	For k,v in oTable
	{
		if k is not integer	;Rows are integers.
		continue
		For k2,v2 in oTable[k]
		RowString .= v2
		
		if (RowString = RowStringToSearch)
		return k
		
		RowString =
	}
}

Table_mTable_NewFromScheme(oTable) {	; creates new empty table from table object template
	oNewTable := Object("base", oTable.base, "ColumnsDelimiter", oTable.ColumnsDelimiter
		, "RowsDelimiter", oTable.RowsDelimiter, "ColumnNames", oTable.ColumnNames)
	return oNewTable
}



Table_mTable_Search(oTable, ColumnsToSearch, StringsToSearch, MatchType="containing") {	; performs search through columns or whole table
	/* Parameters:
	ColumnsToSearch		"|" delimited list of columns to search. If empty (""), search through whole table (all columns.)
	StringsToSearch		"|" delimited list of strings to search except in RegEx MatchType.
	MatchType			Containing, Exactly, StartingWith, EndingWith, RegEx,
						Containing+, Exactly+, StartingWith+, EndingWith+
						"+" suffix means more permissive match type where spaces, tabs, newlines and carriage returns are not relevant for match.
	*/
		
	static RowBase := Table_Base("ToString ToListView", "Table_mRow_")
	oFound := oTable.NewFromScheme()	; create empty table from oTable template

	If (ColumnsToSearch = "")		; search through all columns - whole table.
	{
		For k,v in oTable.ColumnNames
		ColumnsToSearch .= A_Index "|"
		ColumnsToSearch := RTrim(ColumnsToSearch,"|")
	}
	else	; search through specified columns
	ColumnsToSearch := oTable.Col2Num(ColumnsToSearch)
	
	If (SubStr(MatchType,0) = "+")	; more permissive match type where spaces, tabs, newlines and carriage returns are not relevant for match.
	Table_StringReplace(StringsToSearch, A_Space, "", A_Tab, "", "`n", "", "`r", "")	; like RegExReplace(StringsToSearch, "\s")

	
	if MatchType in Containing,Exactly,Containing+,Exactly+	; prepare matchlist
	{
		StringReplace, StringsToSearch, StringsToSearch, `,, `,`,, all
		StringReplace, StringsToSearch, StringsToSearch, |, `,, all
	}
	
	; About coding style below: longer way, but better performance due to less "if evaluations" in loop for MatchTypes not containing "+".
	if MatchType = Containing
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				if CurField contains %StringsToSearch%
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	else if MatchType = Exactly
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				if CurField in %StringsToSearch%
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	else if MatchType = StartingWith
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				Loop, parse, StringsToSearch, "|"
				{
					if (SubStr(CurField,1,StrLen(A_LoopField)) = A_LoopField)
					{
						%k% := Object("base", RowBase, "table", oTable)
						For	k2,v2 in oTable[k]
						%k%.Insert(v2)
						oFound.Insert(%k%)
						oFound.LastFound := oFound.LastFound "," k
					}
				}
			}
		}
	}
	else if MatchType = EndingWith
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				Loop, parse, StringsToSearch, "|"
				{
					if (SubStr(CurField, - (StrLen(A_LoopField) - 1)) = A_LoopField)
					{
						%k% := Object("base", RowBase, "table", oTable)
						For	k2,v2 in oTable[k]
						%k%.Insert(v2)
						oFound.Insert(%k%)
						oFound.LastFound := oFound.LastFound "," k
					}
				}
			}
		}
	}
	else if MatchType = Containing+
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				Table_StringReplace(CurField, A_Space, "", A_Tab, "", "`n", "", "`r", "")
				if CurField contains %StringsToSearch%
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	else if MatchType = Exactly+
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				Table_StringReplace(CurField, A_Space, "", A_Tab, "", "`n", "", "`r", "")
				if CurField in %StringsToSearch%
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	else if MatchType = StartingWith+
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				Table_StringReplace(CurField, A_Space, "", A_Tab, "", "`n", "", "`r", "")
				Loop, parse, StringsToSearch, "|"
				{
					if (SubStr(CurField,1,StrLen(A_LoopField)) = A_LoopField)
					{
						%k% := Object("base", RowBase, "table", oTable)
						For	k2,v2 in oTable[k]
						%k%.Insert(v2)
						oFound.Insert(%k%)
						oFound.LastFound := oFound.LastFound "," k
					}
				}
			}
		}
	}
	else if MatchType = EndingWith+
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				Table_StringReplace(CurField, A_Space, "", A_Tab, "", "`n", "", "`r", "")
				Loop, parse, StringsToSearch, "|"
				{
					if (SubStr(CurField, - (StrLen(A_LoopField) - 1)) = A_LoopField)
					{
						%k% := Object("base", RowBase, "table", oTable)
						For	k2,v2 in oTable[k]
						%k%.Insert(v2)
						oFound.Insert(%k%)
						oFound.LastFound := oFound.LastFound "," k
					}
				}
			}
		}
	}
	else if MatchType = RegEx
	{
		For k in oTable
		{
			if k is not integer	;Rows are integers.
			continue
			Loop, parse, ColumnsToSearch, |
			{
				CurField := oTable[k][A_LoopField]
				if RegExMatch(CurField, StringsToSearch)	; StringsToSearch = NeedleRegEx
				{
					%k% := Object("base", RowBase, "table", oTable)
					For	k2,v2 in oTable[k]
					%k%.Insert(v2)
					oFound.Insert(%k%)
					oFound.LastFound := oFound.LastFound "," k
				}
			}
		}
	}
	
	oFound.LastFound := LTrim(oFound.LastFound, ",")	; stores row numbers of found rows in latest search
	return oFound
}

Table_mTable_HeaderToString(oTable, ColumnsDelimiter="") {	; converts table's header (first row) to string
	if ColumnsDelimiter = 
	ColumnsDelimiter := oTable.ColumnsDelimiter
	For k,v in oTable.ColumnNames
	Header .= v ColumnsDelimiter
	return RTrim(Header, ColumnsDelimiter)
}

Table_mTable_ToString(oTable, ColumnsDelimiter="", RowsDelimiter= "") {		; converts table object to string
	if ColumnsDelimiter =
	ColumnsDelimiter := oTable.ColumnsDelimiter
	if RowsDelimiter =
	RowsDelimiter := oTable.RowsDelimiter

	For k,v in oTable
	{
		if k is not integer	;Rows are integers.
		continue
		For k2,v2 in oTable[k]
		RowString .= v2 ColumnsDelimiter
		RowString := RTrim(RowString, ColumnsDelimiter)
		TableString .= RowString RowsDelimiter
		RowString =
	}
	return RTrim(TableString, RowsDelimiter)
}

Table_mTable_ToListView(oTable) {	; puts table object to ListView

	For k,v in oTable
	{
		if k is not integer	;Rows are integers.
		continue
		For k2,v2 in oTable[k]
		{
			f%A_index% := v2
			TotalFields ++
		}	
		LV_Add("",f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30)
		Loop, %TotalFields%
		f%A_index% =
		TotalFields =
	}
}



Table_mTable_Row2NumL(oTable, RowNumToSearch, Fields*) {	; converts row identified by its fields to number but searches only through specified row numbers (limit). First matching. RowNumToSearch should be oFound.LastFound's value.
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		RowStringToSearch .= v
	}
	
	if (RowNumToSearch = "")	; search through all rows
	return oTable.Row2Num(Fields*)
	
	Loop, parse, RowNumToSearch, |	; search through specified rows
	{
		For k2,v2 in oTable[A_LoopField]
		RowString .= v2
		
		if (RowString = RowStringToSearch)
		return A_LoopField
		
		RowString =
	}
}

Table_mTable_LVSelInfo(oTable, RowNumToSearch="") {	; gets info about selected rows in ListView
  	Loop, %	LV_GetCount("Column") ; total number of columns in LV
	FromColumns .= A_Index "|"	
	FromColumns := RTrim(FromColumns,"|")
	
	; oSelected structure: o1SelRow, o2SelRow, o3SelRow, etc.
	; oSelRow structure: [1] oTableRowNum     [2] LVRowNum      [3,4,5 etc.] FieldsText
	oSelected := Object()
	Loop
    {
        RowNumber := LV_GetNext(RowNumber)
        if !RowNumber
        break
		
		oSelRow := A_Index
		%oSelRow% := Object()	; oSelRow
		%oSelRow%.Insert("")	; [1] oTable RowNum - dummy
		%oSelRow%.Insert(RowNumber)	; [2] LV owNum 
		
		oFields := Object()
		Loop, parse, FromColumns, | 
		{
			LV_GetText(FieldText, RowNumber, A_LoopField)
			oFields.Insert(FieldText)
			%oSelRow%.Insert(FieldText)
		}
		%oSelRow%[1] := oTable.Row2NumL(RowNumToSearch, oFields*)	 ; [1] oTable RowNum - real
		oSelected.Insert(%oSelRow%), oFields := ""
	}
    return oSelected
}

Table_mTable_LVDelete(oTable, RowNumToSearch="") {	; deletes selected row from oTable and ListView. Deletes just 1. selected row for now.
	oSelected := oTable.LVSelInfo(RowNumToSearch)
	For k,v in oSelected	; oSelected structure: o1SelRow, o2SelRow, o3SelRow, etc.
	{
		; structure: [1] oTableRowNum     [2] LVRowNum      [3,4,5 etc.] FieldsText
		for k2,v2 in oSelected[k]
		{
			if A_index = 1		; [1] oTableRowNum
			oTable.DeleteRow(v2)
			else if A_index = 2	; [2] LVRowNum
			LV_Delete(v2)
			else
			break	; [3,4,5 etc.] FieldsText - not relevant
		}
		return  oSelected.1		; allow deleting just 1. selected row for now. Ignore other. Return info about deleted row.
	}
}

Table_mTable_LVAdd(oTable, Fields*) {	; adds new row to oTable and ListView
	; add to oTable
	oTable.AddRow(Fields*)	
	
	; add to ListView
	TotalColumns := oTable.ColumnNames.MaxIndex()
	For k,v in Fields
	{
		if (A_index > TotalColumns)
		break
		f%A_index% := v
	}
	LV_Add("",f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30)
}

Table_mTable_LVModify1(oTable, RowNumToSearch="") {	; Returns row's to modify fields. Must be called prior to oTable.LVModify2()
	; Relevant for machine: stores oTableRowNum and LVRowNum in oTable.LVModifyRowNums
	; for faster performance when displaying search results in LV, specify oFound.LastFound as RowNumToSearch
	
	oSelected := oTable.LVSelInfo(RowNumToSearch)
	oFields := Object(), oRowNums := Object()

For k,v in oSelected.1	; first selected row info
	{
		if A_index = 1		; [1] oTableRowNum
		oRowNums.Insert(v)
		else if A_index = 2	; [2] LVRowNum
		oRowNums.Insert(v)
		else	; [3,4,5 etc.] FieldsText
		oFields.Insert(v)	
	}
	oTable.LVModifyRowNums := oRowNums	; info used by oTable.LVModify2
	return oFields	; return row's to modify fields
}	

Table_mTable_LVModify2(oTable, oNewFields) {	; modifies row in ListView and oTable
	TableRowNum := oTable.LVModifyRowNums.1	; [1] oTableRowNum
	LVRowNum := oTable.LVModifyRowNums.2	; [2] LVRowNum

	oTable.ModifyRow(TableRowNum, oNewFields*)
	For k,v in oNewFields
	f%k% := v
	LV_Modify(LVRowNum,"",f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30)
}

;====== oRow methods ======
Table_mRow_ToString(oRow, ColumnsDelimiter="") {	; converts row object to string
	if ColumnsDelimiter =
	ColumnsDelimiter := oRow.table.ColumnsDelimiter
	For k,v in oRow
	{
		if k is not integer	; field keys are integers.
		continue
		RowString .= v ColumnsDelimiter
	}
	return RTrim(RowString, ColumnsDelimiter)
}

Table_mRow_ToListView(oRow) {	; puts row object to ListView
	For k,v in oRow
	{
		if k is not integer	; field keys are integers.
		continue
		f%k% := v
	}
	LV_Add("",f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30)
}
	

;====== Shared, other ======
Table_StringReplace(ByRef String, params*) {	; replaces "param1" with "param2", "param3" with "param4" (etc.) in string
	For k,v in params
	{
		c++
		if c = 1
		{
			st := v
			continue
		}
		rt := v, c := 0
		StringReplace, String, String, %st%, %rt%, all
	}
}

SplitToObj(String,Delimiter="|") {
	obj := Object()
	Loop, parse, String, %Delimiter%
	obj.Insert(A_LoopField)
	return	obj
}





;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ExitApp
Pause::
Suspend
Pause,,1
return

Escape::
Suspend
ExitApp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
History:
- version 0.18 - release date 09.01.2011.
- version 0.15 - release date 29.12.2010.
- version 0.14 - release date 28.12.2010.
- version 0.13 - release date 28.12.2010.


hoppfrosch
  • Members
  • 399 posts
  • Last active: Feb 26 2016 05:31 AM
  • Joined: 25 Jan 2006
As I needed a simple table class as well, I took the latest version 0.18 from here and overhauled the existing sources to be more conform with AHK_L class syntax. I implemented all functionality available in the original source.

The current class is tested with AHK_L 1.1.5.1 on Win7 x64

Here are the sources for my version 0.1.0.0:

/*!
	Library: cTable version 0.0.1
		Simplified handling of tables
		
		Based on work by Learning one (see: http://www.autohotkey.com/forum/viewtopic.php?t=65995)
        
	Author: Hoppfrosch
*/

/*!
   Class: cTable
      Simplified handling of tables
*/
class cTable {
   static _version := "0.1.0.0"
   static ColumnNames := Object()
   static FilePath := ""
   static ColumnsDelimiter := "`t"
   static RowsDelimiter := "`n"
   static _debug := 0

   
   __New(InputVariableOrFile, ColumnsDelimiter="`t", RowsDelimiter= "`n", _debug=0) {
      this.__debug(_debug)
      
      this.ColumnsDelimiter := ColumnsDelimiter
      this.RowsDelimiter := RowsDelimiter
      
      if % (A_AhkVersion < "1.1.05.00" && A_AhkVersion >= "2.0") {
         MsgBox % "This class is only tested with AHK_L later than 1.1.05.00 (and before 2.0)`nAborting..."
         return 
      }
         
      IfExist, %InputVariableOrFile%   ; construct table from file
      {
         oFile := FileOpen(InputVariableOrFile, "r `n", "UTF-8")   ; hard coded or option...?
         InputVariable := oFile.Read()
         oFile.Close()
         this.FilePath := InputVariableOrFile   ; store constructor file's FilePath
      }
      else   ; construct table from variable
         InputVariable := InputVariableOrFile

      oColumnNames := Object()

      Loop, parse, InputVariable, %RowsDelimiter%
      {
         CurRow := A_LoopField
         if A_index = 1   ; column names are specified as first row
         {
            Loop, parse, CurRow, %ColumnsDelimiter%
            {
               oColumnNames.Insert(A_LoopField)
               ColumnsCount++
            }
            this.ColumnNames := oColumnNames
            continue
         }
         RowNum := A_index-1
         %RowNum% :=  new cTableRow(CurRow, ColumnsCount, ColumnsDelimiter)
      
         this.Insert(%RowNum%)
      }
      
      return this
   }
   
   AddRow(Fields*) {   ; adds new row to table (to the bottom)
      
      NewRowNum := this.MaxIndex() + 1
      TotalColumns := this.ColumnNames.MaxIndex()
      %NewRowNum% := new cTableRow("",0,ColumnsDelimiter)
      For k,v in Fields
      {
         if (A_index > TotalColumns)
            break
         %NewRowNum%.Insert(v)
      }
      this.Insert(%NewRowNum%)
      
      
      return NewRowNum
   }
   
   Col2Num(ColumnsToSearch) {      ; converts column name(s) to column number(s)
      
      
      StringReplace, ColumnsToSearch, ColumnsToSearch, `,, `,`,, all
      StringReplace, ColumnsToSearch, ColumnsToSearch, |, `,, all
      For k,v in this.ColumnNames
      {
         if v in %ColumnsToSearch%
            Found .= k "|"
      }
      RetVal := RTrim(Found, "|")
      
      
      return RetVal
   }
   
   DeleteRow(RowToDeleteNumber="") {   ; deletes row(s)
      
      if (RowToDeleteNumber = 0)   ; delete all existing rows
      {
         this.Remove(1, this.MaxIndex())
         return
      }
      Else if (RowToDeleteNumber = "")   ; delete last row
      {
         oDeletedRow := Object()
         LastRN := this.MaxIndex()
         oDeletedRow := this[LastRN]
         this.Remove(LastRN)
         return oDeletedRow
      }
      else   ; delete specified row
      {
         oDeletedRow := Object()
         oDeletedRow := this[RowToDeleteNumber]
         this.Remove(RowToDeleteNumber)
         return oDeletedRow
      }
   }
   
   HeaderToString(ColumnsDelimiter="") {   ; converts table's header (first row) to string
      if ColumnsDelimiter =
         ColumnsDelimiter := this.ColumnsDelimiter
      For k,v in this.ColumnNames
         Header .= v ColumnsDelimiter
      RetVal := RTrim(Header, ColumnsDelimiter)
      return RetVal
   }
   
   InsertRow(NewRowNum ,Fields*) {   ; inserts new row in table
      if NewRowNum = 0   ; not allowed
         return
   
      if (NewRowNum > this.MaxIndex())
         NewRowNum := this.MaxIndex() + 1   ; add as last row
   
      %NewRowNum% := new cTableRow("",0,ColumnsDelimiter)
      TotalColumns := this.ColumnNames.MaxIndex()
      For k,v in Fields
      {
         if (A_index > TotalColumns)
            break
         %NewRowNum%.Insert(v)
      }
      this.Insert(NewRowNum, %NewRowNum%)
   }
   
   LVAdd(Fields*) {   ; adds new row to oTable and ListView
      ; add to oTable
      this.AddRow(Fields*)   
   
      ; add to ListView
      TotalColumns := this.ColumnNames.MaxIndex()
      For k,v in Fields
      {
         if (A_index > TotalColumns)
         break
         f%A_index% := v
      }
      LV_Add("",f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30)
   }
   
   LVDelete(RowNumToSearch="") {   ; deletes selected row from oTable and ListView. Deletes just 1. selected row for now.
      oSelected := this.LVSelInfo(RowNumToSearch)
      For k,v in oSelected   ; oSelected structure: o1SelRow, o2SelRow, o3SelRow, etc.
      {
         ; structure: [1] oTableRowNum     [2] LVRowNum      [3,4,5 etc.] FieldsText
         for k2,v2 in oSelected[k]
         {
            if A_index = 1      ; [1] oTableRowNum
               this.DeleteRow(v2)
            else if A_index = 2   ; [2] LVRowNum
               LV_Delete(v2)
            else
               break   ; [3,4,5 etc.] FieldsText - not relevant
         }
         return  oSelected.1      ; allow deleting just 1. selected row for now. Ignore other. Return info about deleted row.
      }
   }
   
   LVModify1(RowNumToSearch="") {   ; Returns row's to modify fields. Must be called prior to oTable.LVModify2()
      ; Relevant for machine: stores oTableRowNum and LVRowNum in oTable.LVModifyRowNums
      ; for faster performance when displaying search results in LV, specify oFound.LastFound as RowNumToSearch
      oSelected := this.LVSelInfo(RowNumToSearch)
      oFields := Object()
      oRowNums := Object()

      For k,v in oSelected.1   ; first selected row info
      {
         if A_index = 1      ; [1] oTableRowNum
            oRowNums.Insert(v)
         else if A_index = 2   ; [2] LVRowNum
            oRowNums.Insert(v)
         else   ; [3,4,5 etc.] FieldsText
            oFields.Insert(v)   
      }
      this.LVModifyRowNums := oRowNums   ; info used by oTable.LVModify2
      return oFields   ; return row's to modify fields
   } 
   
   LVModify2(oNewFields) {   ; modifies row in ListView and oTable
    
      TableRowNum := this.LVModifyRowNums.1   ; [1] oTableRowNum
      LVRowNum := this.LVModifyRowNums.2   ; [2] LVRowNum

      this.ModifyRow(TableRowNum, oNewFields*)
      For k,v in oNewFields
         f%k% := v
      LV_Modify(LVRowNum,"",f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30)
   }

   LVSelInfo(RowNumToSearch="") {   ; gets info about selected rows in ListView
      Loop, %   LV_GetCount("Column") ; total number of columns in LV
         FromColumns .= A_Index "|"   
      FromColumns := RTrim(FromColumns,"|")
   
      ; oSelected structure: o1SelRow, o2SelRow, o3SelRow, etc.
      ; oSelRow structure: [1] oTableRowNum     [2] LVRowNum      [3,4,5 etc.] FieldsText
      oSelected := Object()
      Loop
      {
         RowNumber := LV_GetNext(RowNumber)
         if !RowNumber
            break
      
         oSelRow := A_Index
         %oSelRow% := Object()   ; oSelRow
         %oSelRow%.Insert("")   ; [1] oTable RowNum - dummy
         %oSelRow%.Insert(RowNumber)   ; [2] LV owNum
      
         oFields := Object()
         Loop, parse, FromColumns, |
         {
            LV_GetText(FieldText, RowNumber, A_LoopField)
            oFields.Insert(FieldText)
            %oSelRow%.Insert(FieldText)
         }
         %oSelRow%[1] := this.Row2NumL(RowNumToSearch, oFields*)    ; [1] oTable RowNum - real
         oSelected.Insert(%oSelRow%), oFields := ""
      }
      return oSelected
   }

   ModifyRow(RowToModifyNumber, Fields*) {   ; modifies row(s)
   
      oModifyedRow := new cTableRow("",0,ColumnsDelimiter)
      TotalColumns := this.ColumnNames.MaxIndex()
      For k,v in Fields
      {
         if (A_index > TotalColumns)
            break
         oModifyedRow.Insert(v)
      }
      if RowToModifyNumber = 0   ; modify all existing rows
      {
         Loop, % this.MaxIndex()
            this[A_Index] := oModifyedRow
      }
      else   ; modify specified row
         this[RowToModifyNumber] := oModifyedRow
   }
 
   NewFromScheme() {   ; creates new empty table from table object template
      oNewTable := new cTable("", this.ColumnsDelimiter, this.RowsDelimiter)
      oNewTable.ColumnNames := this.ColumnNames
      return oNewTable
   }
   
   Open() {   ; opens table's constructor file in Notepad if it exists
      FilePath := this.FilePath
      IfExist, %FilePath%
         Run, notepad "%FilePath%"
   }
   
   Reload() {   ; reads table's constructor file again and reconstructs table object if constructor file exists   
      FilePath := this.FilePath
      ColumnsDelimiter := this.ColumnsDelimiter, 
      RowsDelimiter := this.RowsDelimiter
      IfNotExist, %FilePath%   ; if constructor file doesn't exist
         return   ; return and leave old table object as is
   
      this := new cTable(FilePath, ColumnsDelimiter,RowsDelimiter) 
   
      return this   ; not necessary as first param is ByRef, but keep it
   }
   
   Row2Num(Fields*) {   ; converts row identified by its fields to number. First matching.
      TotalColumns := this.ColumnNames.MaxIndex()
      For k,v in Fields
      {
         if (A_index > TotalColumns)
            break
         RowStringToSearch .= v
      }
      For k,v in this
      {
         if k is not integer   ;Rows are integers.
            continue
         For k2,v2 in this[k]
            RowString .= v2
      
         if (RowString = RowStringToSearch) {
            return k
         }
      
         RowString =
      }
   }
   
   Row2NumL(RowNumToSearch, Fields*) {   ; converts row identified by its fields to number but searches only through specified row numbers (limit). First matching. RowNumToSearch should be oFound.LastFound's value.
      TotalColumns := this.ColumnNames.MaxIndex()
      For k,v in Fields
      {
         if (A_index > TotalColumns)
            break
         RowStringToSearch .= v
      }
   
      if (RowNumToSearch = "")   ; search through all rows
         return this.Row2Num(Fields*)
   
      Loop, parse, RowNumToSearch, |   ; search through specified rows
      {
         For k2,v2 in this[A_LoopField]
            RowString .= v2
      
         if (RowString = RowStringToSearch)
            return A_LoopField
      
         RowString =
      }
   }
   
   Save() {   ; converts table object to string and saves it to its constructor file. Use only if oTable is created from file.
      if (this.FilePath = "")
         return
      this.SaveAs(this.FilePath)
      return 1
   }

   SaveAs(FilePath, ColumnsDelimiter="`t", RowsDelimiter="`n") {   ; converts table object to string and saves it to specified file
      if ColumnsDelimiter =
         ColumnsDelimiter := this.ColumnsDelimiter
      if RowsDelimiter =
         RowsDelimiter := this.RowsDelimiter
      this.FilePath := FilePath
   
      FileContents := this.HeaderToString(ColumnsDelimiter) RowsDelimiter this.ToString(ColumnsDelimiter, RowsDelimiter)
   
      oFile := FileOpen(FilePath, "w `n", "UTF-8")   ; creates a new file, overwriting any existing file.
      oFile.Write(FileContents)
      oFile.Close()
   }
   
   Search(ColumnsToSearch, StringsToSearch, MatchType="containing") {   ; performs search through columns or whole table
      /* Parameters:
      ColumnsToSearch      "|" delimited list of columns to search. If empty (""), search through whole table (all columns.)
      StringsToSearch      "|" delimited list of strings to search except in RegEx MatchType.
      MatchType            Containing, Exactly, StartingWith, EndingWith, RegEx,  Containing+, Exactly+, StartingWith+, EndingWith+
                           "+" suffix means more permissive match type where spaces, tabs, newlines and carriage returns are not relevant for match.
      */
      
      oFound := this.NewFromScheme()   ; create empty table from this template

      If (ColumnsToSearch = "")      ; search through all columns - whole table.
      {
         For k,v in this.ColumnNames
            ColumnsToSearch .= A_Index "|"
         ColumnsToSearch := RTrim(ColumnsToSearch,"|")
      }
      else   ; search through specified columns
         ColumnsToSearch := this.Col2Num(ColumnsToSearch)
      
      If (SubStr(MatchType,0) = "+")   ; more permissive match type where spaces, tabs, newlines and carriage returns are not relevant for match.
         _cTable_StringReplace(StringsToSearch, A_Space, "", A_Tab, "", "`n", "", "`r", "")   ; like RegExReplace(StringsToSearch, "\s")

      
      if MatchType in Containing,Exactly,Containing+,Exactly+   ; prepare matchlist
      {
         StringReplace, StringsToSearch, StringsToSearch, `,, `,`,, all
         StringReplace, StringsToSearch, StringsToSearch, |, `,, all
      }
      
      ; About coding style below: longer way, but better performance due to less "if evaluations" in loop for MatchTypes not containing "+".
      if MatchType = Containing
      {
         For k in this
         {
            if k is not integer   ;Rows are integers.
               continue
            Loop, parse, ColumnsToSearch, |
            {
               CurField := this[k][A_LoopField]
               if CurField contains %StringsToSearch%
               {
                  oFound.Insert(this[k])
                  oFound.LastFound := oFound.LastFound "," k
               }
            }
         }
      }
      else if MatchType = Exactly
      {
         For k in this
         {
            if k is not integer   ;Rows are integers.
               continue
            Loop, parse, ColumnsToSearch, |
            {
               CurField := this[k][A_LoopField]
               if CurField in %StringsToSearch%
               {
                  oFound.Insert(this[k])
                  oFound.LastFound := oFound.LastFound "," k
               }
            }
         }
      }
      else if MatchType = StartingWith
      {
         For k in this
         {
            if k is not integer   ;Rows are integers.
               continue
            Loop, parse, ColumnsToSearch, |
            {
               CurField := this[k][A_LoopField]
               Loop, parse, StringsToSearch, "|"
               {
                  if (SubStr(CurField,1,StrLen(A_LoopField)) = A_LoopField)
                  {
                     oFound.Insert(this[k])
                     oFound.LastFound := oFound.LastFound "," k
                  }
               }
            }
         }
      }
      else if MatchType = EndingWith
      {
         For k in this
         {
            if k is not integer   ;Rows are integers.
               continue
            Loop, parse, ColumnsToSearch, |
            {
               CurField := this[k][A_LoopField]
               Loop, parse, StringsToSearch, "|"
               {
                  if (SubStr(CurField, - (StrLen(A_LoopField) - 1)) = A_LoopField)
                  {
                     oFound.Insert(this[k])
                     oFound.LastFound := oFound.LastFound "," k
                  }
               }
            }
         }
      }
      else if MatchType = Containing+
      {
         For k in this
         {
            if k is not integer   ;Rows are integers.
               continue
            Loop, parse, ColumnsToSearch, |
            {
               CurField := this[k][A_LoopField]
               _cTable_StringReplace(CurField, A_Space, "", A_Tab, "", "`n", "", "`r", "")
               if CurField contains %StringsToSearch%
               {
                  oFound.Insert(this[k])
                  oFound.LastFound := oFound.LastFound "," k
               }
            }
         }
      }
      else if MatchType = Exactly+
      {
         For k in this
         {
            if k is not integer   ;Rows are integers.
               continue
            Loop, parse, ColumnsToSearch, |
            {
               CurField := this[k][A_LoopField]
               _cTable_StringReplace(CurField, A_Space, "", A_Tab, "", "`n", "", "`r", "")
               if CurField in %StringsToSearch%
               {
                  oFound.Insert(this[k])
                  oFound.LastFound := oFound.LastFound "," k
               }
            }
         }
      }
      else if MatchType = StartingWith+
      {
         For k in this
         {
            if k is not integer   ;Rows are integers.
               continue
            Loop, parse, ColumnsToSearch, |
            {
               CurField := this[k][A_LoopField]
               _cTable_StringReplace(CurField, A_Space, "", A_Tab, "", "`n", "", "`r", "")
               Loop, parse, StringsToSearch, "|"
               {
                  if (SubStr(CurField,1,StrLen(A_LoopField)) = A_LoopField)
                  {
                     oFound.Insert(this[k])
                     oFound.LastFound := oFound.LastFound "," k
                  }
               }
            }
         }
      }
      else if MatchType = EndingWith+
      {
         For k in this
         {
            if k is not integer   ;Rows are integers.
               continue
            Loop, parse, ColumnsToSearch, |
            {
               CurField := this[k][A_LoopField]
               _cTable_StringReplace(CurField, A_Space, "", A_Tab, "", "`n", "", "`r", "")
               Loop, parse, StringsToSearch, "|"
               {
                  if (SubStr(CurField, - (StrLen(A_LoopField) - 1)) = A_LoopField)
                  {
                     oFound.Insert(this[k])
                     oFound.LastFound := oFound.LastFound "," k
                  }
               }
            }
         }
      }
      else if MatchType = RegEx
      {
         For k in this
         {
            if k is not integer   ;Rows are integers.
               continue
            Loop, parse, ColumnsToSearch, |
            {
               CurField := this[k][A_LoopField]
               if RegExMatch(CurField, StringsToSearch)   ; StringsToSearch = NeedleRegEx
               {
                  oFound.Insert(this[k])
                  oFound.LastFound := oFound.LastFound "," k
               }
            }
         }
      }
      
      oFound.LastFound := LTrim(oFound.LastFound, ",")   ; stores row numbers of found rows in latest search
      return oFound
   }

   StringReplace(params*) { ; replaces "param1" with "param2", "param3" with "param4" (etc.) in all fields in table object
      For k,v in this
      {
         if k is not integer   ;Rows are integers.
            continue
         this[k] =  this[k].StringReplace(params) 
      }
   }

   ToString(ColumnsDelimiter="", RowsDelimiter= "") {      ; converts table object to string
      if ColumnsDelimiter =
         ColumnsDelimiter := this.ColumnsDelimiter
      if RowsDelimiter =
         RowsDelimiter := this.RowsDelimiter

      MyCount := this.MaxIndex()
      Loop, %MyCount%
      {
         Row := this[A_Index]
         RowString .= Row.ToString()
         if (A_Index < this.MaxIndex() )
            RowString .= RowsDelimiter
      }
      RetVal := RowString
      return RetVal
   }
   
   ToListView() {   ; puts table object to ListView
      For k,v in this
      {
         if k is not integer   ;Rows are integers.
            continue
         For k2,v2 in this[k]
         {
            f%A_index% := v2
            TotalFields ++
         }   
         LV_Add("",f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30)
         Loop, %TotalFields%
         f%A_index% =
         TotalFields =
      }
   }

   Version() {
      return this._version
   }
   
}


/*!
   Class: cTableRow
      Base class for handling table rows
*/
class cTableRow {
      /**************************************************************************************************************
   Variable: version
   Version of class implementation
   ***************************************************************************************************************	
   */
   static _version := "0.1.0"
   static ColumnsDelimiter := "`t"
   static _debug := 0

   Version() {
      return this._version
   }
   
   
   __New(CurRow="", ColumnsCount=0, ColumnsDelimiter="`t", _debug=0) {
      this.__debug(_debug)
      
      StringSplit, field, CurRow, %ColumnsDelimiter%
      Loop, %ColumnsCount%
         this.Insert(field%A_Index%)
      Loop, %ColumnsCount%
         field%A_Index% =
      
      
      return this
   }
   
   ToString(ColumnsDelimiter="") {   ; converts row object to string
      
      if ColumnsDelimiter = 
         ColumnsDelimiter := this.ColumnsDelimiter
      Cols := this.MaxIndex()
      Loop, %Cols%
      {
         RowString .= this[A_Index]
         if (A_Index < this.MaxIndex() )
            RowString .= ColumnsDelimiter
      }
      RetVal:= RowString
      return RetVal
   }
   
   ToListView() {   ; puts row object to ListView
      For k,v in this
      {
         if k is not integer   ; field keys are integers.
            continue
         f%k% := v
      }
      LV_Add("",f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15,f16,f17,f18,f19,f20,f21,f22,f23,f24,f25,f26,f27,f28,f29,f30)
   }
      
   StringReplace(params*) { ; replaces "param1" with "param2", "param3" with "param4" (etc.) in all fields in table object
      For k,v in this   ; v = field
      {
         c := 0
         For k2,v2 in params[1]
         {
            c++
            if c = 1
            {
               st := v2
               continue
            }
            rt := v2, c := 0
            StringReplace, v, v, %st%, %rt%, all
         }
         this[k] := v
      }
      return this 
   }
}


;====== Shared, other ======
_cTable_SplitToObj(String,Delimiter="|") {
   obj := Object()
   Loop, parse, String, %Delimiter%
      obj.Insert(A_LoopField)
   return   obj
}

_cTable_StringReplace(ByRef String, params*) {   ; replaces "param1" with "param2", "param3" with "param4" (etc.) in string
   For k,v in params
   {
      c++
      if c = 1
      {
         st := v
         continue
      }
      rt := v, c := 0
      StringReplace, String, String, %st%, %rt%, all
   }
}

/*
Author: IsNull
http://www.autohotkey.com/forum/topic59244.html
license: not specified 
default license for forum where initially posted:GPL v2
*/
_cTable_ToString(this){
   if(!IsObject(this))
      return, this
   str := ""
   fields := this._NewEnum()
   while fields[k, v]
   {
      if(!IsObject(v)){
         str .= !IsFunc(v) ? "[" k "] := "  v "`n" : k "() calls "  v "`n"
      }else{
         subobje := _cTable_ToString(v)
         str .= "[" k "]<ob>`n" _cTable_multab(subobje)
      }
   }
   return, str
}

_cTable_multab(str){
   Loop, parse, str, `n, `r
      newstr .= A_Tab A_LoopField "`n"
   return, newstr
}

And here is a example file (pretty similar to original):

#include cTable.ahk

;===oTable testing area=================================================================
Variable =      ; column names are specified as first row
(
First name%A_Tab%Last name%A_Tab%Occupation%A_Tab%Notes
Jack%A_Tab%Gates%A_Tab%Driver%A_Tab%
Mark%A_Tab%Weber%A_Tab%Student%A_Tab%His father is a driver.
Jim%A_Tab%Tucker%A_Tab%Driver%A_Tab%
Jill%A_Tab%Lochte%A_Tab%Artist%A_Tab%Jack's sister.
Jessica%A_Tab%Hickman%A_Tab%Student%A_Tab%
Mary%A_Tab%Jones%A_Tab%Teacher%A_Tab%Her favorite song is "D r i v e r".
Lenny%A_Tab%Stark%A_Tab%Driver%A_Tab%
Jack%A_Tab%Black%A_Tab%Actor%A_Tab%His wife is artist.
Tony%A_Tab%Jackman%A_Tab%Surfer%A_Tab%
Jonny%A_Tab%Poor%A_Tab%Beggar%A_Tab%
Scott%A_Tab%Jenstan%A_Tab%Teacher%A_Tab%Lives in New York.
)


oTable := new cTable(Variable)   ; creates table object from variable
Variable =    ; not longer needed



; create simple gui for testing
Gui 1: Add, ListView, x5 y5 w500 h300 grid, % oTable.HeaderToString("|")   ; converts table's header (first row) to string.
Gui 1: Show, w510 h310 hide


;=== Field management ===
;MsgBox % oTable.3.2   ; get value from [3. row, 2. column]
;oTable.1.1 := "Bobby"   ; set [1. row, 1. column] to "Bobby"


;=== Converting to numbers ===
;MsgBox % oTable.Row2Num("Jonny", "Poor", "Beggar")   ; get number of row whose fields are: Jonny, Poor, Beggar. (Identification by fields)
;MsgBox % oTable.Col2Num("Occupation")   ; get number of "Occupation" column
;MsgBox % oTable.Col2Num("First name|Notes")   ; get numbers of "First name" and "Notes" columns


;=== ToString method ===
;MsgBox % oTable.ToString()   ; convert whole table object to string
;MsgBox % oTable.3.ToString()   ; convert 3. row object to string
;MsgBox % oTable.11.ToString("#")   ; convert 11. row object to string but use custom delimiter
;MsgBox % oTable.HeaderToString()   ; converts table's header (first row) to string


;=== MaxIndex (Count) ===
;MsgBox % oTable.MaxIndex()   ; get total number of rows (in future maybe: oTable.Count)
;MsgBox % oTable.ColumnNames.MaxIndex()   ; get total number of columns


;=== Row management ===
;oTable.AddRow("Joe", "Newman", "Kiteboarder", "Freestyle & Wave")   ; add row (to the bottom)
;oTable.InsertRow(2 ,"Mike", "Insertovich", "Actor")   ; inserts new row number 2.
;oTable.ModifyRow(3 ,"Sergey", "Modifysky", "Actor")   ; modify row number 3.
;oTable.ModifyRow(0 ,"Chris", "Allman", "Actor")   ; modify all existing rows
;oTable.DeleteRow(2)   ; delete 2. row
;oTable.DeleteRow()      ; delete last row
;oTable.DeleteRow(0)   ; delete all rows


;=== Searching ===
;MsgBox % oTable.Search("Occupation", "Driver").ToString()    ; search Occupation column for containing string "driver"
;MsgBox % oTable.Search("Occupation|Notes", "Driver").ToString()    ; search Occupation and Notes columns for containing string "driver"
;MsgBox % oTable.Search("", "Driver", "containing+").ToString()    ; search whole table (all columns) for containing string "driver" but ignore withespaces
;MsgBox % oTable.Search("Last name", "^J.*an$", "RegEx").ToString() ; Search for all last names starting with "J" and ending with "an".
;MsgBox % oTable.Search("First name", "ny", "EndingWith").ToString()    ;  search first names ending with "ny"
;MsgBox % oTable.Search("Last name", "ja|bla", "StartingWith").ToString()    ;  search last names starting with "ja" or "bla"
;MsgBox % oTable.Search("", "Jack", "exactly").ToString()    ; search whole table (all columns) for string "Jack" (not containing, but exactly)


;=== Multiple filters search ===
; step 1: search "Occupation" and "Notes" columns for containing "Driver" or "artist" strings. "|" is query delimiter.
; step 2: search that search result again: search "First name" column for containing "J" string
;oFound := oTable.Search("Occupation|Notes", "Driver|artist")      ; store search results as object
;oFound2 := oFound.Search("First name", "J")      ; search oFound (second search filter)

; or shorter:   oFound2 := oTable.Search("Occupation|Notes", "Driver|artist").Search("First name", "J")   ; etc. --> multiple filters

;MsgBox % oFound2.ToString()      ; convert search results to string
;MsgBox % oFound2.2.ToString()   ; convert 2. row from search results to string
;MsgBox % oFound2.1.3   ; get [1. row, 3. column] field from search results
;MsgBox % oFound2.MaxIndex()   ; get number of found rows - from oFound2 (in future maybe: oFound2.Count)


;=== RegEx search ===   and LastFound property
; Search for all last names starting with "J" and ending with "an"
;oFound3 := oTable.Search("Last name", "^J.*an$", "RegEx")   ; store search results as object
;MsgBox % oFound3.ToString()   ; convert search results to string
;MsgBox % oFound3.LastFound   ; each time after calling search method, row numbers of found rows are stored in LastFound key/property - read only.


;=== Massive StringReplace ===
;oTable.StringReplace("y", "X", ".", " :)")   ; replaces "param1" with "param2", "param3" with "param4" (etc.) in all fields in table object.


;=== Interacting with files ===
;oTable.SaveAs(A_ScriptDir "\Table file.txt","|","#")      ; converts table object to string and saves it to specified file but uses custom delimiters.
; When constructing table from such file, must specify custom delimiters -->  oTable := Table_ObjCreate(A_ScriptDir "\Table file.txt","|","#")

;oTable1 := new cTable(A_ScriptDir "\Table file.txt")   ; creates table object from constructor file
;oTable1.Save()   ; converts table object to string and saves it to its constructor file. Use only if oTable is created from file.
;oTable.SaveAs(A_ScriptDir "\Table file.txt")      ; converts table object to string and saves it to specified file.
;oTable.Open()   ; opens table's constructor file in Notepad if it exists
;oTable.Reload()   ; reads table's constructor file again and reconstructs table object if constructor file exists



;=== ToListView method ===
F1::   ; shows whole oTable in ListView
LV_Delete()   ; empty ListView
oTable.ToListView()
Gui 1: Show
Return


F2::   ; finds all drivers and shows search results in ListView
LV_Delete()   ; empty ListView
oTable.Search("Occupation", "driver").ToListView()
Gui 1: Show
Return


F3::   ; Do a RegEx search and show search results in ListView
LV_Delete()   ; empty ListView
oFound := oTable.Search("Last name", "^J.*an$", "RegEx") ; Search for all last names starting with "J" and ending with "an".
oFound.ToListView()
Gui 1: Show
;MsgBox, 64, Numbers of found rows in latest search, % oFound.LastFound   ; LastFound property
;MsgBox, 64, oFound to string, % oFound.ToString()
Return


F4::   ; search whole table (all columns) for containing string "new" and show search results in ListView
LV_Delete()   ; empty ListView
oTable.Search("","New").ToListView()   ; empty 1. parameter means: search through whole table (all columns)
;oTable.1.ToListView()   ; adds 1. row from oTable to ListView
Gui 1: Show
return



 
;=== oTable - ListView interaction ===
d::oTable.LVDelete()   ; delete selected row
a::oTable.LVAdd("Mia","Addstan","Driver")   ; add new row

e::      ; edit selected row
CurFields =
oCurFields := oTable.LVModify1()   ; returns row's to modify fields. Must be called prior to oTable.LVModify2()
for k,v in oCurFields
CurFields .= v "|"
CurFields := RTrim(CurFields,"|")

if (CurFields = "")   ; nothing selected
return

InputBox, NewRow,, Current Fields:  %CurFields%,,,,,,,,%CurFields%
if ErrorLevel
return

oNewFields := _cTable_SplitToObj(NewRow)
oTable.LVModify2(oNewFields)   ; modifies row in ListView and oTable
return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ExitApp
Pause::
Suspend
Pause,,1
return

GuiClose:
Escape::
Suspend
ExitApp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I published my work - containing a test suite for the class as well - on Github

Have fun ...



_________________________

;     (.)~(.)   
;    (-------)                                    
;---ooO-----Ooo---------------------------------------------------
;    Hoppfrosch  - AHK 1.1.05.01 Unicode 32bit on Win7 Ultimate
;-----------------------------------------------------------------                        
;    ( )   ( )                            
;    /|\   /|\


Learning one
  • Members
  • 1483 posts
  • Last active: Jan 02 2016 02:30 PM
  • Joined: 04 Apr 2009
That was my first custom object :mrgreen: , while class syntax wasn't available yet. I'm glad you fond time to rewrite it in class syntax style. :wink: If you have time and interest, I suggest you to implement InvertTable method. This [VxE]'s function, which is made for string based table (delimiters: row `n, column `t), not 2D array like cTable, shows what I mean;
; ======Example======
Table=
(
A`tB
1`t4
2`t5
3`t6
)

F1::	; press F1 to invert and re-invert table
Table := InvertTable(Table)
MsgBox, %Table%
return

; ======Function======
InvertTable( Table ) { ; by [VxE]	http://www.autohotkey.com/forum/topic66290.html
   Loop, Parse, Table, `n, `r
      If ( 1 == B_Index := A_Index )
         Loop, Parse, A_LoopField, % Table := "`t"
            cell_%B_Index%_%A_Index% := A_LoopField, C_Index := A_Index
      Else Loop, Parse, A_LoopField, % "`t"
            cell_%B_Index%_%A_Index% := A_LoopField

   Loop, % C_Index
      Loop % B_Index + 0 * ( C_Index := A_Index )
         Table .= Chr( 9 + ( A_Index = 1 ) ) . cell_%A_Index%_%C_Index%

   Return SubStr( Table, 3 )
}


hoppfrosch
  • Members
  • 399 posts
  • Last active: Feb 26 2016 05:31 AM
  • Joined: 25 Jan 2006

If you have time and interest, I suggest you to implement InvertTable method.


Mathematically spoken this is "transpose a matrix". "Invert" seems to me to be an incorrect term in this context, since matrix inversion has an exact mathematical meaning ...

I'm not quite sure how to handle transposing correctly in my context: as in original data the first row has some special handling/meaning, as it holds the column descriptions ... Should I transpose the whole matrix (including special handled first row) - or should I omitt the first row? After transposing: Should the new first new row be handled as special "title row" - or should it be shifted to second row to be a simple data row? As I don't have an idea now, what is "the correct way" (if there is any at all ...), I hestiate to implement it ...