wxListCtrl and ClientData

BlitzMax Modules Forums/Brucey's Modules/wxListCtrl and ClientData

Here is a stripped down version of a program i am working on.
There is a GUI file and a Main file.

The main file creates a sqllite database and adds some tables and data.

The first table (MainTask) is read by TMainTask and a list is returned to the frame to be populated in the topmost ListCtrl (and stored as ClientData). I have a listbox example that works fine but the difference is the first item in the listbox is the primary key i need for the next lookup.

I am displaying the data in a different order to what is in the table. The table has a Primary Key of MainTaskID but this is not in the first column. This might be my problem and if so i guess i can use it in column 1 but give it a zero width.

Once i click one of these items in the ListCtrl it is supposed to pass the correct MainTaskID to the TSubTask.GetListForTypeId function so the relevant Sub Tasks can be returned and displayed in the second ListCtrl.

Unfortunately only the MainTaskID = 1 SubTask is returned and displayed.

I haven't got the hang of this client data for the listctrl yet.

The code looks quite messy but this is because i normally have 3 sets of files: GUI, DataBase Types and the Main File.
That is why i am passing values around so much.

Any help appreciated.

Cheers
Glenn

ListCtrlClientDataGUI.bmx
'
' BlitzMax code generated with wxCodeGen v1.02 : 10 Mar 2008 22:34:08
' 
' 
' PLEASE DO "NOT" EDIT THIS FILE!
' 
SuperStrict

Import wx.wxFrame
Import wx.wxListCtrl
Import wx.wxPanel
Import wx.wxWindow


Type MyFrame2Base Extends wxFrame

	Field m_panel5:wxPanel
	Field m_listCtrl1:wxListCtrl
	Field m_listCtrl2:wxListCtrl


	Method Create:MyFrame2Base(parent:wxWindow = Null,id:Int = wxID_ANY, title:String = "", x:Int = -1, y:Int = -1, w:Int = 787, h:Int = 328, style:Int = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL)
		Return MyFrame2Base(Super.Create(parent, id, title, x, y, w, h, style))
	End Method

	Method OnInit()

		Local bSizer11:wxBoxSizer
		bSizer11 = New wxBoxSizer.Create(wxVERTICAL)
		m_panel5 = New wxPanel.Create(Self, wxID_ANY,,,,, wxTAB_TRAVERSAL)

		Local bSizer12:wxBoxSizer
		bSizer12 = New wxBoxSizer.Create(wxVERTICAL)
		m_listCtrl1 = New wxListCtrl.Create(m_panel5, wxID_ANY,,,,, wxLC_REPORT)
		m_listCtrl1.SetMinSize(700,-1)
		bSizer12.Add(m_listCtrl1, 1, wxALL, 5)

		m_listCtrl2 = New wxListCtrl.Create(m_panel5, wxID_ANY,,,,, wxLC_REPORT)
		m_listCtrl2.SetMinSize(700,-1)
		bSizer12.Add(m_listCtrl2, 1, wxALL, 5)

		m_panel5.SetSizer(bSizer12)
		m_panel5.Layout()
		bSizer11.Add(m_panel5, 1, wxEXPAND | wxALL, 5)

		SetSizer(bSizer11)
		Layout()

		m_listCtrl1.ConnectAny(wxEVT_COMMAND_LIST_ITEM_SELECTED, _ListCtrl1Clicked, Null, Self)
	End Method

	Function _ListCtrl1Clicked(event:wxEvent)
		MyFrame2Base(event.sink).ListCtrl1Clicked(wxListEvent(event))
	End Function

	Method ListCtrl1Clicked(event:wxListEvent)
		DebugLog "Please override MyFrame2.ListCtrl1Clicked()"
		event.Skip()
	End Method

End Type


MainFile
'
' Example application stub generated by wxCodeGen v1.02 : 10 Mar 2008 21:41:30
'
SuperStrict

Framework wx.wxApp

Import "ListCtrlClientDataGUI.bmx"
Import BaH.DBSQLite
Import brl.filesystem
Import brl.system

Const cListCtrlDateWidth:Int = 80

New MyApp.run()

Type MyApp Extends wxApp

	Field frame:MyFrame2
	
	Method OnInit:Int()
?macos
		' make the default Mac font for controls not so big
		wxSystemOptions.SetOption(wxWINDOW_DEFAULT_VARIANT, wxWINDOW_VARIANT_SMALL)
?
		' needed so my graphics show up
		wxInitAllImageHandlers()
		' for Mac/Win32 (it is default on Linux)
		wxImage.AddHandler( New wxXPMHandler )
		' create the frame
		Local frame:MyFrame2 = MyFrame2(New MyFrame2.Create(, , "ListCtrl Client Data Test"))
		' frames are created hidden. You need to manually show it.
		frame.show()
		' make sure it is brought to the front
		SetTopWindow(frame)

		Return True
	End Method
	
End Type



