Code archives/BlitzPlus Gui/Multi-Column List Box

This code has been declared by its author to be Public Domain code.

Download source code

Multi-Column List Box by Ghost Dancer
(Posted 18 years ago)
Multi-Column List Box with sort (Windows only). I've modified Ziltches code to make it OO structured and also added my own sort functionality (clicking column heading will alternate between ascending and decending sort). Currently no support for multiple selected items but hassle me and I'm sure I'll do it.
'******************************************************************************
'Multi-column list box with sort V1.2
'Original code: Ziltch 29 August 2006.
'Modified by Ghost Dancer 06 August 2008
'You can use this code if you credit Ziltch & Ghost Dancer
'
'V1.2 update
'	- Added sortEnable & sortDisable methods.
'	- Fixed sorting bug.
'	- Added update method which can be called on EVENT_WINDOWSIZE to reset first column width.
'	- Renamed some methods.
'
'V1.1 updates
'	- Fixed to work in Max 1.30.
'	- TListView now extends TProxyGadget.
'	- Is now unicode compliant.
'	- Remembers the selected item after a sort.
'******************************************************************************

'******************************************************************************
'example usage
'******************************************************************************
Strict

Import MaxGUI.Drivers

'set up
Local window:TGadget = CreateWindow("Multi-Column List Example", 100, 100, 320, 250, Null, WINDOW_RESIZABLE|WINDOW_TITLEBAR|WINDOW_CLIENTCOORDS)
Local listView:TListView = TListView.Create(2, 2, 310, 150, window, "Name")
SetGadgetLayout listView, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_CENTERED

'add 2nd & 3rd columns
listView.addListViewColumn("Sex", 80)
listView.addListViewColumn("Age", 80)

'add some data
listView.addListViewItem(["Simon", "Male", "34"])
listView.addListViewItem(["Jane", "Female", "29"])
listView.addListViewItem(["Peter", "Male", "38"])
listView.addListViewItem(["Sally", "Female", "44"])

'2nd list
Local listView2:TListView = TListView.Create(2, 160, 310, 80, window, "one")
SetGadgetLayout listView2, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED

listView2.addListViewColumn("two", 80)
listView2.addListViewColumn("three", 80)

'add some data
listView2.addListViewItem(["xtest 1-1", "xtest 1-2", "xtest 1-3"])
listView2.addListViewItem(["test 2-1", "test 2-2", "test 2-3"])

Local Gadget:TGadget

'Main Loop
Repeat
	WaitEvent()
	Gadget = TGadget(EventSource())
	
	Select EventID()
		Case EVENT_WINDOWSIZE
			TListView.update
		Case EVENT_WINDOWCLOSE
			Select gadget
			Case window
				Exit
			End Select
	End Select
Forever

'print selected data
Print "Selected Item: " + listView.getSelectedItem() + ", aged " + listView.getSelectedItem(2)

End


