Bottoni, Edit, List Box, Static

Esempio: ctl_one

Abbiamo già utilizzato i bottoni nell’esempio precedente, quindi a questo punto dovreste avere già un pò di familiarità con essi; comunque ho deciso di creare questa apposita sezione che tratta dei controlli per completezza.

Controlli

Una cosa da ricordare a proposito dei controlli è che anch’essi sono semplicemente delle finestre. Come per ogni altra finestra hanno una Window Procedure una Window Class etc… che viene registrata dal sistema. Qualsiasi cosa potete fare con le finestre, potete farla con i controlli.

Messaggi

Come ricorderete dalla precedente discussione sul loop dei messaggi, windows comunica usando i messaggi, vengono inviati per far fare qualcosa al controllo, e quando si verifica un evento sul controllo esso invia indietro un messaggio di notifica. Per i controlli standard ci sta un messaggio WM_COMMAND predefinito, , come abbiamo già visto per i bottoni ed i menu. Per i Common Control di cui parleremo fra poco, ci sta un messaggio WM_NOTIFY.

I messaggi inviati cambiano da controllo a controllo, ognuno di essi ha il proprio set di messaggi Alcune volte lo stesso messaggio viene utilizzato per caratteristiche comuni a più controlli, ma in generale funzioneranno solamente per il controllo per cui sono stati designati. Questo è particolarmente noioso per i messaggi relativi ai controlli listbox e combobox (LB_* e CB_*) che praticamente assolvono le stesse funzioni, ma non sono interscambiabili. E devo ammettere che anche io spesso faccio confusione e li mischio :)

Esistono tuttavia dei messaggi generici, tipo WM_SETTEXT che sono supportati dalla quasi totalità dei controlli. Un controllo è semplicemente una finestra dopotutto.

È possibile inviare i messaggi usando l’API SendMessage(), e usare GetDlgItem() per ricavare l’handle del controllo, oppure si può usare SendDlgItemMessage() che esegue entrambe le operazioni per noi. I risultati di entrambi i metodi sono uguali.

Edits

Uno dei controlli più utilizzati all’interno dell’ambiente windows, è sicuramente l’EDIT control, serve a consentire all’utente di inserire, modificare, copiare, etc… del testo. Windows Notepad è poco più di una normalissima finestra con un grande edit control all’interno.

Questo è il codice usato per interfacciarsi con l’edit control nell’esempio allegato:

    SetDlgItemText(hwnd, IDC_TEXT, "Questa è una stringa.");

Questo è tutto ciò che serve per cambiare il testo contenuto nel controllo (può essere usato praticamente su qualsiasi controllo che ha un valore di testo associato, STATIC, BUTTON e così via).

Ricavare il testo dal controllo è altrettanto semplice, tuttavia richiede un minimo di lavoro in più in confronto all’operazione di settaggio…

    int len = GetWindowTextLength(GetDlgItem(hwnd, IDC_TEXT));
    if(len > 0)
    {
        int i;
        char* buf;

        buf = (char*)GlobalAlloc(GPTR, len + 1);
        GetDlgItemText(hwnd, IDC_TEXT, buf, len + 1);

        //... fa qualcosa con il testo ...

        GlobalFree((HANDLE)buf);
    }

Prima di tutto abbiamo bisogno di allocare della memoria per contenere la stringa dato che non ci viene semplicemente restituito un puntatore ad una stringa già in memoria. Per fare questo, abbiamo prima bisogno di sapere quanta memoria dobbiamo allocare. Non esiste una GetDlgItemTextLength() ma esiste una GetWindowTextLength(), quindi tutto quello che dobbiamo fare è ricavarci l’handle del controllo tramite GetDlgItem() (La creazione della funzione GetDlgItemTextLength() vi viene lasciata come esercizio.)

Ora che abbiamo una lunghezza, possiamo allocare della memoria. In questo esempio ho aggiunto il controllo per assicurarmi che esista del testo da processare, alcune volte è utile aggiungere questo controllo per evitare operazioni aggiuntive, ma tutto questo dipende da voi e da cosa dovete fare. Assumendo che esiste del testo, chiamiamo GlobalAlloc() per allocare della memoria. GlobalAlloc() come potete vedere è equivalente a calloc() la chiamata standard del c usata nella programmazione DOS/UNIX. Essa alloca della memoria, e ne inizializza il contenuto a 0 e restituisce un puntatore a questa memoria. È possibile specificare flag differenti come primo parametro, ma dipendono ovviamente da cosa si va a fare con la memoria allocata. In questo tutorial le flag utilizzate saranno sempre le stesse.

