Editing cells in ListIcons (updated for Vista)

Share your advanced PureBasic knowledge/code with the community.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Editing cells in ListIcons (updated for Vista)

Post by srod »

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

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.
Last edited by srod on Fri Oct 02, 2009 10:21 am, edited 7 times in total.
I may look like a mule, but I'm not a complete ass.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Example:

Code: Select all

XIncludeFile "Edit ListIcon.pbi"
DisableExplicit

LoadFont(1, "Arial",10)

If OpenWindow(0, 100, 100, 600, 600, "ListIcon Example", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  If CreateGadgetList(WindowID(0))
    ListIconGadget(1, 5, 5, 490, 390, "", 0, #PB_ListIcon_GridLines)
    SetGadgetFont(1, FontID(1))
    For i = 1 To 30
      AddGadgetColumn(1, i, "Col "+Str(i), 100)
    Next
    For row = 1 To 100
      AddGadgetItem(1,-1,"")
      For col = 2 To 10
        SetGadgetItemText(1, row-1, "row "+Str(row-1)+", col "+Str(col), col)
      Next
    Next

    SetListIconEditable(1)
;    EditCell(1, 19, 5)
    Repeat
      Event = WaitWindowEvent()
    Until Event = #PB_Event_CloseWindow
  EndIf
EndIf
Last edited by srod on Tue Jul 08, 2008 1:26 pm, edited 1 time in total.
I may look like a mule, but I'm not a complete ass.
User avatar
Hroudtwolf
Addict
Addict
Posts: 803
Joined: Sat Feb 12, 2005 3:35 am
Location: Germany(Hessen)
Contact:

Post by Hroudtwolf »

Very nice.
Thanks for sharing. :)

A very orderly source.
Dare
Addict
Addict
Posts: 1965
Joined: Mon May 29, 2006 1:01 am
Location: Outback

Post by Dare »

Nifty and learn-from-able.

Thanks.
Dare2 cut down to size
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Hroudtwolf wrote:A very orderly source.
Now there's a title for a book! :)
I may look like a mule, but I'm not a complete ass.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Update: 11/05/07.
Added the command EditCell() to allow a cell to be edited from code.

See first post.
I may look like a mule, but I'm not a complete ass.
mueckerich
User
User
Posts: 22
Joined: Thu Dec 16, 2004 10:36 am
Location: Germany/Allgaeu

Post by mueckerich »

Thanks for sharing, this one will help me a lot in my new project. :D
Believe means you don't know
milan1612
Addict
Addict
Posts: 894
Joined: Thu Apr 05, 2007 12:15 am
Location: Nuremberg, Germany
Contact:

Post by milan1612 »

Hey srod,
how to implement cell editing for a specific column only?
(Would be a nice feature for a "property grid")
thanks...
Windows 7 & PureBasic 4.4
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

milan1612 wrote:Hey srod,
how to implement cell editing for a specific column only?
(Would be a nice feature for a "property grid")
thanks...
For that, you'll have to roll your sleeves up and adjust the source! :)

An easy adjustment to make.

I've edited the source in my first post to show you how to adjust the code to restrict which cells can be edited etc. You only need to look in the _LIEListProc() procedure and add a couple of checks on the row and column indexes of the cell to be edited etc.
I may look like a mule, but I'm not a complete ass.
milan1612
Addict
Addict
Posts: 894
Joined: Thu Apr 05, 2007 12:15 am
Location: Nuremberg, Germany
Contact:

Post by milan1612 »

OK thanks, I'll take a look. I was just too lazy... :lol:
Windows 7 & PureBasic 4.4
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

milan1612 wrote:OK thanks, I'll take a look. I was just too lazy... :lol:
:)
I may look like a mule, but I'm not a complete ass.
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Post by rsts »

Very impressive code. I believe there's lots to learn from this.

Many thanks for sharing.

cheers
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

You're welcome. 8)

As I say I was just wondering if editing cells in this manner was possible. Of course, the method is equivalent to superimposing an edit control over the cell to be edited ourselves etc, but windows, through the #LVS_EDITLABELS style, takes some of the load off! It does mean, however, that this method is not as flexible as superimposing an edit control ourselves, but it should suffice for many applications.
I may look like a mule, but I'm not a complete ass.
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

I like how you say just "tinkering" and then pump out a large chunk of complex code :) Nice job.

One thing I noticed - Fred snuck in GadgetType() at some point and I had no idea. That sneaky...

Also, not sure why my name was mentioned? I don't think I ever had a method of editing listicon cells.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Xombie wrote:I like how you say just "tinkering" and then pump out a large chunk of complex code :) Nice job.
Wait until you see what happens when I claim to be working on a complex piece of code - I churn out a piece of s*it! :wink:
One thing I noticed - Fred snuck in GadgetType() at some point and I had no idea. That sneaky...
Aye, in the past I've used GetClassName_() etc, but this way is nice.
Also, not sure why my name was mentioned? I don't think I ever had a method of editing listicon cells.
Consider yourself removed from the editable ListIcon hall of fame and dumped into the hall of shame! :D

Actually, I think that xgrid warrants a sepcial invite into the hall of fame. :wink:
I may look like a mule, but I'm not a complete ass.
Post Reply