Type MyFrame2 Extends MyFrame2Base

	Method OnInit()
		Super.OnInit()
		' There are 2 list controls. Create the Column Headers here
		CreateListCtrlColumnHeaders()
		' populate the Due Date ListCtrl
		PopulateListCtrl(m_listCtrl1)		
		
	End Method

	Method CreateListCtrlColumnHeaders()
		' do the First List Control
		SetColumnHeadings(m_listCtrl1)
		' do the Second List Control
		SetColumnHeadings(m_listCtrl2)
	End Method

	Method SetColumnHeadings(ListControl:wxListCtrl)
		Select ListControl
			Case m_listCtrl1
				ListControl.InsertColumn(0, ("TM"), wxLIST_FORMAT_LEFT, 30)	' Team Member Initials
				ListControl.InsertColumn(1, ("Created"), wxLIST_FORMAT_LEFT, cListCtrlDateWidth)
				ListControl.InsertColumn(2, ("Due Date"), wxLIST_FORMAT_LEFT, cListCtrlDateWidth)
				ListControl.InsertColumn(3, ("Completed"), wxLIST_FORMAT_LEFT, cListCtrlDateWidth)
				ListControl.InsertColumn(4, ("Cancelled"), wxLIST_FORMAT_CENTRE, cListCtrlDateWidth)
				ListControl.InsertColumn(5, ("Main Task Name"), wxLIST_FORMAT_LEFT,200)
			Case m_listCtrl2
				ListControl.InsertColumn(0, ("TM"), wxLIST_FORMAT_LEFT, 30)	' Team Member Initials
				ListControl.InsertColumn(1, ("Created"), wxLIST_FORMAT_LEFT, cListCtrlDateWidth)
				ListControl.InsertColumn(2, ("Due Date"), wxLIST_FORMAT_LEFT, cListCtrlDateWidth)
				ListControl.InsertColumn(3, ("Completed"), wxLIST_FORMAT_LEFT, cListCtrlDateWidth)
				ListControl.InsertColumn(4, ("Cancelled"), wxLIST_FORMAT_CENTRE, cListCtrlDateWidth)
				ListControl.InsertColumn(5, ("Sub Task Name"), wxLIST_FORMAT_LEFT,200)
			Default
				DebugLog "You have passed a ListCtrl that doesn't exist"
		End Select
	End Method

	Method PopulateListCtrl(ListControl:wxListCtrl)
		' delete the Items, so the column headers don't need to be recreated
		ListControl.DeleteAllItems()
		' check the ListCtrl exists
		If ListControl Then
			' Get the data for the MainTask ListCtrl (using the id) from TSelector
			Local list:TList = TMainTask.GetListForTypeId()
			' i is the row number in the ListCtrl
			Local i:Int = 0	
			For Local dataset:TMainTask = EachIn list
				' InsertIntoList(Row Number, the data for this element of the list, the name of the ListCtrl)
				MTInsertIntoList(i, dataset, ListControl)
				' Increment i so you are always adding to the end of the ListCtrl
				' comment this line out if you want your listctrl populated in reverse, ie i = 0
				i:+ 1
			Next
		End If
	End Method

	Method MTInsertIntoList(index:Int, dataSet:TMainTask, list:wxListCtrl)
		' wxListItem - This type stores information about a wxListCtrl item or column.
		' Some comments taken from <a href="http://www.gibbongames.com/content/view/52/50/" target="_blank">http://www.gibbongames.com/content/view/52/50/</a>
		' wxListItem encapsulates the contents of a given listbox cell.
		' So each row/column combination has its own wxListItem structure.
		Local item:wxListItem = New wxListItem.Create()
		' Define all the parameters for the column, then use list.SetItem(item) to bind it
		' Column 0
		item.SetImage(-1)
		item.SetText(dataSet.TMID)
		item.SetId(index)
'******************* set the Client Data???? so it can be used in "Method MainTaskSelected(event:wxListEvent)"
		item.SetData(dataSet)
		' Apparently Column 0 requires InsertItem, but Column n > 0 requires SetItem (assuming that Column 0 has been previously inserted).
		list.InsertItem(item)
		' Column 1
		item.SetText(dataSet.MTCreateDate)
		item.SetColumn(1)
		list.SetItem(item)
		' Column 2
		item.SetText(dataSet.MTDueDate)
		item.SetColumn(2)
		list.SetItem(item)
		' Column 3
		item.SetText(dataSet.MTCompletedDate)
		item.SetColumn(3)
		list.SetItem(item)
		' Column 4
		If dataSet.MTCancelled = 1 Then
			item.SetText("Yes")
		Else
			item.SetText("No")
		End If
		item.SetColumn(4)
		list.SetItem(item)
		' Column 5
		item.SetText(dataSet.MTName)
		item.SetColumn(5)
		list.SetItem(item)
	End Method

	Method ListCtrl1Clicked(event:wxListEvent)
		DebugLog "ListCtrl1Clicked"
		' once the MainTask ListCtrl is clicked we need to populate the SubTask ListCtrl
		' Find the index of the item clicked
		Local index:Int = wxCommandEvent(event).GetInt()
		' Get the value of the item clicked
		' Note: This is the TMainTask object we stored earlier.
		' It gives us access to the Id, for example, which gives us a quicker
		' lookup in the SQL. (ie. no table joins, since the Id is a foreign key!)
