Editing cells in ListIcons (updated for Vista)
Posted: Fri May 11, 2007 2:31 am
Update: 08/07/08. Vista compatible.
Getting this to run on Vista required a couple of dirty hacks as we are doing something here which Windows (especially Vista) is set up to prevent!
In fact, earlier version of Windows are also set up to prevent us moving the edit control reserved for editing labels (column 0) in order to edit other cells - it's just that the prevention fails! Not on Vista (with XP themes enabled) however!
I'll not bore you with the details of the evil hacks - suffice to say that I have tested on XP and Vista and everything seems okay. I am suspicious that the edit control alignment will be out on Win Server 2003, but this is easily fixed if it proves to be the case.
Code is below.
===================================
Update: 31/07/07.
ListIcon now scrolls automatically if the user double-clicks a partially visible cell to edit it etc.
===================================
Update: 11/05/07.
Added the command EditCell() to allow a cell to be edited from code.
===================================
Hi,
something I've been tinkering with for the last couple of hours, just to see if it could be done!
and which adds yet another method of editing cells in a listicon to the collection from myself, netmaestro, Xombie, Einander,...
It works as follows.
Basically, a Windows list view control allows you to edit the labels in column zero if you set the #LVS_EDITLABLES style and, at a minimum, process the #LVN_ENDLABELEDIT message. What my library does on intercepting a double-click on a valid cell, is instruct Windows to edit the corresponding label in column 0, but before Windows has a chance to position the edit control used to actually edit the label, the library manages to grab a hold of the control and reposition it over the selected cell etc. On completion of the editing process, I just switch the text in the cell for that in the edit control etc.
Simple and quite effective.
It's not perfect, but then this is nothing more than a cheap and cheerful way of editing cells!
Edit ListIcon.pbi
NOTE that for this to work, you must blank out column zero by setting it's width to zero. The library automatically prevents the user from resizing this column.
Getting this to run on Vista required a couple of dirty hacks as we are doing something here which Windows (especially Vista) is set up to prevent!

I'll not bore you with the details of the evil hacks - suffice to say that I have tested on XP and Vista and everything seems okay. I am suspicious that the edit control alignment will be out on Win Server 2003, but this is easily fixed if it proves to be the case.
Code is below.
===================================
Update: 31/07/07.
ListIcon now scrolls automatically if the user double-clicks a partially visible cell to edit it etc.
===================================
Update: 11/05/07.
Added the command EditCell() to allow a cell to be edited from code.
===================================
Hi,
something I've been tinkering with for the last couple of hours, just to see if it could be done!

It works as follows.
Basically, a Windows list view control allows you to edit the labels in column zero if you set the #LVS_EDITLABLES style and, at a minimum, process the #LVN_ENDLABELEDIT message. What my library does on intercepting a double-click on a valid cell, is instruct Windows to edit the corresponding label in column 0, but before Windows has a chance to position the edit control used to actually edit the label, the library manages to grab a hold of the control and reposition it over the selected cell etc. On completion of the editing process, I just switch the text in the cell for that in the edit control etc.
Simple and quite effective.

