Lezione 6 – Il controllo EDIT
I controlli "Edit" sono il modo più semplice e comodo per consentire all'utente di immettere o modificare del testo; inoltre, possono anche essere usati allo scopo di mostrare del testo, senza permetterne la modifica. In questo caso, il vantaggio che offrono rispetto ai controlli "Static" è quello di consentire le operazioni di clipboard (o di "appunti" che dir si voglia), cioè il testo mostrato può essere selezionato e copiato su clipboard al fine di "incollarlo" altrove.
I bit di stile di un controllo Edit controllano gran parte del suo funzionamento. ES_READONLY serve per rendere il controllo "di sola lettura": il testo è mostrato, ma l'utente non può cambiarlo. ES_LEFT, ES_CENTER, ed ES_RIGHT, definiscono l'allineamento del testo. ES_LOWERCASE, ed ES_UPPERCASE, fanno sì che il controllo trasformi automaticamente tutti i caratteri scritti in minuscole e maiuscole rispettivamente; ES_PASSWORD fa sì che i caratteri siano mostrati come asterischi, ma essi non sono trasformati in asterischi, il programma può accedere al "vero" testo immesso dall'utente. ES_NUMBER permette all'utente di immettere solo cifre.
ES_MULTILINE permette di avere un controllo di edit per molteplici linee di testo (il default è una sola linea). Spesso, questo si associa all'ulteriore bit di stile ES_WANTRETURN, che fa sì che, se l'utente usa il tasto Enter nell'immissione di testo, questo sia interpretato come un "a-capo"; normalmente, la pressione di Enter equivale invece ad un click sul bottone di default del dialogo.
Un edit multilinea può avere diversi comportamenti di "scrolling": per default, "nessuno", se l'utente immette più testo di quanto ne possa mostrare una riga del controllo, ci sarà un automatico "word wrap" alla riga seguente, e se non c'è più spazio per altre righe, un "beep" di errore. Ma si può avere uno scroll automatico, sia in orizzontale di 10 caratteri alla volta (ES_AUTOHSCROLL), sia in verticale di una "pagina" alla volta (ES_AUTOVSCROLL); l'edit multilinea può anche venir dotato di esplicite barre di scroll, con i bit di stile WS_HSCROLL per la orizzontale, WS_VSCROLL per la verticale, come indicano i nomi di questi bit di stile, essi sono d'altra parte applicabili a qualsiasi finestra (WS_ sta per "window style"); delle scroll bar (che sono a loro volta dei controlli) parleremo più avanti.
L'unico di questi bit di stile ad essere applicabile agli edit non multi-linea è ES_AUTOHSCROLL. Un bit di stile generico che è usato molto spesso con i controlli di edit, sia multi-linea sia a linea singola, è WS_BORDER, che dà un "bordo" rettangolare al controllo.
Windows ci permette di cambiare "al volo" i vari bit di stile di una finestra (in particolare, di un controllo), attraverso l'API SetWindowLong, che già abbiamo visto, con indice GWL_STYLE per i bit di stile "normali" (WS_qualcosa, ES_qualcosa, ...) e GWL_EXSTYLE per i bit di stile "estesi" (WS_EX_ecc); similmente, GetWindowLong ci permette di ottenere a runtime i bit di stile di una finestra, usando gli stessi indici. Quindi, è abbastanza agevole realizzare un piccolo programma sperimentale che ci permetta di verificare l'effetto di varie combinazioni di bit di stile.
Faremo, dunque, una finestra di dialogo con tante AUTOCHECKBOX, una per ciascun bit di stile che ci interessa, e un singolo Edit cui applicare la combinazione; in risposta ad ogni click su di uno dei bottoni con AUTOCHECKBOX, la nostra dialog procedure (nella gestione del relativo WM_COMMAND) provvederà a settare o resettare il corrispondende bit di stile nell'Edit.
Il file Esperim.Rc sarà dunque qualcosa del tipo:
#include "windows.h"
#include "resource.h"
IDD_DIALOG1 DIALOG 100, 100, 200, 210
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Stili per controlli Edit"
FONT 8, "MS Sans Serif"
BEGIN
LTEXT "Stile:",IDC_ETIC,10,6,20,10
LTEXT "",IDC_STIL,30,6,40,10
EDITTEXT IDC_EDIT,10,20,180,28
AUTOCHECKBOX "ES_READONLY",ES_READONLY,10,50,90,10
AUTOCHECKBOX "ES_CENTER",ES_CENTER,10,60,90,10
AUTOCHECKBOX "ES_RIGHT",ES_RIGHT,10,70,90,10
AUTOCHECKBOX "ES_LOWERCASE",ES_LOWERCASE,10,80,90,10
AUTOCHECKBOX "ES_UPPERCASE",ES_UPPERCASE,10,90,90,10
AUTOCHECKBOX "ES_PASSWORD",ES_PASSWORD,10,100,90,10
AUTOCHECKBOX "ES_NUMBER",ES_NUMBER,10,110,90,10
AUTOCHECKBOX "ES_MULTILINE",ES_MULTILINE,10,120,90,10
AUTOCHECKBOX "ES_WANTRETURN",ES_WANTRETURN,10,130,90,10
AUTOCHECKBOX "ES_AUTOHSCROLL",ES_AUTOHSCROLL,10,140,90,10
AUTOCHECKBOX "ES_AUTOVSCROLL",ES_AUTOVSCROLL,10,150,90,10
AUTOCHECKBOX "WS_BORDER",0x8008,10,170,90,10
AUTOCHECKBOX "WS_HSCROLL",0x8002,10,180,90,10
AUTOCHECKBOX "WS_VSCROLL",0x8001,10,190,90,10
END
Abbiamo definito una AUTOCHECKBOX per ogni bit di stile che interessa esplorare; come ID del controllo, abbiamo usato lo stesso valore del bit di stile. è importante evitare duplicati per gli ID, e per questo basterà che le costanti IDC_ definite nel nostro resource.h non siano potenze di 2, infatti, tutte le costanti "bit di stile", avendo un solo bit settato, saranno, appunto, potenze di 2. Ad esempio, questo resource.h va benissimo:
#define IDD_DIALOG1 101 #define IDC_EDIT 3 #define IDC_ETIC 5 #define IDC_STIL 6
Il controllo IDC_EDIT è quello a cui applicheremo gli stili di interesse; IDC_ETIC è una semplice scritta, che non cambieremo, mentre, in IDC_STIL, manterremo un display esadecimale dei "bit di stile correnti" del controllo, così da poter esaminare in dettaglio la situazione.
I tre check-box corrispondenti ai bit di stile WS_ che ci interessano hanno un ID particolare; infatti, per gli ID si possono usare solo 16 bit, mentre il valore "naturale" dei bit WS_BORDER, eccetera, è di 0x00800000, ecc; abbiamo codificato questo caso settando il bit più alto dell'ID il nostro codice dovrà accuratamente shiftare il resto dell'ID a sinistra di 20 bit, se il bit più alto è settato, per ottenere il corrispondente valore di "bit mask".
Si noti, ancora, la mancanza di un checkbox che corrisponda ad ES_LEFT. Esaminando winuser.h (lo header file di sistema che, fra l'altro, definisce tutte queste costanti di stile), potremo vedere che ES_LEFT è definito come valore zero. Ciò significa che non vi è un "vero" bit di stile dedicato a codificare "allineamento a sinistra": l'allineamento a sinistra è il default che si ottiene se non è richiesto, n´ ES_RIGHT, n´ ES_CENTER. La macro di "pseudo-bit-di-stile" ES_LEFT è dunque definita al puro scopo di permetterci di non dover tenere conto di questo in continuazione, di solito possiamo programmare "come se" ES_LEFT, ES_RIGHT, ed ES_CENTER fossero tre bit fra di loro mutuamente esclusivi, anche se, in realtà, l'allineamento è codificato in due bit soli.
// esper.c
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#include "resource.h"
// chiusura di un generico dialogo modale
void OnDlgClose(HWND hWnd)
{
EndDialog(hWnd,0);
}
// torna lo stile di un controllo di un dialogo
LONG getStyle(HWND hDlg, int idCtl)
{
return GetWindowLong(GetDlgItem(hDlg, idCtl), GWL_STYLE);
}
// cambia lo stile di un controllo di un dialogo
void setStyle(HWND hDlg, int idCtl, LONG styl)
{
HWND hCtl = GetDlgItem(hDlg, idCtl);
SetWindowLong(hCtl, GWL_STYLE, styl);
}
// mostra lo stile di un controllo di un dialogo, in
// formato esadecimale, come testo di un altro controllo
// dello stesso dialogo
void showStyle(HWND hDlg, int idCtl, int idShow)
{
LONG styl = getStyle(hDlg, idCtl);
char buf[12];
wsprintf(buf,"0x%8.8x",styl);
SetDlgItemText(hDlg, idShow, buf);
}
// torna il bit di stile rappresentato da un bottone,
// o 0 per finestre che non rappresentano tali bit
LONG getStylebit(HWND hwnd, LONG refproc)
{
// ignora i non-button
LONG winproc = GetWindowLong(hwnd, GWL_WNDPROC);
if(winproc != refproc) return 0;
// ottiene l'ID
int id = GetDlgCtrlID(hwnd);
// caso speciale di codifica
if(id & 0x8000) id = (id&~0x8000) << 20;
return id;
}
BOOL OnDlgInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
// mostra lo stile dell'edit (in esadecimale)
showStyle(hwnd, IDC_EDIT, IDC_STIL);
// ottiene la winproc di un bottone
HWND hButton = GetDlgItem(hwnd, ES_READONLY);
LONG refproc = GetWindowLong(hButton, GWL_WNDPROC);
// la ricorda per il futuro
SetWindowLong(hwnd, DWL_USER, refproc);
return TRUE;
}
void OnDlgCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
// gestisce solo clic su bottoni
if(codeNotify != BN_CLICKED) return;
// recupera la winproc dei button
LONG refproc = GetWindowLong(hwnd, DWL_USER);
// modifica lo stile come specificato
LONG sb = getStylebit(hwndCtl,refproc);
LONG styl = getStyle(hwnd,IDC_EDIT);
if(IsDlgButtonChecked(hwnd,id)==BST_CHECKED) styl |= sb;
else styl &= ~sb;
setStyle(hwnd,IDC_EDIT,styl);
// mostra nuovamente lo stile in esadecimale
showStyle(hwnd, IDC_EDIT, IDC_STIL);
}
BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
HANDLE_MSG(hwndDlg,WM_CLOSE,OnDlgClose);
HANDLE_MSG(hwndDlg,WM_INITDIALOG,OnDlgInitDialog);
HANDLE_MSG(hwndDlg,WM_COMMAND,OnDlgCommand);
}
return FALSE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
return 0;
}
L'unica "finezza" si accentra sulla getStylebit, che è architettata, oltre che per decodificare lo stile dall'id del controllo sulla base dell'eventuale bit 0x8000, anche per dare sempre 0 per eventuali non-bottoni; a questo scopo, verifichiamo che la window procedure del controllo in esame sia proprio quella dei bottoni.
Eseguendo questo programma, osserviamo che lo stile iniziale dell'edit è 0x50010000 da un esame di winuser.h, possiamo confermare che questo corrisponde ai tre bit di stile:
WS_CHILD|WS_VISIBLE|WS_TABSTOP
il che, in effetti, appare abbastanza sensato, e che possiamo modificarlo come previsto facendo cambiare lo stato delle varie checkbox.
Purtroppo, osserviamo anche che quasi tutti questi cambiamenti di bit di stile non hanno effetto alcuno! Fra le poche eccezioni, ES_LOWERCASE ed ES_UPPERCASE, che effettivamente agiscono sul testo immesso mentre sono settati, bench´, cosa tutto sommato ragionevole, non sul testo preesistente.
Se il lettore che ha osservato questo effetto ha anche provato a studiare la documentazione dell'SDK di Windows, è anche possibile che abbia notato una breve osservazione presente nella documentazione della SetWindowLong: alcuni bit di stile sono in cache, così che i cambiamenti fatti con SetWindowLong non avranno effetto finch´ non si chiama la funzione SetWindowPos. Provando a seguire questa indicazione, il lettore può aver provato a rimediare al difetto con l'aggiunta di una chiamata, appunto, a detta API:
SetWindowPos(hCtl, 0,0,0,0,0, SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER);
nel codice come lo abbiamo appena presentato, questa chiamata andrebbe naturalmente entro la funzione setStyle, subito dopo la chiamata a SetWindowLong e subito prima della fine della setStyle. Questa è una chiamata a SetWindowPos che non fa nessun cambiamento (grazie ai vari bit SWP_NOxxx), ma dice alla finestra che qualcosa è cambiato grazie all'apposito bit d'opzione SWP_FRAMECHANGED.
Qualcosa, in effetti, migliora; ad esempio, i bit di WS_HSCROLL e WS_VSCROLL adesso hanno effetto, facendo apparire, se settati, le scrollbar desiderate. Ma la maggior parte dei bit di stato restano invece desolatamente priva di effetti, per quanto noi possiamo settarli.
Purtroppo, la causa di questo "non-comportamento" è mal documentata ma sicuramente vera: non pochi dei bit di stile dei controlli di Windows sono, così ci dice l'osservazione, esaminati dal controllo soltanto in partenza; dopo che il controllo è stato creato, non tiene più conto di eventuali modifiche a questi bit. Ciò è sicuramente più efficiente, in quanto, in questo modo, la window procedure del controllo non ha bisogno di esaminare quei bit ad ogni messaggio che riceve, bensì solo alla creazione del controllo stesso. Come possiamo noi allora ottenere invece il "dinamismo" che ci serve per vedere in opera tutti i vari bit di stile al semplice clic su di una checkbox?
Una soluzione c'è, bench´ forse poco intuitiva; essa si basa, d'altronde, su di un'API che ancora non abbiamo visto, per quanto l'esistenza di qualcosa del genere debba poter essere considerata "implicita" in tutte le operazioni di Windows. Questa domanda ci introduce all’argomento del prossimo capitolo: la creazione di finestre.
Concludiamo il capitolo descrivendo qualche altro messaggio che gli edit sono in grado di spedire. I controlli di Edit possono spedire vari messaggi di notifica (tramite la solita WM_COMMAND) al dialogo che li contiene.
Ogni volta che l'utente modifica il testo dell'edit control, il controllo anzitutto spedisce alla finestra-genitrice un WM_COMMAND con codice di notifica EN_UPDATE; come al solito, LOWORD(wParam) contiene l'ID, e lParam la handle, del controllo di edit. In risposta a questa notifica, il dialogo può, ad esempio, apportare delle modifiche al testo contenuto nel controllo; dopo di che, il controllo spedisce al dialogo una ulteriore notifica, EN_CHANGE, dopo avere mostrato il nuovo testo.
Se il controllo ha barre di scroll, orizzontale e/o verticale, e l'utente usa una di esse, allora il controllo, subito prima di mostrare il nuovo testo, notifica il dialogo con EN_HSCROLL e EN_VSCROLL rispettivamente.
Quando il controllo ottiene il focus (tipicamente perch´ l'utente ci ha cliccato su, ma potrebbe esserci anche arrivato in altro modo, come, ad esempio, con un tab), la notifica è EN_SETFOCUS; quando il controllo perde il focus, è invece EN_KILLFOCUS, quest'ultima occasione è un buon momento per "validare" il testo che l'utente ha posto nell'edit, se esso deve rispettare un qualche vincolo sintattico o semantico. Attenti, però, che un Enter, salvo che l'edit non abbia stile ES_WANTRETURN, può causare "il click del bottone di default" senza aver prodotto EN_KILLFOCUS!
EN_MAXTEXT è la notifica "il testo ha raggiunto il massimo ammesso"; può essere gestita, in alternativa al settaggio del bit di stile ES_AUTOHSCROLL ecc, per eseguire "scrolling" secondo modalità particolari sotto il totale controllo del programma.
Parecchi sono pure i messaggi che possono essere spediti al controllo di edit, per ottenere da esso delle informazioni, o modificarne le operazioni.
WM_UNDO, ad esempio, è la richiesta di "disfare" l'ultima modifica apportata al testo (si può prima spedire EM_CANUNDO, che ritornerà 0 se l'undo non è possibile in questo stato). WM_COPY, WM_PASTE, e WM_CUT, operano sulla clipboard ("appunti") nel modo prevedibile, in particolare, la "zona" del testo del controllo su cui essi hanno effetto è la "selezione corrente", che è anche quella parte del testo del controllo che viene mostrata all'utente con "highlight".La selezione corrente può anche essere cancellata, senza effetto sulla clipboard, spedendo all'edit un WM_CLEAR. Il messaggio EM_SETSEL permette di impostare la "selezione corrente": start è in wParam, end in lParam, e inoltre: start=0, end=-1, per fare la selezione di tutto il testo presente; start=-1, per fare una selezione vuota; il "caret" (il cursore tipico dei testi) è posto al più grande fra i due valori start ed end.
Per chiedere quale sia la selezione corrente, EM_GETSEL, con wParam posto all'indirizzo di una DWORD di "inizio", lParam a quello di una DWORD di "fine" (per entrambi è accettabile il valore 0, se quel dato non interessa); come per EM_SETSEL, gli indici partono da 0, e la end è l'indice del primo carattere non selezionato, dopo la selezione corrente.
EM_REPLACESEL cambia il testo della selezione corrente, con una stringa (terminata da 0 binario, come al solito) il cui indirizzo iniziale è lParam; wParam, se diverso da zero, dice al controllo di tenere memorizzato il testo precedente al fine di permettere un eventuale undo.
Ci sono più di altre due dozzine di codici di messaggio EM_xxx, che permettono operazioni assai precise e dettagliate; non sarebbe però appropriato, entro i limiti di questa guida, entrare nei dettagli di tutte queste operazioni.
Dovendo scrivere un editor, o implementare operazioni molto particolari, sarà certo il caso di ripassarsi tutti i dettagli e le possibilità in un opportuno testo di riferimento (come, ad esempio, la documentazione in linea dell'SDK, disponibile gratuitamente per consultazione e download dal sito Microsoft, purtroppo solo in inglese). Si vedono, a volte, esempi di subclassing portati su controlli di edit, che sembrano ignorare totalmente le enormi potenzialità dei controlli stessi già nel loro stato-base (per non dire dei controlli di Rich Edit, che sono un vasto sovrainsieme di quelli di Edit, aggiungendo varie altre funzioni oltre a quelle di poter mantenere molteplici font e colori, e testo anche al di là dei 64k che normalmente limitano un controllo di Edit).
Fra le cose di una certa importanza generale, notiamo, tuttavia, il messaggio EM_SETREADONLY, con lParam=0 e wParam pari a TRUE perchè l'edit divenga readonly, a FALSE per farlo invece divenire read-write; questo ci risparmia, nel caso assai comune che si voglia cambiare dinamicamente questo bit di stato, la necessità di usare la nostra funzione ricreaWindow (visto che, come abbiamo visto, il bit di stile settato con SetWindowLong non ha, di per sè, alcun effetto).
E' inoltre il caso di tenere presenti le API SetDlgItemInt e GetDlgItemInt, che si sposano particolarmente bene con lo stile ES_NUMBER per un controllo di edit: esse, infatti, rendono più comodo il comune compito di comunicare (scrivere, o leggere) il testo di un tale controllo visto come codifica decimale di un numero intero. Non ci sono, purtroppo, equivalenti per altre basi, come l'esadecimale; ove occorra, ciò va invece ottenuto attraverso "subclassing".











Hello