' ***************************** GetItemData(index) looks correct but i keep getting a 1 sent to the PopulateSubTask as the id
		Local sMainTask:TMainTask = TMainTask(m_listCtrl1.GetItemData(index))
		' now fill in the SubTask ListCtrl
		Populate_m_listCtrl2(sMainTask.MainTaskID, m_listCtrl2)
	End Method

	Method Populate_m_listCtrl2(id:Int, ListControl:wxListCtrl)
		' delete the Items, so the column headers don't need to be recreated
		ListControl.DeleteAllItems()
		' check the ListCtrl exists
		If ListControl Then
			' Get the data for the MainTask ListCtrl (using the id) from TSelector
			Local list:TList = TSubTask.GetListForTypeId(id)
			' i is the row number in the ListCtrl
			Local i:Int = 0	
			For Local dataset:TSubTask = EachIn list
				' InsertIntoList(Row Number, the data for this element of the list, the name of the ListCtrl)
				STInsertIntoList(i, dataset, ListControl)
				' Increment i so you are always adding to the end of the ListCtrl
				' comment this line out if you want your listctrl populated in reverse, ie i = 0
				i:+ 1
			Next
		End If
	End Method

	Method STInsertIntoList(index:Int, dataSet:TSubTask, list:wxListCtrl)
		' wxListItem - This type stores information about a wxListCtrl item or column.
		' Some comments taken from <a href="http://www.gibbongames.com/content/view/52/50/" target="_blank">http://www.gibbongames.com/content/view/52/50/</a>
		' wxListItem encapsulates the contents of a given listbox cell.
		' So each row/column combination has its own wxListItem structure.
		Local item:wxListItem = New wxListItem.Create()
		' Define all the parameters for the column, then use list.SetItem(item) to bind it
		' Column 0
		item.SetImage(-1)
		item.SetText(dataSet.TMID)
		item.SetId(index)
'******************* set the Client Data???? so it can be used in "Method MainTaskSelected(event:wxListEvent)"
		item.SetData(dataSet)
		' Apparently Column 0 requires InsertItem, but Column n > 0 requires SetItem (assuming that Column 0 has been previously inserted).
		list.InsertItem(item)
		' Column 1
		item.SetText(dataSet.STCreateDate)
		item.SetColumn(1)
		list.SetItem(item)
		' Column 2
		item.SetText(dataSet.STDueDate)
		item.SetColumn(2)
		list.SetItem(item)
		' Column 3
		item.SetText(dataSet.STCompletedDate)
		item.SetColumn(3)
		list.SetItem(item)
		' Column 4
		If dataSet.STCancelled = 1 Then
			item.SetText("Yes")
		Else
			item.SetText("No")
		End If
		item.SetColumn(4)
		list.SetItem(item)
		' Column 5
		item.SetText(dataSet.STName)
		item.SetColumn(5)
		list.SetItem(item)
	End Method
	
End Type





Type TDatabaseTables

	Global db:TDBConnection

	Method Init:TDatabaseTables()
	
		If Not db Then
			DeleteFile("ListCtrlClientData.db")
			DebugLog "Deleted File to start with a fresh one"
			If Not FileType("ListCtrlClientData.db") Then
				CreateDB("ListCtrlClientData.db")
			End If
			db = LoadDatabase("SQLITE", "ListCtrlClientData.db")
		End If
	
		' Check that the DB connection has been made
		If Not db Then
			Notify("Unable to Open the Database ListCtrlClientData.db")
			End
		End If
	
		' Check if the database has an error on opening
		If db.hasError() Then
			errorAndClose(db)
		End If

		Return Self
	End Method
	
	Method CreateDB(filename:String)

		Local dbase:TDBConnection = LoadDatabase("SQLITE", "ListCtrlClientData.db")
		Local s:String
		' Create tblMainTask
		s = "CREATE TABLE tblMainTask (MainTaskID integer primary key AUTOINCREMENT, " + ..
	  		" MTName varchar(100), " + ..
	  		" MTCreateDate varchar(10), " + ..
	  		" MTDueDate varchar(10), " + ..
	  		" MTCompletedDate varchar(10), " + ..
	  		" MTCancelled integer, " + ..
	  		" SelectorID integer, " + ..
			" TMID integer) "
		dbase.executeQuery(s)
		If dbase.hasError() Then
			errorAndClose(dbase)
		End If
		DebugLog "tblMainTask Created"
		' Create tblSubTask
		s = "CREATE TABLE tblSubTask (SubTaskID integer primary key AUTOINCREMENT, " + ..
	  		" STName varchar(100), " + ..
	  		" STOrder integer, " + ..
	  		" STMemo varchar(1000), " + ..
	  		" STCreateDate varchar(10), " + ..
	  		" STDueDate varchar(10), " + ..
	  		" STCompletedDate varchar(10), " + ..
	  		" STLastUpdatedDate varchar(10), " + ..
	  		" STCancelled integer, " + ..
	  		" MainTaskID integer, " + ..
			" TMID integer) "
		dbase.executeQuery(s)
		If dbase.hasError() Then
			errorAndClose(dbase)
		End If
		DebugLog "tblSubTask Created"
		
		' create some data...
		' tblMainTask
		dbase.executeQuery("INSERT INTO tblMainTask values (NULL, 'MainTask One', '01 Jan 2008', '02 Feb 2008', '03 Mar 2008', 0, 1, 1)")
		dbase.executeQuery("INSERT INTO tblMainTask values (NULL, 'MainTask Two', '01 Mar 2008', '02 Feb 2008', '03 Jan 2008', 0, 1, 2)")

		' tblSubTask
		dbase.executeQuery("INSERT INTO tblSubTask values (NULL, 'SubTask One for MainTask One', 1, 'Memo Field','01 Jan 2008', '02 Feb 2008', '03 Mar 2008', '04 Mar 2008', 0, 1, 1)")
		dbase.executeQuery("INSERT INTO tblSubTask values (NULL, 'SubTask One for MainTask Two', 1, 'Memo Field','01 Jan 2007', '02 Feb 2007', '03 Mar 2007', '04 Mar 2007', 0, 2, 2)")
		dbase.executeQuery("INSERT INTO tblSubTask values (NULL, 'SubTask Two for MainTask Two', 1, 'Memo Field','02 Jan 2007', '03 Feb 2007', '04 Mar 2007', '05 Mar 2007', 0, 2, 2)")

		dbase.close()
		
	End Method

	Function errorAndClose(db:TDBConnection)
		DebugLog(db.error().toString())
		db.close()
		End
	End Function
		
