ListBox - Tips and Tricks       door Borje Hagsten
==================================================
Windows heeft vier controles om een lijst te maken 
van punten of  keuzen voor selectie door de 
gebruiker: StandaardlistBox, StandaardcomboBox en 
geavanceerdere ListView en Treeview. We kunnen dat 
ook zelf doen met een Listbox. 
==================================================
De StandaardlistBox is een zeer krachtige controle, 
met een grote reeks van keuzes. In de oudere 
systemen Win95/98/ME, was u beperkt tot 32.736 
karakters per ListBox. Echter, met op 
winNT-Gebaseerde systemen, is de capaciteit 
behoorlijk uitgebreid tot een omvang van een 
32 bits integer.
==================================================
De verwezenlijking, de beschikbare stijlen, de 
berichtberichten en dergelijke, zijn eveneens goed 
uitgelegd in de PowerBASIC Help (PBWin.hlp - 
zie CONTROLE LISTBOX TOEVOEGEN), en in Win32.hlp. 
Vergeet niet om die punten voor een of andere 
werkelijk grote ideen en documentatie zorgvuldig 
te bekijken. Ook, zijn er vele nuttige 
omslagfuncties (als Listbox_SetCurSel) die in het 
dossier Commctrl.inc op uw PowerBASICschijf 
gevonden kunnen worden. Aangezien heel wat 
informatie gemakkelijk beschikbaar is, proberen we 
ons zo veel mogelijk te concentreren op die welke 
daar niet behandeld zijn.
==================================================
Het vervangen van een ListBox item's tekst 
- - - - - - - - - - - - - - - - - - - - - - - - -  
Van alle bevelen beschikbaar voor ListBox, is er 
geen enig bevel om de tekst van een bestaandsdeel 
te vervangen. 
De volgende procedure wordt gecreerd voor 
gemakkelijk gebruik in de DDT-dialogen. 

De eerste parameter is het handvat van de dialoog, 
terwijl de tweede parameter identiteitskaart van de 
controle ListBox is. De derde parameter is het op 
nul-gebaseerde te vervangen puntaantal (m.a.w., het 
eerste punt is 0), en de vierde parameter is 
de vervangingstekst.

'===================================================
FUNCTION Listbox_Replace (BYVAL hDlg AS DWORD, _
                          BYVAL CtrlId AS LONG, _
                          BYVAL Index AS DWORD, _
                          BYVAL txt AS STRING) AS LONG
'--------------------------------------------------------------
' Replace a ListBox item's text
' Example call from inside a dialog CallBack procedure:
' lRes = Listbox_Replace (CBHNDL, %IDC_LISTBOX1, itemNum, "New text")
'------------------------------------------------------------------

  LOCAL lRes AS LONG

  CONTROL SEND hDlg, CtrlId, %LB_DELETESTRING, Index, 0 TO lRes

  IF lRes = %LB_ERR THEN  ' if index was out of range
      FUNCTION = %LB_ERR  ' return %LB_ERR to indicate error
      EXIT FUNCTION       ' and exit function
  END IF

  CONTROL SEND hDlg, CtrlId, %LB_INSERTSTRING, _
                                            Index, STRPTR(txt) TO lRes

  FUNCTION = lRes         ' return the result from %LB_INSERTSTRING
END FUNCTION
'-------------------------------------------------------------



==================================================
Het toevoegen van meerdere punten aan een ListBox 
------------------------------------------------------------------
Het is gewoonlijk het beste om het aantal punten in een ListBox tot 
een of ander redelijk aantal te beperken -- wij willen de gebruiker 
er niet mee overdonderen! Nochtans, zijn er altijd bepaalde 
uitzonderingen. Bijvoorbeeld, als de punten behoorlijk voorafgaand 
aan het tonen worden gesorteerd, wordt de gebruiker niet gedwongen 
om elke entry te lezen om zijn keus te maken.

Jammer genoeg, kan het toevoegen van vele punten aan een 
standaardlistBox enige tijd vergen. Er is een truc om dingen te 
versnellen door middel van pre-allocation van geheugen als u het 
%LB_INITSTORAGE message gebruikt. Microsoft de Hulp zegt dit 
slechts op systemen Win95/98/ME van toepassing is, maar mijn tests 
in WinXP tonen aan dat het eveneens werk op NT systemen. Deze truc 
kan de algemene uitvoeringssnelheid een verhogen.

