CAPITOLO 13

API HANDLE E PROGRAMMAZIONE AVANZATA


Piccola premessa: in questo capitolo vi troverete di fronte argomenti del tutto nuovi, tipici della programmazione C strutturata (cioè non orientata agli oggetti). Già con il C++ molti dei concetti che seguono sono stati incapsulati in classi (MFC o OWL). Capirete quindi che essendo il Delphi orientato alla logica RAD, i concetti di handle e API sono stati COMPLETAMENTE sostituiti da oggetti VCL (Visual Component Library). Questo se da un lato il programmatore è più libero, da un altro lato lo rende vincolato a classi scritte da altri.
Questo è solo un discorso teorico, in realtà Delphi è talmente potente da poter gestire anche le chiamate API più complesse, nonchè tutti quei concetti tipici della programmazione Windows strutturata (funzioni callback per esempio).
Se avete quindi ancora problemi nello scrivere semplici applicazioni usando i mezzi che Delphi vi propone non posso che consigliarvi di affinare meglio le vostre tecniche, questo capitolo non fa per voi!


HANDLE

Un handle è un numero intero a 32-bit senza segno. Tutto qui!
Il Windows gestisce ogni oggetto (sia esso grafico come una finestra, sia esso hardware come la periferica video) con un handle, ad esempio quando inseriamo un nuovo pulsante in una form, questo ha necessariamente un handle.
E' quindi ovvio che Windows può gestire fino a 232 finestre contemporameamente, non di più perchè questi sono gli handle che può allocare in memoria.
Ma perchè dovremmo accedere ad un handle, quando Delphi ci mette a disposizione tutto già pronto, ma soprattutto come si fa ad accedere ad un handle?
Riguardo alla prima domanda non c'è una risposta precisa, in questo capitolo proporrò alcuni esempi dove per sopperire a limitazioni dell'ambiente Visuale si deve ricorrere a queste tecniche più a basso livello (anche se è un termine improprio).
Per accedere ad un handle è invece semplicissimo, ogni oggetto ha una proprietà non visibile da Object Inspector di nome handle!


COME NASCONDERE L'ICONA SULLA TASKBAR

Avete mai provato a nascondere l'icona sulla TaskBar di Win '95? Molto semplice non si può fare in Delphi!
Per capire ciò si deve premettere che in Delphi esiste un oggetto Application che contiene l'handle del pulsante sulla taskbar (non è propriamente così, ma per semplificarvi le cose...;-) quindi basterebbe nascondere trovare una chiamata per nascondere un oggetto grafico conoscendo il suo handle.
Proviamo quindi ad inserire in una form vuota un pulsante e nel codice click di questo il seguente codice

ShowWindow(handle, SW_HIDE);

Eseguiamo il codice... magia alla pressione del tasto sparirà la finestra!
Se mi avete seguito attentamente avrete già capito che l'istruzione seguente

ShowWindow(Application.handle, SW_HIDE);

Farà sparire il pulsante sulla taskbar... provare per credere!

Ma cosè questa istruzione ShowWindow? E' per l'appunto una API che visualizza, minimizza, massimizza o nasconde una finestra. Allora vi chiederete, il pulsante sulla taskbar è una finestra? Non proprio, in realtà l'oggetto Application è costruito attorno ad una finestra non visibile che serve solo ed esclusivamente per visualizzare il pulsante sotto, nascondendo questa finestra tramite l'istruzione ShowWindow potremo fare sparire anche il pulsante ad essa associato.
Provate, tanto per fare, anche le seguenti istruzioni:

ShowWindow(handle, SW_MINIMIZE);
ShowWindow(handle, SW_MAXIMIZE);

Che rispettivamente minimizzano e massimizzano la form. Non usate queste due istruzioni con Application.handle, vi avviso!


COME CREARE UNA FINESTRA SENZA CAPTION E RESIZABLE

Non so se avete notato ma il titolo della finestra è "appiccicoso", sembra che non esista possibilità di creare una finestra ridimensionabile senza di questo!
Ricorrendo però ad adeguate chiamate API...