Notate che ho aggiunto 1 alla lunghezza del testo, in due posti, perchè questo? Se avete una conoscenza base del c dovreste intuire che GetWindowTextLength() agisce allo stesso modo di strlen() ossia restituisce il numero di caratteri all’interno del testo senza comprendere il terminatore NULL. Questo vuol dire che se allochiamo la stringa senza aggiungere 1, il testo la riempirà completamente e non sarà e il null terminator finale causerà un overflow di un byte e possibilmentè andrà a sovrascrivere altri dati, causando aprendo le porte così alle cose peggiori. Bisogna stare attenti quando si lavora con le lunghezze delle stringhe in windows, alcune API e messaggi si aspettano delle lunghezze per includere il null ed altre non lo fanno, leggete sempre la documentazione della funzione che andate ad utilizzare.

Se vi ho confuso parlando di terminatori null, fate riferimento a un libro base del C che parli di stringhe.

Finalmente possiamo chiamare GetDlgItemText() per recuperare i contenuti del controllo all’interno del buffer di memoria appena allocato. Questa chiamata si aspetta la grandezza del buffer INCLUSO il terminatore null. Il valore restituito, che è stato ignorato nell’esempio è il numero di caratteri copiati, NON INCLUSO il terminatore NULL…. divertente eh? :)

Quando abbiamo finito con il testo dobbiamo liberare la memoria allocata in modo da non causare leak di memoria che sono sempre una brutta cosa e in alcuni casi possono influenzare il corretto funzionamento del vostro codice. Per fare questo, chiamiamo semplicemente GlobalFree() e gli passiamo il nostro puntatore.

È giusto ricordare che esiste un secondo set di API chiamate LocalAlloc(), LocalFree(), etc… che sono legate alle versioni 16-bit di windows. In Win32, le funzioni Local* e Global* sono identiche ed equivalenti.

Edits con i numeri

Immettere del testo è ok, ma cosa fare se volete che l’utente immetta un numero? È una cosa molto diffusa, e fortunatamente c’e’ un’API che rende il tutto più semplice prendendosi cura dell’allocazione della memoria e della conversione della stringa ad un valore intero.

    BOOL bSuccess;
    int nTimes = GetDlgItemInt(hwnd, IDC_NUMBER, &bSuccess, FALSE);

GetDlgItemInt() funziona in modo simile a GetDlgItemText(), eccetto che invece di copiare una stringa in un buffer, la converte internamente ad intero e ci restituisce il valore. Il terzo parametro è opzionale, e prende un puntatore BOOL; dato che la funzione restituisce 0 in caso di errore non c’è nessun modo per sapere se lo 0 restituito è il valore immesso oppure la segnalazione del fallimento della chiamata. Se per voi 0 va bene lo stesso potete ignorare tranquillamente il parametro.

Uno stile utile per l’edit box, in questo caso è ES_NUMBER, che consente di immettere solo caratteri compresi tra 0 e 9. Ricordate però che anche questo è utile solo in caso avete bisogno di un input di intero positivo, dato che lo stile non supporta l’immissione di - (meno) . (decimale) oppure , (virgola).

List Boxes

Un altro controllo molto usato è la List Box. Questo è l’ultimo controllo standard che tratterò per ora, dato che francamente non c’è molto di interessante, e se voi ancora non vi siete scocciati, io si :)

Aggiungere voci

La prima cosa che vorrete fare con una listbox sarà aggiungerci voci all’interno.

    int index = SendDlgItemMessage(hwnd, IDC_LIST, LB_ADDSTRING, 0,
              (LPARAM)"Ciao!");

Come potete vedere è un’operazione molto semplice. Se la listbox ha lo stile LBS_SORT, la nuova voce verrà aggiunta in ordine alfabetico, altrimenti verrà semplicemente aggiunta alla fine della lista.

Questo messaggio ritorna l’indice della nuova voce e possiamo usarlo per eseguire ulteriori operazioni sulla voce appena inserita, tipo associarlo in una struttura che contiene maggiori informazioni oppure trattarlo come un ID per identificare la voce… Dipende da voi e da cosa dovete e volete fare, come al solito.

    SendDlgItemMessage(hwnd, IDC_LIST, LB_SETITEMDATA, (WPARAM)index,
              (LPARAM)nTimes);

Notifiche