End Type


Type TMainTask Extends TDatabaseTables

	Field MainTaskID:Int
	Field MTName:String
	Field MTCreateDate:String
	Field MTDueDate:String
	Field MTCompletedDate:String
	Field MTCancelled:Int
	Field SelectorID:Int
	Field TMID:Int
	
	Method Init:TMainTask()
		Return TMainTask(Super.init())
	End Method

	Function GetListForTypeId:TList()
		Local list:TList = New TList
	
		Local mt:TMainTask = New TMainTask.Init()
		
		If db.isOpen() Then
		
			Local sql:String = "SELECT MainTaskID, MTName, MTCreateDate, MTDueDate, MTCompletedDate, MTCancelled, SelectorID, TMID "
			sql = sql + "FROM tblMainTask "

			' Get the data from the table
			Local query:TDatabaseQuery = db.executeQuery(sql)
			
			' Loop through each record and add the values to the TList
			For Local record:TQueryRecord = EachIn query
				Local MainTask:TMainTask = New TMainTask 
				MainTask.MainTaskID = record.GetInt(0)
				MainTask.MTName = record.GetString(1)
				MainTask.MTCreateDate = record.GetString(2)
				MainTask.MTDueDate = record.GetString(3)
				MainTask.MTCompletedDate = record.GetString(4)
				MainTask.MTCancelled = record.GetInt(5)
				MainTask.SelectorID = record.GetInt(6)
				MainTask.TMID = record.GetInt(7)
				DebugLog "Start of Data for MainTask record"
				DebugLog "MainTaskID = " + MainTask.MainTaskID
				DebugLog "MTName = " + MainTask.MTName
				DebugLog "MTCreateDate = " + MainTask.MTCreateDate
				DebugLog "MTDueDate = " + MainTask.MTDueDate
				DebugLog "MTCompletedDate = " + MainTask.MTCompletedDate
				DebugLog "MTCancelled = " + MainTask.MTCancelled
				DebugLog "SelectorID = " + MainTask.SelectorID
				DebugLog "TMID = " + MainTask.TMID
				DebugLog "End of Data for MainTask record"
				
				list.AddLast(MainTask)
			Next

		EndIf
		
		Return list
	End Function

End Type

Type TSubTask Extends TDatabaseTables

	Field SubTaskID:Int
	Field STName:String
	Field STOrder:Int
	Field STMemo:String
	Field STCreateDate:String
	Field STDueDate:String
	Field STCompletedDate:String
	Field STLastUpdatedDate:String
	Field STCancelled:Int
	Field MainTaskID:Int
	Field TMID:Int
	
	Method Init:TSubTask()
		Return TSubTask(Super.init())
	End Method

	Function GetListForTypeId:TList(id:Int)
		Local list:TList = New TList
	
		Local st:TSubTask = New TSubTask.Init()
		
		If db.isOpen() Then
		
			Local sql:String = "SELECT SubTaskID, STName, STOrder, STMemo, STCreateDate, STDueDate, STCompletedDate, STLastUpdatedDate, STCancelled, MainTaskID, TMID "
			sql = sql + "FROM tblSubTask "
			sql = sql + "WHERE MainTaskID = " + id

			' Get the data from the table
			Local query:TDatabaseQuery = db.executeQuery(sql)
			
			' Loop through each record and add the values to the TList
			For Local record:TQueryRecord = EachIn query
				Local SubTask:TSubTask = New TSubTask 
				SubTask.SubTaskID = record.GetInt(0)
				SubTask.STName = record.GetString(1)
				SubTask.STOrder = record.GetInt(2)
				SubTask.STMemo = record.GetString(3)
				SubTask.STCreateDate = record.GetString(4)
				SubTask.STDueDate = record.GetString(5)
				SubTask.STCompletedDate = record.GetString(6)
				SubTask.STLastUpdatedDate = record.GetString(7)
				SubTask.STCancelled = record.GetInt(8)
				SubTask.MainTaskID = record.GetInt(9)
				SubTask.TMid = record.GetInt(10)
				DebugLog "Start of Data for SubTask record"
				DebugLog "SubTaskID = " + SubTask.SubTaskID
				DebugLog "STName = " + SubTask.STName
				DebugLog "STOrder = " + SubTask.STOrder
				DebugLog "STMemo = " + SubTask.STMemo
				DebugLog "STCreateDate = " + SubTask.STCreateDate
				DebugLog "STDueDate = " + SubTask.STDueDate
				DebugLog "STCompletedDate = " + SubTask.STCompletedDate
				DebugLog "STLastUpdatedDate = " + SubTask.STLastUpdatedDate
				DebugLog "STCancelled = " + SubTask.STCancelled
				DebugLog "MainTaskID = " + SubTask.MainTaskID
				DebugLog "TMID = " + SubTask.TMid
				DebugLog "End of Data for SubTask record"
				
				list.AddLast(SubTask)
			Next

		EndIf
		
		Return list
	End Function

End Type


You shouldn't be casting away your wxListEvent object in the ListCtrl1Clicked() method.

Try using event.GetIndex() instead.

:-)

Also be very aware that currently wxListCtrls do not reference count any object you set in the item client data, so you'll need to keep a separate list around in order to prevent any objects added into the wxListCtrl from being GC'd.

