Dynamic variadic CSV data array to ListView

Helpful script writing tricks and HowTo's
DrReflex

Dynamic variadic CSV data array to ListView

15 Feb 2017, 02:58

I have had several projects that required passing data from an array to a function as a variable length parameter list.

My best example is: The need to dynamically populate a ListView with elements from a 2 dimensional comma separated variable (CSV) file.

Looking at the AHK ListView documentation this does not appear to be possible. My meager attempts to program around or through this apparent limitation failed. The solution was in a post in the Archived Forums (https://autohotkey.com/board/topic/9730 ... omsearch=1). There are several other posts that might get you to the solution.

It turns out that LV_Add() is a "variadic" function. Although LV_Add() is documented as LV_Add([Options, Field1, Field2, ...]) the correct documentation is LV_Add([Options, Field*]) where the asterisk after Field denotes Field to be a variadic parameter and LV_Add() to be a variadic function. In this case Field becomes a variable array of sequential ListView row entries (e.g Field1, Field2, ...). It appears that LV_Insert and LV_Modify are also variadic functions.

In the above post, Jackie Sztuk-Blackholyman provided a simple script to show how to use a variadic parameter to add rows of elements to a ListView. (Thanks Jackie.) I have slightly modified that script to make the code sequence easier to read while adding an extra row and an extra column.

Code: Select all

Gui,Add,ListView,,ColTitle1|ColTitle2|ColTitle3|ColTitle4

args := {}
;BUILD AND ADD DATA ROW 1
args[1] := "A"
args[2] := "B"
args[3] := "C"
args[4] := "D"
LV_Add("",args*)

args := {}
;BUILD AND ADD DATA ROW 2
args[1] := "E"
args[2] := "F"
args[3] := "G"
args[4] := "H"
LV_Add("",args*)
args=

Gui,Show
Return

Esc::
GuiEscape:
GuiClose:
ExitApp
In the following code:
1. I pull the elements into an Array[row,col]=element.
2. I use 1 Loop to build the Column/TitleString (CTString) from the first row of elements. CTString is used by GUI,Add,Listview,,CTString to build ListView.
3. I use 2 nested Loops to read elements by row and column from the Array[row,col]=element into the variadic parameter args* and then use LV_Add("",args*) to add the list of elements one line at a time into ListView.

Code: Select all

Array := {}
Array[1,1] := "ColTitle1"
Array[1,2] := "ColTitle2"
Array[1,3] := "ColTitle3"
Array[1,4] := "ColTitle4"
Array[2,1] := "A"
Array[2,2] := "B"
Array[2,3] := "C"
Array[2,4] := "D"
Array[3,1] := "E"
Array[3,2] := "F"
Array[3,3] := "G"
Array[3,4] := "H"

MaxRow := 3
MaxCol  := 4

;BUILD CTSring (COLUMN TITLE STRING) FROM Array[1,..]
CTString := ""
Loop, %MaxCol%
{
	ColIndex := A_Index
	CTString .= Array[1,ColIndex] . "|"
}
CTString := SubStr(CTString, 1, (StrLen(CTString)-1))   		; REMOVE LAST "|"
;USE CTSString TO BUILD ListView
Gui,Add,ListView,,%CTString%

;BUILD args[ColIndex]  (i.e. args*) FROM Array[Row2..,Col1..]
MaxRow := MaxRow -1  								;REDUCE MaxRow BY 1 TO ACCOUNT FOR COLTITLE ROW
Loop, %MaxRow% 
{
	args := {}
	RowIndex := A_Index + 1  							;INCREASE RowIndex BY 1 TO ACCOUNT FOR COLTITLE ROW
	Loop, %MaxCol%
	{
		ColIndex := A_Index
		args[ColIndex] := Array[RowIndex,ColIndex]
	}
	LV_Add("",args*)
	args = 
}
Gui,Show
Return

Esc::
GuiEscape:
GuiClose:
ExitApp
In the last interation, I use a CSV DataString rather than a CSV datafile so the code is self contained to build the Array[row,col] = element. A the same time I gather the RowMax and ColMax values that will be used to populate the ListView.

Code: Select all

DataString = 
(
ColTitle1, ColTitle2, ColTitle3, ColTitle4
A,B,C,D
E,F,G,H
)

;BUILD ARRAY[Row,Column] FROM DataString
Array     := {}
MaxRow := 0
MaxCol  := 0
Loop, parse, DataString, `n  					;PARSE DataString AT NEW LINES TO GET RowString
{
	RowIndex := A_Index
	RowString := A_LoopField
	Loop, parse, RowString, CSV 				;PARSE RowString AT COMMAS TO GET Elements
	{
		ColIndex := A_Index
		Element  := A_LoopField
		Array[RowIndex,ColIndex] := Element 	;BUILD Array[row,col] FROM Elements
	}
	If (ColIndex > MaxCol)					;IF ColIndex > MaxCol THEN UPDATE MaxCol
		MaxCol := ColIndex
}
MaxRow := RowIndex

;BUILD CTSring (COLUMN TITLE STRING) FROM Array[1,..]
CTString := ""
Loop, %MaxCol%
{
	ColIndex := A_Index
	CTString .= Array[1,ColIndex] . "|"
}
CTString := SubStr(CTString, 1, (StrLen(CTString)-1))	;REMOVE LAST "|"

;USE CTSString TO BUILD ListView
Gui,Add,ListView,,%CTString%
CTString =

;BUILD args[ColIndex] FROM Array[Row2..,Col1..]
MaxRow := MaxRow -1  						;REDUCE MaxRow BY 1 TO ACCOUNT FOR COLTITLE ROW
Loop, %MaxRow% 
{
	args := {}
	RowIndex := A_Index + 1  					; INCREASE RowIndex BY 1 TO ACCOUNT FOR COLTITLE ROW
	Loop, %MaxCol%
	{
		ColIndex := A_Index
		args[ColIndex] := Array[RowIndex,ColIndex]
	}
	;ADD ROW TO ListView USING args*
	LV_Add("",args*)
}
args =
Array =
Gui,Show
Return

Esc::
GuiEscape:
GuiClose:
ExitApp
The code above can easily be changed to input the elements from a CSV file using the "Loop, Read, File" function.

The documentation regarding variadic functions is remarkably sparse given the power of the feature. What documentation there is can be found under Variadic Functions and Variadic Function Calls (https://autohotkey.com/docs/Functions.htm#Variadic). It consists of 2 paragraphs, 10 Notes, and 1 crude example.
If the last parameter in a function definition is followed by an asterisk (*) the function is considered to be variadic and may receive a variable number of parameters.

I hope you find these examples helpful.
User avatar
tdalon
Posts: 44
Joined: 21 Apr 2017, 07:19
Location: Germany
Contact:

Re: Dynamic variadic CSV data array to ListView

18 Jul 2023, 08:00

Many thanks for sharing this gem! Much appreciated :bravo:
User avatar
andymbody
Posts: 1034
Joined: 02 Jul 2017, 23:47

Re: Dynamic variadic CSV data array to ListView

02 Sep 2023, 10:15

Thanks!

But... not sure why the row data was copied to a new array before adding to LV?

Slightly more condensed version that utilizes row data found in original array. Can be condensed further, but left in some vars and comments for clarity:

Code: Select all

#SingleInstance, force
SetBatchLines, -1

; create test data
dataStr := 
(
"ColTitle1,ColTitle2,ColTitle3,ColTitle4
A,B,C,D
E,F,G,H
I,J,K,L
M,N,O,P"
)
dataArray := twoDimArrayFromStr(dataStr, ",")

; add column header to LV
colHeaderStr := buildColumnStr(dataArray)
Gui, Add, ListView,, % colHeaderStr

; add row data to LV
Loop % dataArray.MaxIndex()				; row count
	if (A_Index > 1)					; skip col header row
		LV_Add(, dataArray[A_Index]*)	; add row data	

; show
Gui,Show

return
Esc::
GuiEscape:
GuiClose:
ExitApp

;################################################################################
									   twoDimArrayFromStr(srcStr, cDelim := "`t")
{
; Two dimensional array from string
; designed for array[row,col] -> col will be determined by first row in srcstr

	if (srcStr=="")
		return ""
	
	retArr := []
	Loop, parse, srcStr, `n, `r
	{
		idxRow := A_Index, rowStr := A_LoopField
		Loop, parse, rowStr, % cDelim
		{
			idxCol := A_Index, cellStr := A_LoopField
			retArr[idxRow, idxCol] := cellStr
		}
	}
	return retArr
}
;################################################################################
														 buildColumnStr(srcArray)
{
	retStr := ""
	Loop % srcArray[1].MaxIndex()				; column count
		retStr .= srcArray[1, A_Index] . "|"
	return trim(retStr, "|")					; trim final separator
}

User avatar
flyingDman
Posts: 3012
Joined: 29 Sep 2013, 19:01

Re: Dynamic variadic CSV data array to ListView

02 Sep 2023, 15:44

Or:

Code: Select all

dataStr =
(
ColTitle1,ColTitle2,ColTitle3,ColTitle4
A,B,"C,CC",D
E,F,G,H
I,J,K,L
M,N,O,P
)

gui, add, listview										  ; no columns defined yet
for x,y in strsplit(dataStr,"`n","`r")
	{
	for a,b in c := CSVprs(y)
		(x = 1) && LV_InsertCol(200, ,b)           ; 200 is max # of columns in lv
	(x > 1) && LV_add("",c*)
	}
lv_modifyCol(1,"AutoHdr")
gui, show
return

CSVprs(str)		   				; creates an array of the elements of a CSV string
	{
	arr := []
	loop Parse, str, CSV
		arr.Push(A_LoopField)
	return arr
	}
The CSVprs function also makes sure that a comma inside a "cell" - i.e. "C,CC" - is treated properly
14.3 & 1.3.7
User avatar
andymbody
Posts: 1034
Joined: 02 Jul 2017, 23:47

Re: Dynamic variadic CSV data array to ListView

02 Sep 2023, 17:18

flyingDman wrote:
02 Sep 2023, 15:44
The CSVprs function also makes sure that a comma inside a "cell" - i.e. "C,CC" - is treated properly
Cool... that could be helpful... Thanks for the tip!

:?: Question: What is the advantage of using these types of conditional statements? Are they just used to condense lines of code (rather than each using a 2-line IF instead?)

Code: Select all

; this x looks like an assignment, but is actually (x==1), correct? It's confusing to me
(x = 1) && LV_InsertCol(200, ,b)
(x > 1) && LV_add("",c*)
User avatar
flyingDman
Posts: 3012
Joined: 29 Sep 2013, 19:01

Re: Dynamic variadic CSV data array to ListView

02 Sep 2023, 17:53

That notation might confuse some but I not only am I accustomed to it now, but believe it is sometimes easier to read than the typical "If statements". Yes, and it also condenses the code.
14.3 & 1.3.7
User avatar
andymbody
Posts: 1034
Joined: 02 Jul 2017, 23:47

Re: Dynamic variadic CSV data array to ListView

02 Sep 2023, 18:10

flyingDman wrote:
02 Sep 2023, 17:53
That notation might confuse some but I not only am I accustomed to it now, but believe it is sometimes easier to read than the typical "If statements". Yes, and it also condenses the code.
Ok... thanks for the clarification. I can see the advantage to condensing the code. My simple mind is easily confused by something different. I also see a hint of obfuscation (although not intended). Thanks for providing a different perspective!

Andy

Return to “Tutorials (v1)”

Who is online

Users browsing this forum: No registered users and 41 guests