La funzione principale che una listbox assolve è quella di consentire all’utente di selezionare qualcosa da una lista. Ora, alcune volte non ci interessa esattamente quando lo fanno, per esempio se inseriamo un bottone Rimuovi non abbiamo bisogno di conoscere quando cambia la selezione all’interno della listbox, eseguiamo solo il controllo nel momento in cui viene premuto il bottone.

Altre volte, invece, c’e’ bisogno di conoscere qualcosa in più durante l’interazione con l’utente. Per esempio per far visualizzare informazioni differenti a seconda di quale voce viene selezionata. Per fare questo dobbiamo processare i messaggi di notifica che la listbox ci passa. In questo caso siamo interessati a LBN_SELCHANGE, che ci dice quando la selezione corrente viene modificata dall’utente. LBN_SELCHANGE è inviata via WM_COMMAND la differenza con i bottoni è i menu è che mentre questi inviano il WM_COMMAND generalmente in risposta ad un click, una list box invia il WM_COMMAND per svariate ragioni ed abbiamo bisogno di un ulteriore controllo per capire cosa ci vuole comunicare. Il Codice di Notifica è passato come l’HIWORD di wParam, l’altra metà del parametro ci fornisce l’ID selezionato.

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
            case IDC_LIST:
                // È la nostra listbox,
                // controlliamo il codice di notifica
                switch(HIWORD(wParam))
                {
                    case LBN_SELCHANGE:
                        // La selezione è cambiata ed
                        // eseguiamo le operazioni.
                    break;
                }
            break;
            // ... altri controlli
        }
    break;

Recuperare i dati da una ListBox

Ora che sappiamo che la selezione è cambiata, oppure per esplicita richiesta dell’utente, abbiamo bisogno di ricavare la selezione per farci qualcosa di utile.

Nell’esempio ho usato una listbox a selezione multipla, quindi ricavare la lista delle voci selezionate è un pò più complesso. Se fosse stata una listbox a selezione singola avremmo potuto semplicemente usare LB_GETCURSEL per ricavare l’indice della voce.

Prima abbiamo bisogno di conoscere il numero delle voci selezionate, in modo da poter allocare un buffer per salvarci gli indici.

    HWND hList = GetDlgItem(hwnd, IDC_LIST);
    int count = SendMessage(hList, LB_GETSELCOUNT, 0, 0);

Dopodichè allochiamo un buffer basato sul numero di voci e inviamo un LB_GETSELITEMS per riempire l’array.

    int *buf = GlobalAlloc(GPTR, sizeof(int) * count);
    SendMessage(hList, LB_GETSELITEMS, (WPARAM)count, (LPARAM)buf);

    // ... facciamo qualcosa con gl indici

    GlobalFree(buf);

Nell’esempio, buf[0] è il primo indice e così via fino a buf[count - 1].

Una cosa che generalmente viene fatta con la lista di indici, è ricavarne i dati associati per ogni indice e processarli ulteriormente. Questo è altrettanto semplice quanto il settaggio dei dati originali, inviamo semplicemente un altro messaggio.

    int data = SendMessage(hList, LB_GETITEMDATA, (WPARAM)index, 0);

Se i dati fossero stati di tipo diverso da int (un tipo qualsiasi a 32-bit) avremmo potuto effettuare semplicemente un cast sul tipo appropriato. Se per esempio avessimo inserito una HBITMAPinvece di un int

    HBITMAP hData = (HBITMAP)SendMessage(hList, LB_GETITEMDATA,
               (WPARAM)index, 0);

Controlli Statici

Come i bottoni, i controlli statici sono molto semplici da usare. Ma per completezza li includiamo in questo tutorial. I Controlli Statici sono generalmente questo, statici, ovvero, non cambiano mai o quantomeno non viene fatto niente di particolare con essi. Sono usati nella stragrande maggioranza dei casi semplicemente per far visualizzare del testo all’utente. Comunque potete renderli un pò più utili e personalizzabili assegnando loro un ID unico (VC++ assegna l’ID di default IDC_STATC, che sarebbe -1 ed effettivamente vuol dire “No ID”) e settare il testo in fase di esecuzione per far visualizzare dati dinamici all’utente.

Nel codice d’esempio, ne viene usato uno per far visualizzare il contenuto dell’indice selezionato all’interno della listbox, ovviamente assumendo che ci sia qualcosa di selezionato.

    SetDlgItemInt(hwnd, IDC_SHOWCOUNT, data, FALSE);