Selection-list with item-data

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.

Notes

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.