It's not perfect, but then this is nothing more than a cheap and cheerful way of editing cells!
Edit ListIcon.pbi
Code: Select all
;'Edit ListIcon'.
;-----------------
; Stephen Rodriguez.
; Created with Purebasic 4.02 for Windows (date: May 2007).
; Maintained with PB 4.4.
; Platforms: Windows (tested on XP and Vista 32-bit).
; Licence: DAYLike
; (Do As You Like with it! - No Warranties!)
; A credit to myself, whilst nice, is not absolutely necessary.
;*******************************************************************************************
;NOTES.
;------
; 1) Register a listicon gadget to have editable cells by using the command SetListIconEditable(listID).
; You MUST set up such listicons to have a column zero of zero width.
; 2) Cells are made editable by intercepting double-clicks, setting the #LVS_EDITLABELS style,
; repositioning the edit control which Windows uses to edit the labels in column zero and copying
; the resulting text to the listicon cell.
; 3) Cells can also be edited by means of the command EditCell().
;*******************************************************************************************
#LVM_SUBITEMHITTEST = #LVM_FIRST+57
#LVM_GETSUBITEMRECT = #LVM_FIRST+56
#LVM_GETHEADER = #LVM_FIRST+31
#HDI_ORDER = $80
#EC_RIGHTMARGIN = 2
EnableExplicit
CompilerIf Defined(HDITEM, #PB_Structure) = 0
Structure HDITEM
mask.l
cxy.l
pszText.i
hbm.l
cchTextMax.l
fmt.l
lParam.i
iImage.l
iOrder.l
EndStructure
CompilerEndIf
Structure _LIEdit
listOldProc.i
editHwnd.i
item.i
subitem.i
x.i
y.i
cx.i
cy.i
osVersion.b
blnIsXPThemes.b
EndStructure
Structure _LIEditGlobals
osVersion.b
blnIsXPThemes.b
EndStructure
Declare.i SetListIconEditable(listID)
Declare _LIEEditCell(*liedit._LIEdit, hWnd)
Declare.i _LIEwinProc(hWnd, uMsg, wParam, lParam)
Declare.i _LIEListProc(hWnd, uMsg, wParam, lParam)
Declare.i _LIEeditProc(hWnd, uMsg, wParam, lParam)
Global _LIEditGlobals._LIEditGlobals
;Returns zero if an error.
Procedure.i SetListIconEditable(listID)
Protected result, parenthWnd, *mem._LIEdit, hWnd, dlv.DLLVERSIONINFO, func, lib
;Set globals if appropriate.
If _LIEditGlobals\osVersion = 0
_LIEditGlobals\osVersion = OSVersion()
dlv\cbSize=SizeOf(DLLVERSIONINFO)
lib=OpenLibrary(#PB_Any,"comctl32.dll")
If lib
func=GetFunction(lib,"DllGetVersion")
If func
CallFunctionFast(func, dlv)
If dlv\dwMajorVersion >=6
_LIEditGlobals\blnIsXPThemes = #True
EndIf
EndIf
CloseLibrary(lib)
EndIf
EndIf
;Check that listID references a valid listicon.
If IsGadget(listID) And GadgetType(listID)=#PB_GadgetType_ListIcon
hWnd = GadgetID(listID)
;Is the listicon already registered?
If GetProp_(hWnd, "_LIEdit")=0 ;No!
;Allocate enough memory for a _LIEdit structure.
*mem=AllocateMemory(SizeOf(_LIEdit))
If *mem
SetWindowLong_(hWnd, #GWL_STYLE, GetWindowLong_(hWnd, #GWL_STYLE)&~#LVS_EDITLABELS)
;Set the fields of the _LIEedit structure.
*mem\listOldProc = SetWindowLong_(hWnd, #GWL_WNDPROC, @_LIEListProc())
;Store a pointer to this structure in a window property ofthe listicon.
SetProp_(hWnd, "_LIEdit", *mem)
;Subclass the parent window if not already through another listicon.
parenthWnd=GetParent_(hWnd)
If GetProp_(parenthWnd, "_LIEditOldProc")=0 ;No!
SetProp_(parenthWnd, "_LIEditOldProc", SetWindowLong_(parenthWnd, #GWL_WNDPROC, @_LIEwinProc()))
EndIf
result=1
EndIf
EndIf
EndIf
ProcedureReturn result
EndProcedure
;Sets the specified cell to be edited.
Procedure EditCell(listID, item, subitem)
Protected hWnd, *liedit._LIEdit, numrows, numcols
;Check that listID references a valid listicon.
If IsGadget(listID) And GadgetType(listID)=#PB_GadgetType_ListIcon
;Check that the listicon is registered as editable.
hWnd = GadgetID(listID)
*liedit = GetProp_(hWnd, "_LIEdit")
If *liedit
;Check parameters are in range.
numrows = CountGadgetItems(listID)
numcols = SendMessage_(SendMessage_(hWnd,#LVM_GETHEADER,0,0), #HDM_GETITEMCOUNT,0,0)
If item>=0 And item < numrows And subitem>0 And subitem < numcols
*liedit\item = item
*liedit\subitem = subitem
SetActiveGadget(listID)
_LIEEditCell(*liedit, hWnd)
EndIf
EndIf
EndIf
EndProcedure
Procedure _LIEEditCell(*liedit._LIEdit, hWnd)
Protected rc.RECT, clientrc.RECT, numCols, headerWnd
Protected Dim cols.l(0), i, blnFoundZeroColumn
Protected hdi.HDITEM
;Vista themes requires a cheat because it automatically scrolls the listicon to bring the item being edited
;into view.
If _LIEditGlobals\osVersion >= #PB_OS_Windows_Vista And _LIEditGlobals\blnIsXPThemes
headerWnd = SendMessage_(hWnd,#LVM_GETHEADER,0,0)
numCols = SendMessage_(headerWnd, #HDM_GETITEMCOUNT,0,0)
Dim cols.l(numCols-1)
SendMessage_(hWnd, #LVM_GETCOLUMNORDERARRAY, numCols, @cols())
For i = 0 To numcols-1
If cols(i)
If cols(i) = *liedit\subItem
Break
EndIf
Else
blnFoundZeroColumn = 1
EndIf
Next
i-blnFoundZeroColumn
Dim cols(0)
With hdi
\mask = #HDI_ORDER
\iOrder = i
EndWith
SendMessage_(headerWnd, #HDM_SETITEM, 0, hdi)
EndIf
;Scroll the listicon if the clicked cell is not entirely visible
;*****IF YOU WISH TO RESTRICT WHICH CELLS CAN BE EDITED, THEN PERFORM THE NECESSARY CHECKS HERE
;*****ON THE VALUES OF *liedit\item and *liedit\subitem (WHICH INDICATE WHICH CELL IS ABOUT
;*****TO BE EDITED) AND RUN THE FOLLOWING LINES FOR THOSE CELLS WHICH ARE TO BE EDITED.
rc\top = *liedit\subitem
rc\left = #LVIR_BOUNDS
SendMessage_(hWnd, #LVM_GETSUBITEMRECT, *liedit\item, rc)
GetClientRect_(hWnd, clientrc)
If rc\left < 0 Or (rc\right-rc\left)>=clientrc\right
SendMessage_(hWnd, #LVM_SCROLL,rc\left,0)
Else
If rc\right > clientrc\right
SendMessage_(hWnd, #LVM_SCROLL,rc\right-clientrc\right,0)
EndIf
EndIf
SetWindowLong_(hWnd, #GWL_STYLE, GetWindowLong_(hWnd, #GWL_STYLE)|#LVS_EDITLABELS)
SendMessage_(hWnd, #LVM_EDITLABEL, *liedit\item, 0)
EndProcedure
;Window proc of the ListIcon parent window.
Procedure.i _LIEwinProc(hWnd, uMsg, wParam, lParam)
Protected result, oldwinproc, *nmh. NMHDR, listhWnd, edithWnd, *liedit._LIEdit, *lvd.LV_DISPINFO, rc.RECT
Protected hdi.HDITEM, headerWnd
Static celltext$
;Retrieve the address of the old proc.
oldwinproc = GetProp_(hWnd, "_LIEditOldProc")
Select uMsg
Case #WM_NOTIFY
*nmh=lParam
Select *nmh\code
Case #LVN_BEGINLABELEDIT
listhWnd = *nmh\hwndFrom
;Retrieve the address of the LIEdit structure.
*liedit = GetProp_(listhWnd, "_LIEdit")
If *liedit ;Good to go!
*liedit\editHwnd=0
;Get the handle of the edit control used to edit the label.
edithWnd = SendMessage_(listhWnd, #LVM_GETEDITCONTROL,0,0)
;Subclass the edit control.
SetProp_(edithWnd, "_LIEditOldProc", SetWindowLong_(edithWnd, #GWL_WNDPROC, @_LIEeditProc()))
;Set text.
celltext$=GetGadgetItemText(*nmh\idFrom, *liedit\item, *liedit\subitem)
SendMessage_(edithWnd, #WM_SETTEXT, 0, celltext$)
SetGadgetItemText(*nmh\idFrom, *liedit\item, "",*liedit\subitem)
;Get bounding rectangle.
rc\top = *liedit\subitem
rc\left = #LVIR_BOUNDS
SendMessage_(listhWnd, #LVM_GETSUBITEMRECT, *liedit\item, rc)
*liedit\x=rc\left
*liedit\y=rc\top
*liedit\cx=SendMessage_(listhWnd, #LVM_GETCOLUMNWIDTH, *liedit\subitem,0)
*liedit\cy=rc\bottom-rc\top
EndIf
Case #LVN_ENDLABELEDIT
listhWnd = *nmh\hwndFrom
;Retrieve the address of the LIEdit structure.
*liedit = GetProp_(listhWnd, "_LIEdit")
If *liedit ;Good to go!
Debug 2
*lvd = lParam
If *lvd\item\pszText
SetGadgetItemText(*nmh\idFrom, *liedit\item, PeekS(*lvd\item\pszText), *liedit\subitem)
Else
SetGadgetItemText(*nmh\idFrom, *liedit\item, celltext$, *liedit\subitem)
EndIf
SetWindowLong_(listhWnd, #GWL_STYLE, GetWindowLong_(listhWnd, #GWL_STYLE)&~#LVS_EDITLABELS)
If _LIEditGlobals\osVersion >= #PB_OS_Windows_Vista And _LIEditGlobals\blnIsXPThemes
With hdi
\mask = #HDI_ORDER
\iOrder = 0
EndWith
headerWnd = SendMessage_(listhWnd,#LVM_GETHEADER,0,0)
SendMessage_(headerWnd, #HDM_SETITEM, 0, hdi)
EndIf
EndIf
Default
result=CallWindowProc_(oldwinproc, hWnd, uMsg, wParam, lParam)
EndSelect
Case #WM_NCDESTROY
result=CallWindowProc_(oldwinproc, hWnd, uMsg, wParam, lParam)
RemoveProp_(hWnd, "_LIEditOldProc")
Default
result=CallWindowProc_(oldwinproc, hWnd, uMsg, wParam, lParam)
EndSelect
ProcedureReturn result
EndProcedure
;Window proc of the ListIcon.
Procedure.i _LIEListProc(hWnd, uMsg, wParam, lParam)
Protected result, *liedit._LIEdit, PInfo.LVHITTESTINFO, *nmHEADER.HD_NOTIFY
;Retrieve the address of the LIEdit structure.
*liedit = GetProp_(hWnd, "_LIEdit")
Select uMsg
Case #WM_NOTIFY
*nmHEADER = lParam
Select *nmHEADER\hdr\code
Case #HDN_BEGINTRACK, #HDN_BEGINTRACKW ;Prevent column 0 from being resized.
If *nmHEADER\iItem=0
result=1
EndIf
Case #HDN_ENDTRACK, #HDN_ENDTRACKW
InvalidateRect_(hWnd,0,1)
Default
result=CallWindowProc_(*liedit\listOldProc, hWnd, uMsg, wParam, lParam)
EndSelect
Case #WM_LBUTTONDBLCLK
;Identify the clicked item
PInfo\pt\x = lParam&$ffff
PInfo\pt\y = (lParam>>16)&$ffff
SendMessage_(hwnd, #LVM_SUBITEMHITTEST, 0, PInfo)
If PInfo\iItem <> -1 ;A valid cell was clicked.
*liedit\item = PInfo\iItem
*liedit\subitem = PInfo\iSubItem
_LIEEditCell(*liedit, hWnd)
EndIf
Case #WM_NCDESTROY
result=CallWindowProc_(*liedit\listOldProc, hWnd, uMsg, wParam, lParam)
RemoveProp_(hWnd, "_LIEdit")
FreeMemory(*liedit)
Default
result=CallWindowProc_(*liedit\listOldProc, hWnd, uMsg, wParam, lParam)
EndSelect
ProcedureReturn result
EndProcedure
;Window proc of the edit control.
Procedure.i _LIEeditProc(hWnd, uMsg, wParam, lParam)
Protected result, oldwinproc, *liedit._LIEdit, *wpos.WINDOWPOS
;Retrieve the address of the old proc.
oldwinproc = GetProp_(hWnd, "_LIEditOldProc")
;Retrieve the address of the LIEdit structure.
*liedit = GetProp_(GetParent_(hWnd), "_LIEdit")
Select uMsg
Case #WM_ERASEBKGND
;A hack in order to clear the default selection of characters.
result=CallWindowProc_(oldwinproc, hWnd, uMsg, wParam, lParam)
If *liedit\editHwnd=0
*liedit\editHwnd = hWnd
;Set margins.
SendMessage_(hWnd, #EM_SETMARGINS, #EC_LEFTMARGIN|#EC_RIGHTMARGIN, 4)
SendMessage_(hWnd, #EM_SETSEL, -1,0)
EndIf
Case #WM_WINDOWPOSCHANGING
*wpos=lParam
*wpos\cx=*liedit\cx ;Comment this line to get an edit control which grows with the text.
*wpos\x=*liedit\x
If _LIEditGlobals\osVersion >= #PB_OS_Windows_Vista And _LIEditGlobals\blnIsXPThemes
*wpos\cy=*liedit\cy
*wpos\y=*liedit\y
Else
*wpos\cy=*liedit\cy+3
*wpos\y=*liedit\y-2
EndIf
result=CallWindowProc_(oldwinproc, hWnd, uMsg, wParam, lParam)
Case #WM_NCDESTROY
result=CallWindowProc_(oldwinproc, hWnd, uMsg, wParam, lParam)
RemoveProp_(hWnd, "_LIEditOldProc")
;InvalidateRect_(GetParent_(hWnd),0,0)
Default
result=CallWindowProc_(oldwinproc, hWnd, uMsg, wParam, lParam)
EndSelect
ProcedureReturn result
EndProcedure
NOTE that for this to work, you must blank out column zero by setting it's width to zero. The library automatically prevents the user from resizing this column.