This will be sorted in the future some time... just not had time to go over it yet.

not really sure what you mean in the first 2 sentences.
There are only 2 lines it could refer to and i tried event.GetIndex() but then it won't compile.

change :
Method ListCtrl1Clicked(event:wxListEvent)

	Local index:Int = wxCommandEvent(event).GetInt()

to
Method ListCtrl1Clicked(event:wxListEvent)

	Local index:Int = event.GetIndex()


ah
i left the wxCOmmandEvent in.

ah :-)

when i click the first Main Task i get the correct Sub Task.
the index is 0. does this refer column0?
when i click the second main task i get a null reference from:
Local sMainTask:TMainTask = TMainTask(m_listCtrl1.GetItemData(index))
index is 1

i don't think it has been GC'ed as it finds data for the first item, but i could be wrong

scratch that.
if i click the either item for the first time it works correctly. then click the other item and it fails, so i guess gc has occured.

so to keep the list around can i add a field to the type and use that instead?

Yeah, you could have a field representing the currently selected item.

You could also, when building the second list, populate those items into a list contained within your TMainTask type. Logically, a TMainTask can contain a list of TSubTasks.

So i think you mean add this line to the TMainTask.GetListForTypeId function:
' added this line to get the SubTasks associated to each MainTask, and added a list object to the MainTaskList
Local STList:TList = TSubTask.GetListForTypeId(MainTask.MainTaskID)

then when i am adding the ClientData in MyFrame2.MTInsertIntoList() with item.SetData(dataSet) then i should also have my SubTask List, specific for that MainTask entry (MainTaskID).

Next i click on a MainTask and it does a For Eachin loop to get the SubTask Data from the ClientData.

It sounds reasonable to me, but i haven't the faintest idea how to access the second list in the client data. As you can see i have made no attempt to modify the Populate_m_listCtrl2 or STInsertIntoList methods.

Well i did make some attempts but they failed miserably...

I haven't added the list field either yet, to prevent the client data being GC'ed. I can't seem to wrap my head around adding a list with lists inside it, and then accessing that data afterwards... it is the data access i am struggling with the most.

i feel like this is one of the last hurdles to cross. in my main program using menus and popup windows with a right click is working in a seperate sample program, adding/editing/deleting records is easy and updating the GUI just recalls some of these functions.

I think i am close to pulling the disparate functionality together into the main program to finally get the Task List Manager done.

I just need some help with this part again.

Regards
Glenn

'
' Example application stub generated by wxCodeGen v1.02 : 10 Mar 2008 21:41:30
'
SuperStrict

Framework wx.wxApp

Import "ListCtrlClientDataGUI.bmx"
Import BaH.DBSQLite
Import brl.filesystem
Import brl.system

Const cListCtrlDateWidth:Int = 80

New MyApp.run()

Type MyApp Extends wxApp

	Field frame:MyFrame2
	
	Method OnInit:Int()
?macos
		' make the default Mac font for controls not so big
		wxSystemOptions.SetOption(wxWINDOW_DEFAULT_VARIANT, wxWINDOW_VARIANT_SMALL)
?
		' needed so my graphics show up
		wxInitAllImageHandlers()
		' for Mac/Win32 (it is default on Linux)
		wxImage.AddHandler( New wxXPMHandler )
		' create the frame
		Local frame:MyFrame2 = MyFrame2(New MyFrame2.Create(, , "ListCtrl Client Data Test"))
		' frames are created hidden. You need to manually show it.
		frame.show()
		' make sure it is brought to the front
		SetTopWindow(frame)

		Return True
	End Method
	
End Type