De volgende procedure toont aan hoe records van een string array 
snel aan een ListBox is toe te voegen. Zelfs als alle records niet 
in n enkele array passen, kunt u prestaties nog verbeteren door in 
te schatten hoeveel zullen worden toegevoegd, en hoeveel geheugen er 
voor nodig is. Een call naar %LB_INITSTORAGE met de veronderstelde 
aantallen  zal nog een grote tijdsbesparing zijn. U kunt de code aan 
uw eigen behoeften aanpassen. Zie ook Win32api.hlp of MSDN voor meer 
info over %LB_INITSTORAGE.

'=====================================================================
FUNCTION Listbox_AddArray (BYVAL hDlg AS DWORD, _
                           BYVAL CtrlId AS LONG, _
                           MyArray() AS STRING) AS LONG
'--------------------------------------------------------------------
' Fill a ListBox with array items
' Pre-allocates memory for increased speed and returns 
' total item count
' Example call: 
' cnt = Listbox_AddArray(CBHNDL, %IDC_LISTBOX1, sArray())
'---------------------------------------------------------------------

  LOCAL c AS LONG, ln AS LONG

  FOR c = LBOUND(MyArray) TO UBOUND(MyArray)  ' calculate memory needs
      ln = ln + LEN(MyArray(c)) + 1           ' +1/each for trailing $NUL
  NEXT

  c = UBOUND(MyArray) - LBOUND(MyArray) + 1   ' total number of items
  CONTROL SEND hDlg, %IDC_LISTBOX1, %LB_INITSTORAGE, c, ln  ' allocate memory

  FOR c = LBOUND(MyArray) TO UBOUND(MyArray)  ' add the items to the LixtBox
      CONTROL SEND hDlg, CtrlId, %LB_ADDSTRING, 0, STRPTR(MyArray(c))
  NEXT

  CONTROL SEND hDlg, CtrlId, %LB_GETCOUNT, 0, 0 TO c  ' get/return item count

  FUNCTION = c
END FUNCTION
'--------------------------------------------------------------------
=====================================================================
OwnerDrawn ListBox 
------------------
Microsoft zorgt voor een methode om tekeningen van alle ListBox items 
met uw eigen code te behandelen. Dit geeft ons de mogelijkheid om 
prachtige stunts te doen, als het creren van een lijst met beelden, 
Gebruik makend van verschillende fonts en kleuren, rasterlijnen over 
afbeeldingen en veel meer. Een voorbeeld hiervan kan in 
PBWin70\Samples\DDT\BmpList\ worden gevonden, die laat zien hoe u 
een ownerdrawn lijst moet gebruiken om beelden te presenteren.

Aangezien in dit artikel reeds het onderwerp van het toevoegen van 
meerdere items aan een ListBox is besproken, kan het misschien 
interessant zijn om te leren hoe een standaardlistBox als virtuele 
lijst te gebruiken is.

Een virtuele lijst heeft geen records - in plaats daarvan gebruiken 
wij een globale array en vertellen ListBox hoeveel records het bevat. 
ListBox zet enkel lege ruimte en de verticaal scrollbar voor de 
records op. Het voordeel van deze methode is dat ListBox geen 
geheugen moeten toewijzen en u een onmiddellijke mening krijgt, 
geen kwestie hoe groot de lijst! Natuurlijk, zijn er sommige 
systeemgrenzen voor Win95/98/ME, zoals vroeger verklaard.

Het volgende voorbeeld van de DDT toont aan hoe een 
standaardlistBox een globale serie kan tonen.     
Wanneer de serie wordt gevuld, wordt %LB_SETCOUNT geroepen om 
ListBox de punttelling te vertellen. Alle zichtbare punten worden 
n voor n getrokken, onder %WM_DRAWITEM, waar u de 
verschijning kunt in menig opzicht manipuleren.

Bijvoorbeeld, in de vraag DrawText hieronder, 
zou u %DT_LEFT in %DT_CENTER voor gecentreerde punten, 
of in %DT_RIGHT voor juist-alignedment kunnen veranderen. 
Zie ook Win32api.hlp of MSDN voor meer info op 
DrawText en andere aangewezen API vraag.