'==============================================================================
Type TListView Extends TProxyGadget
'==============================================================================
	Const LBS_MULTICOLUMN = 512
	
	'globals are used for sorting
	Global oldListProc							'used for column heading selections
	Global gadgetList:TList = CreateList()		'global list of type instances
	
	Field curColumn								'number of columns in list
	Field columnHeading:TColumnHeading[1]		'array of column data
	
	Field listBox:TGadget						'the gadget
	Field listboxHwnd							'listbox's HWND handle
	Field sortColumns = True					'enable/disable sorting
	
	
	'------------------------------------------------------------------------------
	Function create:TListView(x, y, w, h, parent:TGadget, heading$ = "", width = 100)
	'heading & width are for first column
	'------------------------------------------------------------------------------
		Local newListView:TListView = New TListView 
		
		newListView.listBox = CreateListBox(x, y, w, h, parent)
		newListView.SetProxy newListView.listBox
		
		Local TempCanvas:TGadget = CreateCanvas(0, 0, 0, 0, parent)
		
		newListView.listboxHwnd = QueryGadget(newListView.listBox, QUERY_HWND)
		
		oldListProc = SetWindowLongW(newListView.listboxHwnd, GWL_WNDPROC, Int(Byte Ptr NewListProc))
		
		newListView.setHeading(0, heading$, width)
		
		'store the gadget in global list...
		gadgetList.AddLast newListView
		
		'and also return it for standard OO usage
		Return newListView
	End Function
	
	
	'------------------------------------------------------------------------------
	Method setHeading(column, heading$, width, action = LVM_SETCOLUMNW)
	'------------------------------------------------------------------------------
		Local col:LVCOLUMNW = New LVCOLUMNW
		
		columnHeading[column] = New TColumnHeading
		columnHeading[column].width = width
		
		If columnHeading[Column].width = 0 Then
			Col.mask = LVCF_TEXT| LVCF_FMT 
		Else
			Col.mask = LVCF_TEXT| LVCF_FMT | LVCF_WIDTH
			col.cx   = columnHeading[Column].width
		End If
		
		col.pszText = heading$.ToWString()
		
		Local ListBoxstyle = GetWindowLongW(ListboxHwnd , GWL_STYLE)
		
		If (ListBoxstyle &  LVS_NOCOLUMNHEADER ) Then
			ListBoxstyle  = ListBoxstyle  ~LVS_NOCOLUMNHEADER 
			If ListBoxstyle & LVS_EDITLABELS=0 Then ListBoxstyle  = ListBoxstyle  | LVS_EDITLABELS
			SetWindowLongW(ListboxHwnd , GWL_STYLE,  ListBoxstyle )  'change the style so that we have headings
		End If
		
		SendMessageW(ListboxHwnd, action, Column, Int(Byte Ptr Col))
		
		columnWidth
	End Method
	
	
	'------------------------------------------------------------------------------
	Method addListViewColumn(heading$, width)
	'------------------------------------------------------------------------------
		curColumn:+ 1
		
		columnHeading = columnHeading[..curColumn+1]
		
		setHeading curColumn, heading, width, LVM_INSERTCOLUMNW
	End Method
	
	
	'------------------------------------------------------------------------------
	Method addListViewItem(text$[])
	'add full row from array
	'------------------------------------------------------------------------------
		Local curRow = CountGadgetItems(listBox)
		
		'add first column & reset width
		AddGadgetItem(listBox, text$[0])
		columnWidth
		
		For Local column = 1 To text$.Length - 1
			Local ListboxHwnd = QueryGadget(listBox, QUERY_HWND)
			Local ColItem:LVITEMW  = New LVITEMW
			
			ColItem.mask = LVIF_TEXT
			ColItem.iSubItem = column
			ColItem.iItem = curRow
			ColItem.pszText = Text$[column].ToWString()
			ColItem.cchTextMax =  Text$[column].Length + 1
			SendMessageW( ListboxHwnd, LVM_SETITEMW ,0, Int(Byte Ptr ColItem))
			ColItem = Null
		Next
	End Method
	
	
	'------------------------------------------------------------------------------
	Method getListViewItem:String(Row, Column)
	'------------------------------------------------------------------------------
		If ListboxHwnd Then
			Local Ans$
			Local TextStorage:Short[1024]
			Local ColItem:LVITEMW  = New LVITEMW
			
			ColItem.mask = LVIF_TEXT
			ColItem.iSubItem = Column
			ColItem.iItem = Row
			ColItem.pszText = TextStorage
			ColItem.cchTextMax =  1024 
			SendMessageW( ListboxHwnd,LVM_GETITEMW,0,Int(Byte Ptr ColItem))
			
			If ColItem.pszText Then
				Ans$=String.FromWString(ColItem.pszText)
				ColItem=Null
			End If
			
			Return Trim(Ans$)
		End If
	End Method
	
	
	'------------------------------------------------------------------------------
	Method setListViewItem(Text:String, Row, Column = 0)
	'set text in a specific row, column
	'------------------------------------------------------------------------------
		If Column = 0 Then listBox.items[Row].Text = text
		
		If ListboxHwnd Then
			Local ColItem:LVITEMW  = New LVITEMW
			ColItem.mask = LVIF_TEXT
			ColItem.iSubItem = Column
			ColItem.iItem = Row
			ColItem.pszText = Text.ToWString()
			ColItem.cchTextMax =  Len(Text) 
			
			Return SendMessageW( ListboxHwnd,LVM_SETITEMW,0,Int(Byte Ptr ColItem))
		End If
	End Method
	
	
	'------------------------------------------------------------------------------
	Method columnWidth(Column = 0)
	'------------------------------------------------------------------------------
		Local col:LVCOLUMNW = New LVCOLUMNW
		
		Col.mask = LVCF_WIDTH
		col.cx   = columnHeading[Column].width
		SendMessageW(ListboxHwnd,LVM_SETCOLUMNW,Column,Int(Byte Ptr Col))
	End Method
	
	
	'------------------------------------------------------------------------------
	Method listBoxetMultiSelect()
	'------------------------------------------------------------------------------
		Local ListBoxstyle = GetWindowLongW(ListboxHwnd , GWL_STYLE)
		
		If  (ListBoxstyle & LVS_SINGLESEL) = LVS_SINGLESEL Then
			Return SetWindowLongW(ListboxHwnd , GWL_STYLE,  ListBoxstyle  ~ LVS_SINGLESEL )  'change the style 		
		End If
	End Method
	
	
	'------------------------------------------------------------------------------
	Method getSelectedItem$(col = 0)
	'------------------------------------------------------------------------------
		If SelectedGadgetItem(listBox) >= 0 Then
			Return getListViewItem(SelectedGadgetItem(listBox), col)
		End If
	End Method
	
	
	'------------------------------------------------------------------------------
	Method sort(sortColumn)
	'------------------------------------------------------------------------------
		If sortColumns Then
			Local itemCount = CountGadgetItems(listBox)
			
			If itemCount Then
				Local c, r, sortCount
				Local selectedItem, selectedItemNew = -1
				Local newList:TList = CreateList()
				Local rowCount = CountGadgetItems(listBox)	'total number of rows/items in list
				
				'store sorted rows in temp list
				Repeat
					Local rowNum = 0, rowText$
					If columnHeading[sortColumn].sortDir = 1 Then rowText$ = "zzzzzz"
					
					'find next row in sequence
					For r = 0 To CountGadgetItems(listBox) - 1
						If getListViewItem(r, sortColumn).ToLower() <= rowText$ = columnHeading[sortColumn].sortDir Then
							rowText$ = getListViewItem(r, sortColumn).ToLower()
							rowNum = r
						End If
					Next
					
					'update selected index
					If selectedItemNew = -1 Then
						 selectedItem = SelectedGadgetItem(listBox)
						If selectedItem = rowNum Then selectedItemNew = sortCount
					End If
					
					'create an temp array to store row data
					Local listRow$[curColumn+1]
					
					'copy this row to new list
					For c = 0 To curColumn
						listRow$[c] = getListViewItem(rowNum, c)
					Next
					
					newList.AddLast listRow$
					
					'remove row from original list & update counter
					listBox.RemoveItem rowNum
					sortCount:+ 1
				Until sortCount = rowCount
				
				'copy sorted data to new list
				For Local row$[] = EachIn newList
					addListViewItem(row$)
				Next
				
				'change sort direction for this column
				If columnHeading[sortColumn].sortDir = 1 Then
					columnHeading[sortColumn].sortDir = 0
				Else
					columnHeading[sortColumn].sortDir = 1
				End If
				
				'reselect item
				If selectedItemNew >= 0 Then SelectGadgetItem listBox, selectedItemNew
			End If
		End If
	End Method
	
	
	'------------------------------------------------------------------------------
	Method sortEnable()
	'------------------------------------------------------------------------------
		sortColumns = True
	End Method
	
	
	'------------------------------------------------------------------------------
	Method sortDisable()
	'------------------------------------------------------------------------------
		sortColumns = False
	End Method
	
	
	'------------------------------------------------------------------------------
	Function update()
	'call on EVENT_WINDOWSIZE to reset width of first column
	'------------------------------------------------------------------------------
		For Local newListView:TListView = EachIn gadgetList
			newListView.columnWidth
		Next
	End Function
	
	
	'------------------------------------------------------------------------------
	Function NewListProc:Int(hWnd:Int, Msg:Int, wParam:Int, lParam) "win32"
	'------------------------------------------------------------------------------
		Const HDN_ITEMCLICKW = -322
		
		If Msg = WM_NOTIFY Then
			Local NotifyMess:HD_NOTIFY = New HD_NOTIFY
			Local Tstr$
			
			MemCopy( NotifyMess, Byte Ptr lParam, SizeOf(HD_NOTIFY) )
			
			If NotifyMess.code = HDN_ITEMCLICKW Then
				'find gadget that was clicked & sort it
				For Local newListView:TListView = EachIn gadgetList
					If newListView.listboxHwnd = hWnd Then newListView.sort NotifyMess.iitem
				Next
			End If
		End If
		
		If oldListProc <>0 Then Return CallWindowProcW(Byte Ptr oldListProc, hWnd, Msg, wParam, lParam)
	End Function

End Type


'==============================================================================
Type TColumnHeading
'used by TListView
'==============================================================================
	Field width
	Field sortDir = 1
EndType


'==============================================================================
Type HD_NOTIFY 
'used by TListView
'==============================================================================
	Field hwndFrom
	Field idFrom
	Field code
	Field iItem
	Field iButton
	Field pitem
EndType