Type MyFrame2 Extends MyFrame2Base

	Method OnInit()
		Super.OnInit()
		' There are 2 list controls. Create the Column Headers here
		CreateListCtrlColumnHeaders()
		' populate the Due Date ListCtrl
		PopulateListCtrl(m_listCtrl1)		
		
	End Method

	Method CreateListCtrlColumnHeaders()
		' do the First List Control
		SetColumnHeadings(m_listCtrl1)
		' do the Second List Control
		SetColumnHeadings(m_listCtrl2)
	End Method

	Method SetColumnHeadings(ListControl:wxListCtrl)
		Select ListControl
			Case m_listCtrl1
				ListControl.InsertColumn(0, ("TM"), wxLIST_FORMAT_LEFT, 30)	' Team Member Initials
				ListControl.InsertColumn(1, ("Created"), wxLIST_FORMAT_LEFT, cListCtrlDateWidth)
				ListControl.InsertColumn(2, ("Due Date"), wxLIST_FORMAT_LEFT, cListCtrlDateWidth)
				ListControl.InsertColumn(3, ("Completed"), wxLIST_FORMAT_LEFT, cListCtrlDateWidth)
				ListControl.InsertColumn(4, ("Cancelled"), wxLIST_FORMAT_CENTRE, cListCtrlDateWidth)
				ListControl.InsertColumn(5, ("Main Task Name"), wxLIST_FORMAT_LEFT,200)
			Case m_listCtrl2
				ListControl.InsertColumn(0, ("TM"), wxLIST_FORMAT_LEFT, 30)	' Team Member Initials
				ListControl.InsertColumn(1, ("Created"), wxLIST_FORMAT_LEFT, cListCtrlDateWidth)
				ListControl.InsertColumn(2, ("Due Date"), wxLIST_FORMAT_LEFT, cListCtrlDateWidth)
				ListControl.InsertColumn(3, ("Completed"), wxLIST_FORMAT_LEFT, cListCtrlDateWidth)
				ListControl.InsertColumn(4, ("Cancelled"), wxLIST_FORMAT_CENTRE, cListCtrlDateWidth)
				ListControl.InsertColumn(5, ("Sub Task Name"), wxLIST_FORMAT_LEFT,200)
			Default
				DebugLog "You have passed a ListCtrl that doesn't exist"
		End Select
	End Method

	Method PopulateListCtrl(ListControl:wxListCtrl)
		' delete the Items, so the column headers don't need to be recreated
		ListControl.DeleteAllItems()
		' check the ListCtrl exists
		If ListControl Then
			' Get the data for the MainTask ListCtrl (using the id) from TSelector
			Local list:TList = TMainTask.GetListForTypeId()
			' i is the row number in the ListCtrl
			Local i:Int = 0	
			For Local dataset:TMainTask = EachIn list
				' InsertIntoList(Row Number, the data for this element of the list, the name of the ListCtrl)
				MTInsertIntoList(i, dataset, ListControl)
				' Increment i so you are always adding to the end of the ListCtrl
				' comment this line out if you want your listctrl populated in reverse, ie i = 0
				i:+ 1
			Next
		End If
	End Method

	Method MTInsertIntoList(index:Int, dataSet:TMainTask, list:wxListCtrl)
		' wxListItem - This type stores information about a wxListCtrl item or column.
		' Some comments taken from <a href="http://www.gibbongames.com/content/view/52/50/" target="_blank">http://www.gibbongames.com/content/view/52/50/</a>
		' wxListItem encapsulates the contents of a given listbox cell.
		' So each row/column combination has its own wxListItem structure.
		Local item:wxListItem = New wxListItem.Create()
		' Define all the parameters for the column, then use list.SetItem(item) to bind it
		' Column 0
		item.SetImage(-1)
		item.SetText(dataSet.TMID)
		item.SetId(index)
'******************* set the Client Data???? so it can be used in "Method MainTaskSelected(event:wxListEvent)"
		item.SetData(dataSet)
		' Apparently Column 0 requires InsertItem, but Column n > 0 requires SetItem (assuming that Column 0 has been previously inserted).
		list.InsertItem(item)
		' Column 1
		item.SetText(dataSet.MTCreateDate)
		item.SetColumn(1)
		list.SetItem(item)
		' Column 2
		item.SetText(dataSet.MTDueDate)
		item.SetColumn(2)
		list.SetItem(item)
		' Column 3
		item.SetText(dataSet.MTCompletedDate)
		item.SetColumn(3)
		list.SetItem(item)
		' Column 4
		If dataSet.MTCancelled = 1 Then
			item.SetText("Yes")
		Else
			item.SetText("No")
		End If
		item.SetColumn(4)
		list.SetItem(item)
		' Column 5
		item.SetText(dataSet.MTName)
		item.SetColumn(5)
		list.SetItem(item)
	End Method

	Method ListCtrl1Clicked(event:wxListEvent)
		DebugLog "ListCtrl1Clicked"
		' once the MainTask ListCtrl is clicked we need to populate the SubTask ListCtrl
		' Find the index of the item clicked
		Local index:Int = event.GetIndex()
		DebugLog "index = " + index
'		DebugStop
		
		' Get the value of the item clicked
		' Note: This is the TMainTask object we stored earlier.
		' It gives us access to the Id, for example, which gives us a quicker
		' lookup in the SQL. (ie. no table joins, since the Id is a foreign key!)
' ***************************** GetItemData(index) looks correct but i keep getting a 1 sent to the PopulateSubTask as the id
		Local sMainTask:TMainTask = TMainTask(m_listCtrl1.GetItemData(index))
		' now fill in the SubTask ListCtrl
		Populate_m_listCtrl2(sMainTask.MainTaskID, m_listCtrl2)
	End Method

	Method Populate_m_listCtrl2(id:Int, ListControl:wxListCtrl)
		' delete the Items, so the column headers don't need to be recreated
		ListControl.DeleteAllItems()
		' check the ListCtrl exists
		If ListControl Then
			' Get the data for the MainTask ListCtrl (using the id) from TSelector
			Local list:TList = TSubTask.GetListForTypeId(id)
			' i is the row number in the ListCtrl
			Local i:Int = 0	
			For Local dataset:TSubTask = EachIn list
				' InsertIntoList(Row Number, the data for this element of the list, the name of the ListCtrl)
				STInsertIntoList(i, dataset, ListControl)
				' Increment i so you are always adding to the end of the ListCtrl
				' comment this line out if you want your listctrl populated in reverse, ie i = 0
				i:+ 1
			Next
		End If
	End Method

	Method STInsertIntoList(index:Int, dataSet:TSubTask, list:wxListCtrl)
		' wxListItem - This type stores information about a wxListCtrl item or column.
		' Some comments taken from <a href="http://www.gibbongames.com/content/view/52/50/" target="_blank">http://www.gibbongames.com/content/view/52/50/</a>
		' wxListItem encapsulates the contents of a given listbox cell.
		' So each row/column combination has its own wxListItem structure.
		Local item:wxListItem = New wxListItem.Create()
		' Define all the parameters for the column, then use list.SetItem(item) to bind it
		' Column 0
		item.SetImage(-1)
		item.SetText(dataSet.TMID)
		item.SetId(index)
