Every widget can have private-data, but it is less commonly known that each list-item in a selection list can have its own private data.
The itemdata for a list-item is of type integer, or actually a DWORD.
Perhaps it is best to think of a selection list as a container of list-items, where each list-item is uniquely identified by a SelIndex. Each list-item contains two data members: the (visible) character string and the itemdata. The itemdata has no meaning to Windows so you can safely use it for storing application-specific information.
Normally I would prefer to use the Progress 9 feature of LIST-ITEM-PAIRS instead of this API solution. But LIST-ITEM-PAIRS are designed to work only if the LIST-ITEMs (e.g. screen-values) are unique!
The following example populates a selection-list widget with each customer.name. When the user chooses a selection-list item, the customer is fetched and its details are displayed. This would normally not be possible because:
* There is no index on customer.name so it would be slow to FIND CUSTOMER WHERE customer.name=screen-value
* There may be several customers with the same name, so the FIND statement may be ambiguous
The solution is to add an identifying value to each list-item. This example stores RECID(customer) in the itemdata. I know RECID is an obsolete function, but I have used it anyway in this example because RECIDs are unique and compatible with Integers.
{windows.i} PROCEDURE Populate : DEFINE VARIABLE ReturnValue AS INTEGER NO-UNDO. DEFINE VARIABLE lpString AS MEMPTR. DEFINE VARIABLE selindex AS INTEGER NO-UNDO. FOR EACH customer NO-LOCK : SET-SIZE(lpString) = 0. SET-SIZE(lpString) = LENGTH(TRIM(customer.NAME)) + 1. PUT-STRING(lpString,1) = TRIM(customer.NAME). RUN SendMessageA IN hpApi (select-1:HWND IN FRAME {&frame-name}, 384, /* LB_ADSTRING */ 0, GET-POINTER-VALUE(lpString), OUTPUT selIndex). RUN SendMessageA IN hpApi (select-1:HWND IN FRAME {&frame-name}, 410, /* LB_SETITEMDATA */ SelIndex, INTEGER(RECID(customer)), OUTPUT ReturnValue). END. SET-SIZE(lpString)=0. END PROCEDURE.
The first call to SendMessage adds a text to the selection list, so it does the same as select-1:ADD-LAST(TRIM(customer.name)). However it also returns a SelIndex, which is the unique and constant index number for the new list-item. This SelIndex is used in the second call to SendMessage.
The second call to SendMessage adds an integer data value to the list-item.
{windows.i} ON VALUE-CHANGED OF SELECT-1 IN FRAME DEFAULT-FRAME DO: DEFINE VARIABLE SelIndex AS INTEGER NO-UNDO. DEFINE VARIABLE ReturnValue AS INTEGER NO-UNDO. RUN SendMessageA (select-1:HWND, 392, /* LB_GETCURSEL */ 0, 0, OUTPUT SelIndex). RUN SendMessageA (select-1:HWND, 409, /* LB_GETITEMDATA */ SelIndex, 0, OUTPUT ReturnValue). FIND customer WHERE RECID(customer)=ReturnValue NO-LOCK NO-ERROR. IF AVAIL customer THEN DO: DISPLAY cust-num NAME city country WITH FRAME {&frame-name}. END. END.
Instead of looking at the screen-value I call SendMessage to get the unique SelIndex, and call SendMessage again to get the value of the item-data connected to that SelIndex. Because this item-data value was actually a RECID, I can now identify the customer and display its details.
The SelIndex value for a list-item never changes even when list-items are deleted or inserted, whether or not the selection-list is sorted.
Instead of storing an Integer value in Itemdata, you can also store a POINTER-VALUE pointing to some string or structure. However when you do this you will have to implement some kind of destructor.