Introduzione alle API Win32

Capitolo 44: messaggi ai LISTBOX (2)

La selezione corrente del listbox (un singolo elemento, ovvero un numero variabile di elementi, secondo lo stile del listbox) viene normalmente stabilita secondo le interazioni dell'utente, ma può anche essere diretta da programma.

Il messaggio LB_SETCURSEL (con l'indice dell'elemento da selezionare, come al solito, in wParam), che funziona solo con liste a selezione singola, seleziona l'elemento indicato (e, se non era fra quelli nella zona visibile della lista, fa scorrere la lista stessa in modo che lo diventi). Alternativamente, si può usare anche LB_SELECTSTRING, che praticamente congiunge le funzioni di LB_FINDSTRING e LB_SETCURSEL (esso ha gli stessi parametri e valore di ritorno di LB_FINDSTRING).

Per liste a selezione multipla, invece, si usa LB_SETSEL: in wParam, 0 per togliere la selezione, 1 per metterla; in lParam, l'indice dell'elemento cui metterla o toglierla (ovvero, -1 per toglierla o metterla a tutti gli elementi simultaneamente). (LB_SELITEMRANGE e LB_SELITEMRANGEEX sono delle piccole ottimizzazioni che consentono di mettere o togliere la selezione a un gruppo contiguo di elementi in un sol colpo).

Per chiedere alla listbox informazioni sulla sua selezione, se la listbox è a selezione singola, si usa il messaggio LB_GETCURSEL, che ritorna l'indice dell'unico elemento selezionato (LB_ERR, se nessun elemento è selezionato).

Per liste a selezione multipla, invece, si usa LB_GETSELCOUNT per ottenere il numero totale di elementi selezionati, e LB_GETSELITEMS per avere l'elenco degli elementi stessi (passando, in wParam, il numero massimo che si è preparati ad accettare, e in lParam l'indice di un nostro buffer, un array di interi, che verrà riempito con l'informazione richiesta); il valore di ritorno è il numero di interi posti nel buffer. Alternativamente, LB_GETSEL, con l'indice dell'elemento di interesse in wParam, permette di sapere se un dato elemento è selezionato o meno (se non lo è, il valore di ritorno sarà zero).

Anche lo stato dello scorrimento (scroll) di una listbox è normalmente modificato dalle azioni dell'utente (in questo caso, dalla sua interazione con la scroll-bar del listbox); anche qui, naturalmente, lo si può stabilire anche da programma, spedendo al listbox il messaggio LB_SETTOPINDEX, avendo in wParam l'indice dell'elemento che si vuole mostrare in cima alla lista. Per chiedere quale sia l'elemento attualmente in cima alla lista, si userà il messaggio LB_GETTOPINDEX, che naturalmente restituisce questa informazione come valore di ritorno.

Per sapere se l'elemento con un certo indice è attualmente visualizzato (e, se sì, a quali coordinate entro l'area del controllo listbox) si usa il messaggio LB_GETITEMRECT, passando, in wParam, l'indice dell'elemento, e, in lParam, l'indirizzo di una nostra struttura di tipo RECT che verrà riempita con le informazioni richieste.

Il valore di ritorno da questo messaggio sarà 1 se l'elemento è visualizzato, 0 in caso contrario (cioè se l'elemento è al di fuori della zona dell'elenco attualmente in vista), -1 (cioè LB_ERR) in caso di errore; nel RECT passato, vengono comunque poste le coordinate-cliente che sarebbero usate se l'elemento fosse mostrato, anche se esse sono in realtà fuori dall'area attualmente in vista (ad esempio, campi top e bottom negativi, se l'elemento è "sopra" al primo attualmente visualizzato). Si noti che la "larghezza" indicata per la visualizzazione corrisponde sempre e comunque all'intera larghezza della listbox (o della sua colonna in cui si trova l'elemento, per listbox a molteplici colonne), anche se l'elemento è in realtà più stretto, ovvero più largo.

Notiamo che, con questi messaggi, abbiamo quanto ci serve per garantire che l'ultima stringa appena aggiunta in coda alla lista sia sempre visualizzata: basterà fare qualcosa tipo...

int idx = SendMessage(hlist, LB_ADDSTRING, 0, (LPARAM)buf); int shown; do { RECT rr; shown = SendMessage(hlist, LB_GETITEMRECT, idx, (LPARAM)&rr); if(!shown) { int itop = SendMessage(hlist, LB_GETTOPINDEX, 0, 0); SendMessage(hlist, LB_SETTOPINDEX, itop+1, 0); } } while(!shown); Questo provoca un lento scorrimento della lista, se era stata fatta scorrere verso la cima e gli elementi contenuti sono tanti. Per avere un effetto grafico più rapido, possiamo circondare questo brano di codice fra due messaggi WM_SETREDRAW, che, come abbiamo accennato, fanno commutare il bit di stile LBS_NOREDRAW della lista. Per garantire il "ridisegno" finale, occorrerà anche una chiamata all'API InvalidateRect alla fine di questa sequenza.

In alternativa, naturalmente, possiamo scegliere di calcolare quale dovrà essere il primo elemento della lista, al fine di garantire che il nuovo elemento appena aggiunto venga visualizzato all'ultimo posto. Per fare questo senza perdita di generalità, bisogna calcolare anche quanti elementi la lista sia in grado di mostrare allo stesso tempo (questo, naturalmente, è abbastanza più difficile per liste owner-drawn con elementi ad altezza variable, caso nel quale vi suggerisco decisamente l'approccio più semplice che ho appena delineato!). Si può partire dal "top-index", verificando quanti elementi a cominciare da esso siano visibili. Questo calcolo, naturalmente, occorre farlo solo una volta per ogni listbox (se non se ne modifica la dimensione), e si può poi registrare il risultato come proprietà della finestra del controllo (con la solita API SetProp, che già abbiamo visto in precedenza).

Questa ottimizzazione è un esercizio molto istruttivo, ed essa può sicuramente venire raccomandata al lettore, anche perchè da questo, se ben svolto, con la dovuta attenzione alla generalità, risulterà un altro utile "mattoncino" di codice riusabile da aggiungere alla propria "piccola libreria personale". Noi, qui, però, non tratteremo più oltre di questa possibilità; pensiamo, infatti, che il primo difettuccio che riscontrammo nel programmillo di "output di debug" possa con queste considerazioni ritenersi già rimediato.

Rimuovere il secondo difettuccio, quello relativo allo scrolling orizzontale, non sarà proprio altrettanto banale, e quindi rimandiamo questo compito al prossimo capitolo.


Capitolo 43: messaggi ai LISTBOX (1)
Capitolo 45: scorrimento orizzontale
Elenco dei capitoli