'******************* set the Client Data???? so it can be used in "Method MainTaskSelected(event:wxListEvent)"
		item.SetData(dataSet)
		' Apparently Column 0 requires InsertItem, but Column n > 0 requires SetItem (assuming that Column 0 has been previously inserted).
		list.InsertItem(item)
		' Column 1
		item.SetText(dataSet.STCreateDate)
		item.SetColumn(1)
		list.SetItem(item)
		' Column 2
		item.SetText(dataSet.STDueDate)
		item.SetColumn(2)
		list.SetItem(item)
		' Column 3
		item.SetText(dataSet.STCompletedDate)
		item.SetColumn(3)
		list.SetItem(item)
		' Column 4
		If dataSet.STCancelled = 1 Then
			item.SetText("Yes")
		Else
			item.SetText("No")
		End If
		item.SetColumn(4)
		list.SetItem(item)
		' Column 5
		item.SetText(dataSet.STName)
		item.SetColumn(5)
		list.SetItem(item)
	End Method
	
End Type





Type TDatabaseTables

	Global db:TDBConnection

	Method Init:TDatabaseTables()
	
		If Not db Then
			DeleteFile("ListCtrlClientData.db")
			DebugLog "Deleted File to start with a fresh one"
			If Not FileType("ListCtrlClientData.db") Then
				CreateDB("ListCtrlClientData.db")
			End If
			db = LoadDatabase("SQLITE", "ListCtrlClientData.db")
		End If
	
		' Check that the DB connection has been made
		If Not db Then
			Notify("Unable to Open the Database ListCtrlClientData.db")
			End
		End If
	
		' Check if the database has an error on opening
		If db.hasError() Then
			errorAndClose(db)
		End If

		Return Self
	End Method
	
	Method CreateDB(filename:String)

		Local dbase:TDBConnection = LoadDatabase("SQLITE", "ListCtrlClientData.db")
		Local s:String
		' Create tblMainTask
		s = "CREATE TABLE tblMainTask (MainTaskID integer primary key AUTOINCREMENT, " + ..
	  		" MTName varchar(100), " + ..
	  		" MTCreateDate varchar(10), " + ..
	  		" MTDueDate varchar(10), " + ..
	  		" MTCompletedDate varchar(10), " + ..
	  		" MTCancelled integer, " + ..
	  		" SelectorID integer, " + ..
			" TMID integer) "
		dbase.executeQuery(s)
		If dbase.hasError() Then
			errorAndClose(dbase)
		End If
		DebugLog "tblMainTask Created"
		' Create tblSubTask
		s = "CREATE TABLE tblSubTask (SubTaskID integer primary key AUTOINCREMENT, " + ..
	  		" STName varchar(100), " + ..
	  		" STOrder integer, " + ..
	  		" STMemo varchar(1000), " + ..
	  		" STCreateDate varchar(10), " + ..
	  		" STDueDate varchar(10), " + ..
	  		" STCompletedDate varchar(10), " + ..
	  		" STLastUpdatedDate varchar(10), " + ..
	  		" STCancelled integer, " + ..
	  		" MainTaskID integer, " + ..
			" TMID integer) "
		dbase.executeQuery(s)
		If dbase.hasError() Then
			errorAndClose(dbase)
		End If
		DebugLog "tblSubTask Created"
		
		' create some data...
		' tblMainTask
		dbase.executeQuery("INSERT INTO tblMainTask values (NULL, 'MainTask One', '01 Jan 2008', '02 Feb 2008', '03 Mar 2008', 0, 1, 1)")
		dbase.executeQuery("INSERT INTO tblMainTask values (NULL, 'MainTask Two', '01 Mar 2008', '02 Feb 2008', '03 Jan 2008', 0, 1, 2)")

		' tblSubTask
		dbase.executeQuery("INSERT INTO tblSubTask values (NULL, 'SubTask One for MainTask One', 1, 'Memo Field','01 Jan 2008', '02 Feb 2008', '03 Mar 2008', '04 Mar 2008', 0, 1, 1)")
		dbase.executeQuery("INSERT INTO tblSubTask values (NULL, 'SubTask Two for MainTask One', 1, 'Memo Field','02 Jan 2008', '02 Feb 2008', '03 Mar 2008', '04 Mar 2008', 0, 1, 1)")
		dbase.executeQuery("INSERT INTO tblSubTask values (NULL, 'SubTask One for MainTask Two', 1, 'Memo Field','01 Jan 2007', '02 Feb 2007', '03 Mar 2007', '04 Mar 2007', 0, 2, 2)")
		dbase.executeQuery("INSERT INTO tblSubTask values (NULL, 'SubTask Two for MainTask Two', 1, 'Memo Field','02 Jan 2007', '03 Feb 2007', '04 Mar 2007', '05 Mar 2007', 0, 2, 2)")

		dbase.close()
		
	End Method

	Function errorAndClose(db:TDBConnection)
		DebugLog(db.error().toString())
		db.close()
		End
	End Function
		
End Type