'====================================================================
' OwnerDrawn Virtual ListBox, DDT example
'--------------------------------------------------------------------
#COMPILE EXE
#INCLUDE "WIN32API.INC"
'---------------------------------
%IDC_LISTBOX1 = 101
'---------------------------------
GLOBAL sArray() AS STRING
'--------------------------------------------------------------------
' Program entrance
'--------------------------------------------------------------------
FUNCTION PBMAIN
  LOCAL hDlg AS DWORD
 
  DIALOG NEW 0, "Virtual ListBox", , , 120, 150, %WS_CAPTION OR %WS_SYSMENU TO hDlg
 
  CONTROL ADD LISTBOX, hDlg, %IDC_LISTBOX1, , 5, 5, 110, 124, _
                             %WS_CHILD OR  _
                             %WS_TABSTOP OR _
                             %WS_VSCROLL OR _
                             %LBS_OWNERDRAWFIXED OR _
                             %LBS_NODATA, %WS_EX_CLIENTEDGE

  CONTROL ADD BUTTON, hDlg, %IDCANCEL, "&Close", 64, 131, 50, 14

  DIALOG SHOW MODAL hDlg, CALL DlgProc
END FUNCTION

'--------------------------------------------------------------------
' Main dialog's callback procedure
'--------------------------------------------------------------------
CALLBACK FUNCTION DlgProc
 
  LOCAL c AS LONG, lpDis AS DRAWITEMSTRUCT PTR

  SELECT CASE CBMSG
    CASE %WM_INITDIALOG
      '---------------------------------------------------------
      ' Dim the global array and set some text to its items,
      ' then tell the ListBox how many items the array contains.
      '---------------------------------------------------------
      REDIM sArray(32000)
 
      FOR c = 0 TO UBOUND(sArray)
        sArray(c) = "This is item" + STR$(c)
      NEXT
 
      CONTROL SEND CBHNDL, %IDC_LISTBOX1, %LB_SETCOUNT, UBOUND(sArray) + 1, 0
      '---------------------------------------------------------
 
    CASE %WM_COMMAND
      SELECT CASE CBCTL
        CASE %IDCANCEL
          IF CBCTLMSG = %BN_CLICKED OR CBCTLMSG = 1 THEN
             DIALOG END CBHNDL
          END IF
 
        CASE %IDC_LISTBOX1
          IF CBCTLMSG = %LBN_SELCHANGE THEN
            ' On double-click, get current selection
            CONTROL SEND CBHNDL, %IDC_LISTBOX1, %LB_GETCURSEL, 0, 0 TO c
 
            ' and show related text in dialog's caption
            DIALOG SET TEXT CBHNDL, sArray(c)

          ELSEIF CBCTLMSG = %LBN_DBLCLK THEN
                  ' do whatever on double-click..
          END IF
 
          END SELECT

     CASE %WM_DRAWITEM
       IF CBWPARAM <> %IDC_LISTBOX1 THEN EXIT FUNCTION ' ensure it's our list
       lpdis = CBLPARAM
       IF @lpdis.itemID = &HFFFFFFFF THEN EXIT FUNCTION  ' empty list
 
        SELECT CASE @lpdis.itemAction

          CASE %ODA_DRAWENTIRE, %ODA_SELECT
            ' DRAW THE BACKGROUND AND SET COLORS TO USE
            IF (@lpdis.itemState AND %ODS_SELECTED) = 0 THEN ' unselec ted
              FillRect @lpdis.hDC, @lpdis.rcItem, GetSysColorBrush(%COLOR_WINDOW)
              SetBkColor @lpdis.hDC, GetSysColor(%COLOR_WINDOW)
              SetTextColor @lpdis.hDC, GetSysColor(%COLOR_WINDOWTEXT)
            ELSE  ' selected item
              FillRect @lpdis.hDC, @lpdis.rcItem, GetSysColorBrush(%COLOR_HIGHLIGHT)
              SetBkColor @lpdis.hDC, GetSysColor(%COLOR_HIGHLIGHT)
              SetTextColor @lpdis.hDC, GetSysColor (%COLOR_HIGHLIGHTTEXT)
              END IF
 
              ' DRAW THE TEXT
              ' hDC is the device context to draw in itemID is current 
              ' item's number rcItem is current item's coordinates    
              ' By decreasing its width, we get a nice text margin
              InflateRect @lpdis.rcItem, -3, 0
              DrawText @lpdis.hDC, BYVAL STRPTR (sArray(@lpdis.itemID)), _
                                                 LEN(sArray(@lpdis.itemID)), _
                                                 @lpdis.rcItem, %DT_LEFT _
                                                 OR %DT_SINGLELINE OR %DT_VCENTER
              FUNCTION = %TRUE
 
           'DRAW FOCUS RECT, IF IN FOCUS..
           CASE %ODA_FOCUS
              CALL DrawFocusRect(@lpdis.hDC, @lpdis.rcItem)
              FUNCTION = %TRUE

        END SELECT

   END SELECT
END FUNCTION
'--------------------------------------------------------------------