Ogni finestra è costituita da due tipi di stile, lo stile e lo stile esteso (introdotto da Windows 3.1).
Per selezionare uno stile basta usare l'API SetWindowLong passando come parametri l'handle della finestra, GWL_STYLE nel caso dello stile, SWL_EXSTYLE nel caso dello stile esteso e lo stile vero e proprio.
Al contrario l'API GetWindowLong può essere usata per recuperare lo stile usato
Ad esempio inseriamo tre pulsante in una form ognuno con il seguente codice nell'evento click:

Button1 var Stile: Integer;
begin
  Stile := GetWindowLong(Handle,GWL_STYLE) AND NOT WS_CAPTION;
  SetWindowLong(Handle,GWL_STYLE, Stile);
  ClientHeight := ClientHeight + 1;
  ClientHeight := ClientHeight - 1;
end;
Button2 begin
  SetWindowLong(Handle,GWL_STYLE, WS_TILEDWINDOW);
  ShowWindow(handle, SW_NORMAL);
  ClientHeight := ClientHeight + 1;
  ClientHeight := ClientHeight - 1;
end;
Button3 var Stile: Integer;
begin
  Stile := GetWindowLong(Handle,GWL_EXSTYLE) or WS_EX_TOOLWINDOW;
  SetWindowLong(Handle,GWL_EXSTYLE, Stile);
  ClientHeight := ClientHeight + 1;
  ClientHeight := ClientHeight - 1;
end;
Button4 var Stile: Integer;
begin
  Stile := GetWindowLong(Handle,GWL_EXSTYLE) or WS_EX_CLIENTEDGE;
  SetWindowLong(Handle,GWL_EXSTYLE, Stile);
  ClientHeight := ClientHeight + 1;
  ClientHeight := ClientHeight - 1;
end;

Ora eseguito l'esempio e provate in sequenza i pulsanti. Poi riavviate il progetto e provate i tasti in ordine sparso. Il risultato saranno delle Form con stile non direttamente selezionabili in Delphi!
Vi potreste (giustamente) chiedere come assegnare uno stile alla finestra PRIMA che venga creata. Per fare ciò dovremo sovrascrivere un metodo dell'oggetto TForm.

TIP

lampa04.gif (2054 byte) Se non vi è ben chiaro il concetto di sovrascrittura di un metodo vi consiglio la lettura del capitolo OOP-Object Oriented Programming.

Per fare ciò dovremo sovrascrivere il metodo CreateParams, l'oggetto TForm1 sarà definito quindi come segue

type
  TForm1 = class(TForm)
    procedure CreateParams(var Params: TCreateParams); override;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

Ovviamente questo è solo in prototipo del metodo CreateParams, inserite quindi anche le seguenti righe di codice (nella sezione implementation, ma penso che avevate già capito)...

procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.ExStyle := Params.ExStyle or WS_EX_CLIENTEDGE;
end;

Il risultato sarà indentico alla pressione del tasto Button4, con l'unica differenza che è stato fatto prima che la finestra fosse stata creata.
Ma cos'è questo metodo CreateParams? Altro non è che i parametri che verranno passati alle API necessarie per creare la finestra vera e propria, quindi la sovrascrittura del metodo stesso ci permette di manipolare quello che Delphi ci nasconde.


UN BUG NELLA VCL

Quando minimizziamo una finestra di Windows questa mostra delle "ragnatele". Purtroppo i progetti scritti in Delphi sembrano odiare questo gradevole effetto grafico. Nella realtà l'oggetto Form che siamo soliti usare è costruito attorno all'API CreateWindow, in altre parole ogni finestra che viene creata in Windows deve usare quest'API. Gli stessi pulsanti, CheckBox e ListBox sono in realtà finestre che contengono handle. Purtroppo gli sviluppatori di Delphi pr qualche criticabile ragione hanno deciso di creare un oggetto Application che "sovrasta" tutte le Form del Delphi. Per questo quando minimizziamo una Form nella realtà non viene minimizzata ma bensì nascosta e quindi Win '95 non può visualizzare l'effetto grafico in questione.
Come venirne a capo? Semplicemente con qualche chiamata API ;-)
A conferma di quanto vi ho appena spiegato provate ad inserire in un pulsante la seguente riga di codice:

ShowWindow(handle, SW_MINIMIZE);

Se avete ben capito l'uso di questa API la finestra dovrebbe venire minimizzata, e in effetti questo avviene ma non nel modo corretto...
Il primo passo sarà quello di fare apparire nella taskbar il pulsante; per fare ciò dovremo sovrascrivere nuovamente il metodo CreateParams (vedi sezione precedente). Tralasciando il prototipo, il metodo vero e proprio sarà il seguente:

procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.WndParent := GetDeskTopWindow;
end;

In effetti apparirà un pulsante nella taskbar, ma questo certo non risolve il nostro problema!
Per nascondere il vecchio pulsante nella taskbar basta proseguire come già spiegato nella prima parte del capitolo, aggiungiamo quindi il seguente codice nell'evento OnFormCreate della Form1:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Show;
  ShowWindow(Application.handle, SW_HIDE);
end;

Siamo in dirittura di arrivo (enjoy ;-))... Adesso ci troviamo di fronte a due possibilità:

  1. Sovrascriviamo il metodo in TForm che minimizza le Form
  2. Intercettiamo il messaggio che Windows manda alla Form1 quando questa sta per essere minimizzata e l'adattiamo per il nostro caso.

Il primo metodo è sicuramente più semplice, ma dato che noi siamo programmatori seri procederemo con il secondo.
L'intercettazione di un messaggio in Delphi è piuttosto semplice, in effetti è molto simile alla sovrascrittura di un metodo con l'unica eccezione che il nome della procedura non è definito in alcun oggetto (lo possiamo quindi decidere noi stessi) e che il parametro override andrà sostituito con message nomemessaggio.
L'oggetto TForm1 andrà quindi definito come segue:

type
  TForm1 = class(TForm)
    procedure CreateParams(var Params: TCreateParams); override;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    procedure WmSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
  public
    { Public declarations }
  end;

Se avete ben compreso la procedura WmSysCommand poteva anche venir denominata come minimizza, non sarebbe cambiato nulla, invece è importante osservare che il nome del messaggio che ci interessa intercettare è WM_SYSCOMMAND e quindi la procedura accetta un parametro di tipo TWmSysCommand.
Analogamente se volessimo intercettare in altro messaggio (ad esempio WM_SIZE dovremo come parametro il tipo previsto dall'Object Pascal (in questo caso TWmSize). Non è poi così difficile, vero?
Unica avvertenza, dichiarate i messaggi sempre come privati!
Sarà bene presentare anche la vera procedura, altrimenti qui facciamo notte... ;-)

procedure TForm1.WmSysCommand(var Msg: TWMSysCommand);
Begin
  DefaultHandler(Msg);
End;

Incredibile, funziona...
Ma dove sta la magia? Semplice, provate a sostituire DefaultHandler(Msg); con Inherited; non funzionerà più!
In effetti abbiamo fatto tutto questo lavoro per sovrascrivere quel benedetto messaggio, non per ereditare il bug.
Ma cosè quella fantomatica riga DefaultHandler? Per spiegarlo devo farvi riscrivere tutta la procedura (spiacente)

procedure TForm1.WmSysCommand(var Msg: TWMSysCommand);
Var DefMsg: TMessage;
Begin
  DefMsg := TMessage(Msg);
  DefWindowProc(handle, Msg.Msg, DefMsg.wParam, DefMsg.lParam);
End;

Questa procedura funziona alla perfezione al posto dell'altra per il semplice motivo che DefaultHandler altro non è che l'incapsulazione Pascal dell'API DefWindowProc. Quest'API serve per richiamare il funzionamento standard di un messaggio Windows, quindi richiamando DefaultHandler la finestra verrà minimizzata e non nascosta.


Vi consiglio vivamente un download del progetto finale della form free minimize bug.

G3.gif (414 byte)

DOWNLOAD THIS PROJECT

 

Successivo
Precedente