Type TMainTask Extends TDatabaseTables

	Field MainTaskID:Int
	Field MTName:String
	Field MTCreateDate:String
	Field MTDueDate:String
	Field MTCompletedDate:String
	Field MTCancelled:Int
	Field SelectorID:Int
	Field TMID:Int
	
	Method Init:TMainTask()
		Return TMainTask(Super.init())
	End Method

	Function GetListForTypeId:TList()
		Local list:TList = New TList
	
		Local mt:TMainTask = New TMainTask.Init()
		
		If db.isOpen() Then
		
			Local sql:String = "SELECT MainTaskID, MTName, MTCreateDate, MTDueDate, MTCompletedDate, MTCancelled, SelectorID, TMID "
			sql = sql + "FROM tblMainTask "

			' Get the data from the table
			Local query:TDatabaseQuery = db.executeQuery(sql)
			
			' Loop through each record and add the values to the TList
			For Local record:TQueryRecord = EachIn query
				Local MainTask:TMainTask = New TMainTask 
				MainTask.MainTaskID = record.GetInt(0)
				MainTask.MTName = record.GetString(1)
				MainTask.MTCreateDate = record.GetString(2)
				MainTask.MTDueDate = record.GetString(3)
				MainTask.MTCompletedDate = record.GetString(4)
				MainTask.MTCancelled = record.GetInt(5)
				MainTask.SelectorID = record.GetInt(6)
				MainTask.TMID = record.GetInt(7)
				DebugLog "Start of Data for MainTask record"
				DebugLog "MainTaskID = " + MainTask.MainTaskID
				DebugLog "MTName = " + MainTask.MTName
				DebugLog "MTCreateDate = " + MainTask.MTCreateDate
				DebugLog "MTDueDate = " + MainTask.MTDueDate
				DebugLog "MTCompletedDate = " + MainTask.MTCompletedDate
				DebugLog "MTCancelled = " + MainTask.MTCancelled
				DebugLog "SelectorID = " + MainTask.SelectorID
				DebugLog "TMID = " + MainTask.TMID
				DebugLog "End of Data for MainTask record"
				
				' added this line to get the SubTasks associated to each MainTask, and added a list object to the MainTaskList
				Local STList:TList = TSubTask.GetListForTypeId(MainTask.MainTaskID)
				
				list.AddLast(MainTask)
			Next

		EndIf
		
		Return list
	End Function

End Type

Type TSubTask Extends TDatabaseTables

	Field SubTaskID:Int
	Field STName:String
	Field STOrder:Int
	Field STMemo:String
	Field STCreateDate:String
	Field STDueDate:String
	Field STCompletedDate:String
	Field STLastUpdatedDate:String
	Field STCancelled:Int
	Field MainTaskID:Int
	Field TMID:Int
	
	Method Init:TSubTask()
		Return TSubTask(Super.init())
	End Method

	Function GetListForTypeId:TList(id:Int)
		Local list:TList = New TList
	
		Local st:TSubTask = New TSubTask.Init()
		
		If db.isOpen() Then
		
			Local sql:String = "SELECT SubTaskID, STName, STOrder, STMemo, STCreateDate, STDueDate, STCompletedDate, STLastUpdatedDate, STCancelled, MainTaskID, TMID "
			sql = sql + "FROM tblSubTask "
			sql = sql + "WHERE MainTaskID = " + id

			' Get the data from the table
			Local query:TDatabaseQuery = db.executeQuery(sql)
			
			' Loop through each record and add the values to the TList
			For Local record:TQueryRecord = EachIn query
				Local SubTask:TSubTask = New TSubTask 
				SubTask.SubTaskID = record.GetInt(0)
				SubTask.STName = record.GetString(1)
				SubTask.STOrder = record.GetInt(2)
				SubTask.STMemo = record.GetString(3)
				SubTask.STCreateDate = record.GetString(4)
				SubTask.STDueDate = record.GetString(5)
				SubTask.STCompletedDate = record.GetString(6)
				SubTask.STLastUpdatedDate = record.GetString(7)
				SubTask.STCancelled = record.GetInt(8)
				SubTask.MainTaskID = record.GetInt(9)
				SubTask.TMid = record.GetInt(10)
				DebugLog "Start of Data for SubTask record"
				DebugLog "SubTaskID = " + SubTask.SubTaskID
				DebugLog "STName = " + SubTask.STName
				DebugLog "STOrder = " + SubTask.STOrder
				DebugLog "STMemo = " + SubTask.STMemo
				DebugLog "STCreateDate = " + SubTask.STCreateDate
				DebugLog "STDueDate = " + SubTask.STDueDate
				DebugLog "STCompletedDate = " + SubTask.STCompletedDate
				DebugLog "STLastUpdatedDate = " + SubTask.STLastUpdatedDate
				DebugLog "STCancelled = " + SubTask.STCancelled
				DebugLog "MainTaskID = " + SubTask.MainTaskID
				DebugLog "TMID = " + SubTask.TMid
				DebugLog "End of Data for SubTask record"
				
				list.AddLast(SubTask)
			Next

		EndIf
		
		Return list
	End Function

End Type


After rereading everything i can find about Client Data I can confidently say i understand it conceptually.
Accessing its data in a clean way that makes sense to me is another story.
So because this has delayed any progress on my actual project for the last week i have decided to not use client data and go back to the simple method of putting the primary key into the first ListCtrl column and giving it a width of 0. That way it is not seen but i can access it with wxListItem.GetText().

Not the most elegant solution but since i am still a beginner, hey it works :)

If anyone wants to see what i ended up with just let me know and i will post the commented code.

Cheers
Glenn

Righty... I've added support for reference counting Objects stored in wxListCtrl item client data now.

From my initial tests :
1) Added objects to items, and see how long they stay valid for
2) DeleteallItems and replace, seeing if we are leaking.

both are passing, so I'm reasonably happy that it's working now.
However, it will require normal use to make sure I haven't missed anything out.

This means that you should be able now to safely use data stored in client data without having to keep your own copy of it elsewhere.

Have we praised you enough lately?

Many thanks again.

Glenn