You are on page 1of 130

Manuale sul Delphi

AUTORE: Carlo Marona IMPAGINAZIONE: DrN3On RDC - Red Diamond Crew


LEZIONE 1:

La storia

Il Delphi nasce come evoluzione del "Borland Turbo Pascal". Il Pascal un linguaggio ad alto livello che stato sviluppato alla fine degli anni sessanta dal professor Niklaus Wirth a Zurigo. Il suo scopo era quello di realizzare un linguaggio comprendente un piccolo numero di concetti fondamentali di programmazione sufficienti ad insegnarla in forma di disciplina logica e sistematica; nello stesso tempo voleva un linguaggio che fosse facile ed efficiente da implementare sulla maggior parte dei calcolatori. Il suo successo nel conseguire questo obiettivo pu essere misurato dalla rapida diffusione dell'uso del PASCAL sia come linguaggio usato nella didattica dei principi di programmazione (si usa tutt'oggi nelle universit), sia come linguaggio effettivo per scrivere software di base ed applicativo. Cosa si intende per linguaggio ad alto livello? Un linguaggio ad alto livello un linguaggio che tende con i suoi costrutti, la sua sintassi e grammatica di avvicinarsi il pi possibile al linguaggio ed al ragionamento umano. In contrapposizione ai linguaggi ad alto livello, ci sono i linguaggi a basso livello come l'Assembler ed affini che rimangono molto pi vicini al linguaggio macchina utilizzato dal calcolatore. Queste macchine infatti non comprendono il linguaggio dell'uomo direttamente, sanno solamente lavorare con operazioni matematiche di base (addizione, sottrazione, operazioni logiche implementate da circuiti elettronici) su sequenze pi o meno lunghe di 0 o di 1. Rimanendo molto scomodo lavorare con sequenze di questo genere, i linguaggi di programmazione tendono ad rendere il lavoro del programmatore pi semplice astraendo da quello con cui effettivamente il calcolatore andr a lavorare. L'Assembler gi un linguaggio ad alto livello rispetto al linguaggio macchina puro, ma un linguaggio a basso livello rispetto ad altri linguaggi come il Pascal che lavora su un livello di astrazione superiore. L'evoluzione del Pascal negli anni successivi al suo sviluppo, lo ha portato alla definizione della versione 7.0 (includente la v. 2.0 del Turbo Vision) completamente orientata agli oggetti che a tutt'oggi viene ancora utilizzato per la realizzazione di software per DOS. Con l'avvento di Windows la Borland rilasci il Borland Pascal pacchetto basato sul linguaggio Object Pascal, naturale evoluzione del Turbo Pascal. La stesura di applicazioni in ambiente Windows rimaneva comunque alquanto lenta e complessa; era necessario conoscere molto bene la programmazione in Object Pascal e le immancabili API di Windows. Fu cos che la Borland rilasci la prima versione del Delphi, la 1.0 appunto. Questa versione di Delphi produceva applicazioni per Windows a 16-bit. Successivamente venne rilasciata la versione 2.0 completamente rivista ed ottimizzata per lo sviluppo di applicativi in ambiente 32-bit. Siamo all'incirca nel 1996 e da qui il Delphi si evoluto ad una velocit impressionante, quasi una versione all'anno. Ad oggi (2001) la versione pi recente del Delphi la 5.0 e si vocifera che per la seconda met dell'anno sar disponibile la versione 6.0. Con il Delphi la Borland ha introdotto il concetto di sviluppo Visuale RAD (Rapid Application Development) orientato alla gestione degli eventi come nel Visual Basic, senza perdere per i vantaggi della compilazione Pascal. da notare che il codice scritto in Delphi conforme all'Object Pascal di cui ha anche ereditato il numero di versione; la versione 8.0 per Delphi 1.0, la 9.0 per Delphi 2.0 etc. Ci confermato dal fatto che possibile compilare vecchie applicazioni in Object Pascal tramite il compilatore Delphi. Da notare che, come l'Object Pascal il Delphi un linguaggio GP (General Purpose) ovvero un tool di sviluppo che permette di realizzare applicazioni di qualsiasi tipo, pi o meno complesse e tutto questo con una semplicit estrema. Ho voluto fare questa precisazione perch mi capitato molte volte di parlare con persone "ignoranti" (nel senso che ignorano ;-) !!) che bistrattano questo linguaggio solamente perch magari non ne hanno sentito parlare come il Visual Basic di Microsoft e quindi lo ritengono un linguaggio "giocattolo".

LEZIONE 2:

Versioni di Delphi

Abbiamo gi visto nell'Introduzione storica le prime due versioni di Delphi: la 1.0 e la 2.0. Abbiamo gi detto le principali differenze tra queste due versioni ovvero la prima lavora a 16-bit e la seconda a 32-bit. Dalla 2.0 in poi tutte le versioni di Delphi lavorano a 32-bit. Fin dalla prima versione di Delphi per ognuna di queste sono disponibili pi edizioni; in Delphi 1.0 queste erano due e si differenziavano per il supporto allo sviluppo Client/Server. Dalla versione 2.0 possibile scegliere tra tre edizioni differenti "Delphi 2.0 Standard", "Delphi 2.0 Developer" e la "Delphi 2.0 Client/Server". Dalla versione 4.0 queste tre edizioni hanno preso i nomi rispettivamente di "Standard", "Professional", "Enterprise". Le principali differenze tra queste edizioni stanno nella possibilit o no di gestire database, di disporre del supporto per internet, di lavorare con database server con drivers nativi (SQL Server, Oracle, Sybase, Interbase), di incorporare la gestione del multi-language o internazionalizzazione del software e nella disponibilit di tool di sviluppo aggiuntivi quali ad esempio l'SQL Monitor e il supporto per la creazione visuale di query SQL, il supporto per tecnologie CORBA, MIDAS, e COM. La versione 3.0 introduce una novit interessante non presente nelle versioni precedenti: il supporto per i packages. Al pari delle librerie DLL permettono di raggruppare del codice e permetterne il caricamento solo quando se ne ha la necessit; e di condividere, magari con altre applicazioni, il codice in esse contenuto. Ho detto al pari delle DLL: ci non del tutto corretto, ci sono delle differenze importanti tra i due sistemi come vedremo poi nel seguito del corso. Le versioni "Standard" sono, come dice il nome stesso, il livello base di Delphi per la realizzazione di applicazioni semplici che non richiedono l'utilizzo di database e l'accesso ad Internet, perlopi indirizzate a studenti, hobbisti, principianti. Le versioni "Developer" o "Professional" sono rivolte a coloro che utilizzano il Delphi per lavoro e che necessitano di supporto per database e internet. Le versioni "Enterprise", come dice il nome, sono rivolte alle aziende che necessitano del supporto per il controllo di versione, accesso a database sever, internet, internazionalizzazione del software. In questo corso si presuppone, almeno per alcune funzionalit, di avere a disposizione l'edizione "Professional" di Delphi 5.0, la stessa che normalmente utilizzo per lavoro.

Immagini delle confezioni delle varie releases della versione 5.0 (sito Borland)

LEZIONE 3:

Pascal, Delphi, Linux

Come gi menzionato nell'Introduzione storica, l'intento del professor Niklaus era quello di sviluppare un linguaggio facilmente implementabile su piattaforme differenti. Esistono diverse implementazioni del Pascal alcune di queste sono disponibili anche in ambiente Linux. Si tratta di implementazioni di tipo GNU e OpenSource, quale per esempio il "Free Pascal". Questa implementazione completamente compatibile con il Pascal standard e nelle sue ultime versioni implementa anche l'Object Pascal. Esistono anche ambienti di sviluppo GNU come per esempio Lazarus che riproducono quasi in maniera speculare l'IDE di Delphi e che permettono di realizzare con la stessa facilit (anche se ancora con molti problemi) interfacce grafiche per applicazioni Linux. Le distribuzioni menzionate supportano nativamente codice Delphi. Ci permetter di effettuare il porting di applicazioni per Windows in ambiente Linux apportando il minor numero possibile di modifiche al codice. Sulla stessa strada sta lavorando Borland che ha gi rilasciato la versione 1.0 del suo nuovo prodotto, il Kylix. Questo prodotto a tutti gli effetti il porting di Delphi per Windows in ambiente Linux. La stessa Borland afferma che dalla versione 6.0 di Delphi sar possibile effettuare il porting di applicazioni da Windows a Linux semplicemente ricompilando il codice con il Kylix. Il Kylix, per chi gi conosce Delphi, ne la riproduzione esatta: stessa interfaccia grafica, stessi tools, stesse impostazioni e disposizione dei comandi. La cosa pi importante che il codice compilato con il Kylix nativo per Linux ovvero in formato ELF. Per permettere il porting nella maniera pi semplice possibile la Borland ha dovuto riscrivere l'intera VCL (Visual Component Library, ovvero la libreria dei componenti visuali di Delphi) in maniera tale che fosse indipendente dalla piattaforma. Ci stato realizzato creando una nuova libreria la CLX (si legge clix) che si basa per la parte grafica sulle librerie Qt (oggetti multipiattaforma della Trolltech) le stesse utilizzate anche per la realizzazione della parte grafica di KDE. Purtroppo per le prime due edizioni del Kylix non esiste una licenza Open Source o GPL per cui andranno acquistate e non a basso prezzo. Si vocifera per la prossima uscita di una terza edizione che andr a coprire questa fascia. L'intento di Borland infine anche quello di integrare sotto lo stesso tool di sviluppo sia Delphi che C++ BUILDER. Staremo a vedere.

LEZIONE 4:

Panoramica sul linguaggio

Sempre facendo riferimento alla parte introduttiva storica di questo corso, abbiamo visto che Delphi si basa su Object Pascal. Si tratta, come d'altronde il Pascal, di un linguaggio compilato, fortemente tipizzato, che supporta la progettazione strutturata e orientata agli oggetti. Offre un codice di facile lettura, una compilazione veloce e l'uso di pi file unit per la programmazione modulare. Object Pascal ha delle speciali funzionalit che supportano la struttura a componenti e l'ambiente RAD di Delphi. I programmi Delphi sono generalmente divisi in moduli di codice sorgente denominati unit. Ogni programma inizia con una intestazione, che ne specifica il nome, la quale seguita da una clausola opzionale uses e da un blocco di dichiarazioni e di istruzioni. La clausola uses elenca le unit che sono collegate nel programma; queste possono essere condivise da programmi diversi ed hanno spesso proprie clausole uses. La clausola uses fornisce al compilatore informazioni sulle dipendenze esistenti tra i moduli. Dal momento che queste informazioni vengono memorizzate nei moduli stessi, i programmi in Object Pascal non richiedono n makefile, n file header, n direttive "include" per il preprocessore come per esempio nel C++. Diamo un'occhiata alle estensioni utilizzate dal compilatore Delphi. Questo si aspetta di trovare il codice sorgente in Pascal in di tre tipi: File sergente di unit, che terminano con l'estensione .PAS File progetto, che hanno come estensione .DPR File sorgente di package, che terminano con l'estensione .DPK

I file sorgente delle unit contengono la maggior parte del codice di un'applicazione. Ogni applicazione Delphi ha un singolo file progetto e diversi file unit; il file progetto, corrispondente al file "principale" nel Pascal

tradizionale, organizza i file unit in un'applicazione. Delphi mantiene automaticamente un file progetto per ciascuna applicazione. I file sorgente dei packages sono simili ai file progetto, ma vengono usati per costruire speciali librerie a collegamento dinamico, denominate package. Esistono altri file utilizzati nella realizzazione di una applicazione Delphi che non contengono codice Pascal. Questi file vengono di norma mantenuti automaticamente da Delphi. Essi sono: I file from, terminano con l'estensione .DFM I file di risorse, che hanno come estensione .RES I file di opzioni di progetto, che terminano in .DOF

Esistono anche altri tipi di file utilizzati esclusivamente per le impostazioni dell'ambiente di sviluppo come per esempio i file .DSK che contengono le impostazioni del desktop dell'IDE. I file .DFM sono dei file di risorse di Windows che contengono bitmap, stringhe e cos via. Ogni file form la rappresentazione binaria della scheda Delphi corrispondente generalmente ad una finestra o ad una finestra di dialogo di una applicazione Windows. L'IDE di Delphi permette anche di modificare in maniera testuale il contenuto di questi file; tranne in casi eccezionali, come il recupero di schede danneggiate a causa di componenti malfunzionanti, non sia ha la necessit di intervenire direttamente in maniera testuale su questo tipo di file. La gestione viene generalmente tenuta dai tool visuali di Delphi. Ad ogni scheda altres associato un file .PAS contenente il codice Pascal che si riferisce alla scheda in oggetto. Questo file contiene il codice degli eventi degli oggetti nella scheda, le routines definite dall'utente per quella scheda, ecc. Per impostazione predefinita questi file hanno lo stesso nome del file form. I file di risorse sono file di risorse standard di Windows (.RES) per contenere la bitmap per l'icona dell'applicazione. Questo quello che si ha prima di effettuare la compilazione del codice. Una volta che il codice stato compilato, troveremo dei nuovi file nella directory del progetto, alcuni con lo stesso nome di quelli appena visti ma con estensioni differenti. Queste sono: .DCU (Delphi Compiled Unit): sono le estensioni che il compilatore da alle unit compilate che in seguito verranno linkate insieme nel file .EXE .DLL (Dinamic Link Library): solo le estensioni che contraddistinguono una libreria a collegamento dinamico; ovviamente questo tipo di estensione sar disponibile qualora sia stato compilato un progetto di libreria a collegamento dinamico. .BPL (Borland Package Library): sono le estensioni delle librerie a collegamento dinamico della borland ovvero i package; anche queste estensioni non saranno sempre disponibili tranne se si effettua la compilazione di un progetto di package.

Diamo uno sguardo ad un semplice esempio, il tipico programmino "Hello World!" che si soliti realizzare in fase di apprendimento di un nuovo linguaggio. Si tratta di una semplice applicazione per console che pu essere compilato ed eseguito dalla riga di comando. Program Hello_World; {$APPTYPE CONSOLE} Var MessageStr : String; Begin MessageStr := 'Hello world!'; Writeln(MessageStr);

End.

La prima riga di questo codice, dichiara il nome del programma. La direttiva {$APPTYPE CONSOLE} indica al compilatore che si tratta di una applicazione console, da eseguire dalla riga comandi. La riga successiva dichiara una variabile di tipo stringa. Successivamente, all'interno del blocco Begin..End., che costituisce il blocco principale del programma, viene dapprima assegnata alla variabile MessageStr il valore contenuto nella stringa 'Hello world!'. In ultimo con l'istruzione Writeln(MessageStr) il contenuto della variabile MessageStr viene visualizzato sullo schermo dopo di che il programma termina. Per compilare il programma dell'esempio, possibile, se Delphi stato correttamente installato e nella path di sistema riportato il percorso ..\BIN nella directory di installazione di Delphi, utilizzare il seguente comando: supponendo di aver chiamato il file del programma Hworld.Pas o Hworld.dpr possiamo scrivere DCC32 Hworld.pas Otterremo un file eseguibile di nome Hworld.exe che se eseguito stamper a video il testo 'Hello world!'. Questo non solitamente l'uso che si fa di Delphi. Delphi solitamente utilizzato per la realizzazione di applicazioni Windows con interfaccia grafica non eseguibili dalla riga di comando. Anche la struttura del file di progetto leggermente differente e non contiene generalmente codice effettivo dell'applicazione se non poche chiamate ai metodi definiti nelle unit. Vediamo un attimo alcune particolarit che avrete sicuramente gi notato. Prima di tutto il segno ";" (punto e virgola) che delimita la fine di una istruzione. Il Pascal, come il C++ ed altri linguaggi simili, necessita di questo segno di punteggiatura per riconoscere il termine di una istruzione. Un'altra particolarit il "." (punto) che delimita la fine del blocco principale del codice. Come vedremo pi avanti, solamente il blocco principale delle unit avr il punto dopo l'End del blocco principale, i blocchi delle Funzioni e delle Procedure utilizzeranno normalmente il punto e virgola. Ma passiamo ora ad esaminare in maniera pi approfondita la sintassi e la struttura del linguaggio.
LEZIONE 5:

Panoramica sull'IDE

L'IDE (Integrated Development Environment) di Delphi ci mette a disposizione molti strumenti utili per la stesura del codice e per la realizzazione visuale delle interfacce grafiche dei nostri programmi.

Fig. 1- La finestra principale dell'IDE di Delphi Quello che colpisce non appena si avvia l'IDE di Delphi per la prima volta, che questo non ricopre interamente il desktop. Ci permette di vedere ci che dietro l'IDE senza ridurre l'IDE stesso ad icona.

Fig. 2 - Come appare l'IDE di Delphi nel desktop di Windows

L'IDE si compone principalmente di quattro sezioni fondamentali: la finestra principale (fig. 1), l'Object Inspector, il Code Editor, il Form Designer. La finestra principale, contiene i menu, le toolbar con i tasti per l'accesso rapido alle funzioni pi usate (personalizzabile), la Component Palette contenente tutti i componenti visuali e non installati in Delphi. Quest'ultima pu essere organizzata a piacimento aggiungendo o togliendo pagine e spostando i componenti da una pagina all'altra. Per esempio, si potrebbe aggiungere una pagina "Help" dove andremo a riporre tutti quei componenti che serviranno ad implementare il sistema di aiuto in linea delle nostre applicazioni. Per fare ci, si pu cliccare con il tasto destro sulla Component Palette e scegliere la voce Properties che visualizzer una finestra di dialogo tramite la quale sar possibile procedere alla personalizzazione. L'IDE permette di personalizzare la disposizione e le caratteristiche degli strumenti attivi dando la possibilit di salvare queste impostazioni come configurazioni del desktop per essere richiamate alla necessit. Per esempio, come visibile nella figura 2, nel mio ambiente di sviluppo ho definito una configurazione "Edit" che utilizzo durante la stesura del codice. Si potrebbe definire anche una configurazione "Debug", da attivare quando si effettua il debug di una applicazione, che visualizzi gli strumenti per il debug e nasconda quelli non necessari a questo scopo. Una volta salvate, queste configurazioni sono richiamabili tramite il menu a tendina posto nella toolbar a fianco a quella dei menu. L'Object Inspector quello strumento che ci permette di modificare a design time la propriet dei componenti che inseriamo nelle Form dell'applicazione. Ci permette altres di associare agli eventi dei componenti il codice opportuno.

Fig. 3 - L'Object Inspector di Delphi Il Code Editor un editor ASCII completo, ricco di utili funzionalit, tramite il quale si agisce sul codice sorgente dell'applicazione.

Fig 4 - Il Code Editor Il Form Designer lo strumento che, durante la fase di sviluppo, ci permette di disporre e configurare i componenti nelle nostre Form. A livello visuale, insieme all'Object Inspector, costituisce lo strumento fondamentale per lo sviluppo delle nostre applicazioni.

Fig. 5 - Il Form Designer Vedremo questi strumenti pi approfonditamente nei paragrafi seguenti. Oltre a questi strumenti sono da menzionare: il Project Manager per la gestione dei progetti e di tutti i files che lo compongono, la To-Do List che permette di definire all'interno del codice cosa c' da fare (appunto To-Do!), il Class Explorer che permette di visualizzare l'albero "genealogico" di una classe, ovvero tutti gli oggetti di cui quella classe eredita propriet e metodi. Una novit importante da segnalare, rispetto alle versioni precedenti, la possibilit che offre il Project Manager di aprire pi progetti contemporaneamente. Ci risulta molto utile quando abbiamo a che fare con applicazioni composte anche da DLL. Un'altra funzionalit messaci a disposizione dall'IDE di Delphi l'Object Repository, ovvero uno strumento che ci permette di creare una specie di database contenente schede, finestre di dialogo, moduli dati, wizard, DLL atte a semplificare lo sviluppo di applicazioni. Questo disponibile dal menu File scegliendo la voce New. Cos facendo apparir una finestra contenente le strutture base di per vari oggetti. In pratica questo strumento rappresenta una interfaccia per gestire templates di oggetti. Ci permette ad esempio di creare applicazioni con la stessa struttura di finestre, magari centralizzando il codice in comune. possibile espandere la dotazione di templates esistenti aggiungendone di personalizzati; ci possibile cliccando con il tasto destro del mouse sulla finestra dell'Object Repository e scegliendo la voce Properties, oppure dal menu Tool scegliendo la voce Repository. Questa la dotazione standard dell'IDE di Delphi, ma non finisce qui La struttura dell'IDE aperta, ovvero possibile scrivere applicazioni o add-on per l'interfaccia di sviluppo stessa. Per esempio, come potete notare dalle figure precedenti, nel mio ambiente di sviluppo ho installato una serie di add-on chiamati G-Experts che aggiungono molte funzionalit utili all'interfaccia di sviluppo. In poche parole, se l'IDE manca di qualche funzionalit, potrete sempre inserirla voi scrivendo del codice ad hoc. Questo argomento non verr trattato nel corso, ma esistono libri, come quelli di Marco Cant, che spiegano come creare estensioni per l'IDE di Delphi. Tra gli strumenti aggiuntivi, forniti con Delphi, sono presenti anche un Image Editor molto semplice per realizzare le icone dei componenti e delle applicazioni, il Database Desktop (per le versioni Professional) per l'esplorazione e la manipolazione di database ed il Package Editor. In ultimo da aggiungere che la maggior parte degli strumenti nominati precedentemente possono essere disposti a piacere all'interno dell'IDE. Per esempio possibile inserire nella finestra che contiene l'Object Inspector anche il Project Manager come possibile vedere dalle figure. Questi strumenti sono di tipo dockable, ovvero si comportano come le barre degli strumenti di Word e quindi possibile, trascinandoli, posizionarli in una finestra o nell'altra oppure lasciarli liberi come finestra a s nel desktop.
LEZIONE 6:

L'Object Inspector

L'Object Inspector , insieme al Form Designer, lo strumento pi utilizzato, a livello visuale, per la creazione delle applicazioni. Tramite questo strumento si possono gestire tutte le propriet (disponibili a design time) ed eventi dei componenti visuali e non. Anche questo strumento ampiamente personalizzabile. Una delle personalizzazioni possibili quella di visualizzare l'elenco delle propriet ordinate per nome o raggruppate per categorie.

Fig. 1 - Le propriet del componente Button1 visualizzate nell'Object Inspector Come possibile vedere dalla figura 1 tutte le propriet impostabili a design time, sono disponibili nell'Object Inspector. Le pagine di questo si dividono, sia per quanto riguarda le propriet che per i gestori degli eventi, in due parti: sulla sinistra sono riportati i nomi delle propriet e sulla destra i rispettivi valori. L'Object Inspector capace di visualizzare ed impostare numerosi tipi di dati. Per quanto riguarda i dati numerici e le stringhe di caratteri, questi vengono rappresentati cos come sono (per esempio la propriet Height del componente e la propriet Name). Per tipi di dati pi complessi, come per esempio il font, che rappresentato da un classe TFont, l'Object Inspector aggiunge davanti al nome della propriet un segno "+". Cliccando su di esso, si espandono le propriet relative alla classe TFont istanziata per l'Oggetto Button1.

Fig. 2 - La propriet Font del componente Button1.

Quando a fianco di un valore presente un tasto con tre puntini ( ) significa che l'Object Inspector ha un Editor di Propriet ad hoc per quel tipo di valore. Cliccando questo tasto si apre una finestra che permette di modificare il valore della propriet e di riportarlo automaticamente nell'Object Inspector. La stessa operazione pu essere effettuata premendo la combinazione di tasti Ctrl + Invio dalla tastiera. Sempre come esempio, quello riportato di seguito l'editor di propriet per il tipo TFont.

Fig. 3 - L'editor di propriet per il tipo TFont.

Quando invece al posto del tasto presente il tasto ci indica che possibile scegliere il valore da una lista predefinita di valori. Infatti cliccando questo tasto, otteniamo ci che visibile nella figura 4

Fig. 4 - Scelta del valore dalla lista di valori predefiniti.

Questo possibile in quanto la propriet ModalResult visibile in figura di tipo TModalResult che altro non che un tipo enumerato. Tutti i valori dei tipi enumerati sono selezionabili con la modalit precedentemente vista. anche possibile utilizzare i tasti freccia della tastiera per selezionare un valore oppure facendo doppio click con il tasto sinistro del mouse i valori si avvicenderanno secondo l'ordine riportato nell'elenco. Per esempio, prendiamo in esame la figura 4. Il valore selezionato per la propriet ModalResult mrNone; senza visualizzare la tendina con l'elenco potremmo fare doppio click con il mouse sul valore per ottenere mrOk; se continuiamo a fare doppio click avremo mrCancel e cos via. C' ancora un altro tipo di dato che l'Object Inspector in grado di gestire; il tipo Set, insieme. Quando una propriet di tipo Set, il suo valore racchiuso tra parentesi quadre. In pratica vengono rappresentati tra parentesi gli elementi che sono inclusi nell'insieme.

Fig. 5 - Come appare una propriet di tipo Set nell'Object Inspector

Le propriet Set, presentano, come le propriet di tipo Class, un segno "+" davanti al nome della propriet; cliccando su di esso viene mostrato l'elenco degli elementi che possono appartenere all'insieme. Per indicare che un particolare elemento incluso nell'insieme, impostare il suo valore a True e viceversa per escluderlo impostarlo a False. Passiamo ora a visionare la pagina dell'Object Inspector dedicata alla manipolazione dei gestori di eventi.

Fig. 6 - La pagina relativa ai gestori degli eventi. Nella figura 6 riportata la pagina degli eventi relativi ad un componente Button (TButton). Per definire un gestore di eventi per un particolare evento, ci sono vari modo; fare doppio click con il tasto sinistro del mouse sull'evento corrispondente, scrivere il nome del gestore di evento nella casella a destra del nome dell'evento e premere invio, scegliere un gestore di evento tra quelli gi definiti agendo sul tasto con la freccia. Seguendo il primo modo l'IDE creer per noi un gestore di evento il cui nome sar composto dal nome del componente pi il nome dell'evento a cui si riferisce. Ovviamente verr aggiunto al codice tutto ci che necessario per creare il gestore di evento. Vediamo un esempio. Sempre con il nostro componente Button di nome Button1, facendo doppio click sul evento OnClick nell'Object Inspector, verr creato automaticamente un gestore per l'evento OnClick di nome "Button1Click" e verr aggiunto il seguente codice a quello gi esistente

procedure TForm1.Button1Click(Sender: TObject); begin end; All'interno del blocco Begin..End di questa procedura andr solamente inserito il codice che dovr essere eseguito nel momento in cui si premer il componente Button. Insieme al codice sopra riportato, viene, ovviamente, aggiunta la dichiarazione della procedura "Button1Click" come procedura di tipo pubblico all'interno della classe TForm1.

type

TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations }

Nella parte superiore dell'Object Inspector presente presente una casella combinata dalla quale possibile scegliere il componente di cui modificare le propriet.
LEZIONE 7:

Il Form Designer

Il Form Designer di Delphi quello strumento che permette, a livello visuale, di creare le interfacce grafiche delle applicazioni semplicemente disponendo i componenti su di esso ed impostando le loro propriet. Questo gestisce sia componenti visuali che non visuali. Gli oggetti non visuali, (ovviamente in fase di sviluppo sono visibili!) sono rappresentati da dei riquadri assomiglianti agli speed buttons delle toolbars recanti per una icona che stata definita durante lo sviluppo del componente. Un esempio di oggetto non visuale l'oggetto Timer (TTimer) che in fase di sviluppo appare cos .

Lavorare con il Form Designer semplicissimo, sufficiente selezionare un componente dalla Component Palette, situata nella finestra principale dell'IDE e posizionarlo all'interno di quella che sar la nostra form. Il posizionamento pu avvenire in vari modi: si pu cliccare sul componente nella Component Palette e successivamente cliccare nella form alla posizione in cui lo vogliamo inserire; si pu cliccare il componente nella Component Palette quindi cliccare nella form alla posizione voluta e trascinare per definirne le dimensioni. Se non si trascina durante il posizionamento del componente, questo assumer una la dimensione standard definita la momento dello sviluppo. Una volta che il componente si trova nella form, possiamo spostarlo, ridimensionarlo, cambiare le sue propriet. Per posizionare pi componenti dello stesso tipo possibile selezionare selezionarlo dalla Component Palette tenendo premuto il tasto Shift sulla tastiera. Cos facendo intorno ad esso nella Component Palette apparir un rettangolo di colore blu che indicante che stiamo posizionando pi componenti di quel tipo. Per disattivare questa modalit di inserimento basta cliccare sul tasto con la freccia nella Component Palette. Un'altro modo per inserire componenti in un form quello di fare doppio clic sul tasto relativo nella Component Palette; cos facendo il componente verr posizionato al centro della form.

Fig. 1 - Il Form Designer con alcuni componenti

Per spostare un componente sufficiente cliccare su di esso e mantenendo cliccato trascinarlo nella posizione voluta. Per ridimensionare un componente, invece, occorre prima selezionarlo e quindi trascinare una delle otto maniglie che compaiono attorno ad esso. La logica la stessa utilizzata da programmi di grafica tipo CorelDraw e simili. Nella figura 1 riportata l'immagine del Form Designer con alcuni componenti. possibile notare le maniglie di ridimensionamento attorno al componente Edit1. I puntini visibili sullo sfondo della form sono relativi alla griglia di posizionamento, ci permette di allineare comodamente i componenti in quanto, grazie alla propriet snap, questi si agganciano automaticamente, con l'angolo superiore, a questi punti. Quando si sposta un componente, appare un riquadro che ci informa sulla posizione dell'angolo superiore sinistro del componente rispetto all'angolo superiore sinistro della form, il tutto espresso in pixel.

Fig. 2 - Spostamento di un componente La stessa cosa accade quando si ridimensiona un componente; questa volta per vengono visualizzate le dimensioni che assume lo stesso espresse sempre in pixel e rappresentano la lunghezza e l'altezza del componente.

Fig. 3 - Ridimensionamento di un componente Facendo click con il tasto destro del mouse su un componente o sulla form appare un menu le cui voci permettono di allineare i componenti, di definirne l'ordine di tabulazione (la sequenza con cui viene assegnato il fuoco ai componenti spostandosi con il tasto Tab della tastiera), cambiare lo Z-Order dei componenti (portarli in primo piano o sullo sfondo), modificare l'ordine di creazione dei componenti.

Fig. 4 - Il menu contestuale. In questo menu possono essere presenti anche altre voci (quelle visualizzate sono quelle standard) in base all'oggetto specificato poich questo potrebbe avere un Component Editor personalizzato che semplifichi la configurazione delle sue propriet. Potrebbero essere presenti anche voci che permettono di impostare, senza ricorrere all'Object Inspector, alcune propriet come per esempio per un componente TreeView (TTreeView), dove in testa al menu contestuale appare la voce "Items Editor..." che permette di aprire un editor ad hoc per l'inserimento, la cancellazione e ridisposizione degli elementi dell'albero. All'interno del Form Designer possibile selezionare pi oggetti per impostarne le propriet comuni agli stessi valori. Ho parlato di propriet comuni in quanto al momento della selezione di pi componenti, anche di tipo differente, l'Object Inspector visualizzer ovviamente solo le propriet impostabili su tutti i componenti selezionati. Per selezionare pi componenti si pu selezionare il primo di essi e tenendo premuto il tasto Shift sulla tastiera selezionare gli altri. anche possibile utilizzare solamente il mouse per selezionare pi componenti. Se i componenti sono posizionati in modo da essere all'interno di una area rettangolare, si pu, cliccare sulla form e trascinare. Cos facendo si vedr un rettangolo che avr come origine il punto in cui si iniziato a trascinare e come dimensione quella da voi definita trascinando il cursore del mouse. A questo punto rilasciando il tasto del mouse avremo selezionato tutti i componenti presenti all'interno del rettangolo di selezione. Una volta selezionati i componenti possono essere deselezionati cliccando semplicemente sulla form o sottraendoli alla selezione tenendo sempre premuto il tasto Shift sulla tastiera e cliccando su di essi. Gli spostamenti e i ridimensionamenti sono possibili anche tramite la tastiera, tramite i tasti freccia e al combinazione del tasto Ctrl o del tasto Shift. Per spostare un componente tramite i tasi freccia, necessario prima selezionarlo quindi tenendo premuto il tasto Ctrl ed agire sui tasti freccia per spostarlo nelle direzioni rappresentate dai tasti. Per ridimensionare un componente procedere in maniera analoga allo spostamento agendo per sul tasto Shift. Questa modo di operare risulta comodo per un posizionamento ed un ridimensionamento preciso dei componenti in quanto le variazioni avvengono a scatti di un pixel per volta. Una volta posizionati i componenti all'interno della form, onde evitare di spostarli inavvertitamente, possibile bloccarli con la funzione "Lock Controls" dal menu Edit.
LEZIONE 8:

Il Code editor (parte prima)

Il Code editor di Delphi un editor ASCII molto completo, ricco di molte funzioni utili nella stesura del codice. Quando si impostano le propriet degli oggetti a livello visuale, l'ambiente integrato di Delphi apporta, a livello di codice Pascal, le modifiche necessarie. Per esempio quando aggiungiamo ad una Form un componente prendendolo dalla "Component Palette", viene aggiunta automaticamente la dichiarazione di quell'oggetto nel codice. Le caratteristiche fondamentali del "Code Editor", sono il "Syntax Highligth", il "Class Completion", il "Code Insight", il "Code Explorer", la possibilit di aggiungere dei segna posto (bookmarks) per ritornare velocemente ad una certa posizione all'interno del codice, i "Tool Tips" che forniscono informazioni sia durante la fase di debug (valori delle variabili, ) che in fase di stesura del codice, fornendo per esempio aiuto sulla sequenza e sul tipo di parametri da passare ad una funzione o procedura. Altra funzionalit che ritengo

importante, se non fondamentale, quella dell'aiuto in linea. Posizionando il cursore su una porzione del codice e premendo F1, si accede direttamente all'help relativo al testo su cui posizionato il cursore.

Fig. 1 - Ecco come appare la schermata del Code Editor Come possibile vedere dall'immagine, nel code editor compare gi del codice che stato inserito al momento della creazione di una nuova applicazione. Infatti quando si crea una nuova applicazione, l'ambiente di sviluppo crea come sia il file di progetto che una Form (o finestra) principale. N.B. Nelle immagini che vedrete in questo corso, potreste accorgervi di configurazioni e impostazioni che potrebbero differire dal vostro ambiente di sviluppo. Per esempio, la combinazione di colori che vedete nel Code Editor "Classic" ovvero segue i colori del vecchio ambiente di sviluppo Turbo Pascal. Ritengo sia la migliore sia per il contrasto a livello visivo, sia perch essendo lo sfondo di colore scuro e lavorandoci parecchie ore non stanca. La configurazione standard presente di default dopo l'installazione prevede uno sfondo bianco. Torno a ripetere che in questo corso si presuppone di avere a disposizione almeno la versione 5 Professional di Delphi. Il codice visibile nella figura fa riferimento alla form riportata di seguito

Fig. 2 - Main form del programma non contenente alcun componente Come potete vedere, nel codice definita una classe TForm1 e una variabile Form1 di tipo TForm1. In pratica l'ambiente di Delphi ha gi predisposto per noi le basi per cominciare a realizzare la nostra applicazione.

Ora, se aggiungiamo un componente alla nostra Form, vedremo che il codice viene automaticamente aggiornato. Per esempio aggiungiamo dalla Component Palette un componente Button e vediamo cosa succede

Fig. 3 - La nostra Form con aggiunto il componente Button Ecco cosa viene automaticamente aggiunto al codice

Fig. 4 - La definizione dell'oggetto Button aggiunto alla nostra Form. Quando inseriamo un componente in una form, viene automaticamente aggiornato il codice nel Code Editor e possiamo anche notare che, come in precedenza per la Form, gli viene anche assegnato un nome in base al numero di componenti dello stesso tipo, con lo stesso nome, gi presenti nella Form stessa. Infatti il nostro Bottone all'interno del codice Delphi viene identificato tramite il nome di Button1. Questa solo il minimo che pu fare per noi l'ambiente di sviluppo di Delphi, infatti come avrete notato in Fig. 1, accanto all'area predisposta per l'editing del codice, vi un riquadro contenente una struttura ad albero, che ci fa vedere le varie sezione del codice in forma semplificata. Questo riquadro il Code Explorer che ci permette di navigare all'interno del codice senza scorrere il testo del codice, ma semplicemente selezionando la voce che ci interessa tra quelle riportate. Quando si inserisce del codice nell'editor, viene automaticamente aggiornata la struttura del Code Explorer. Questo strumento , come tutti gli strumenti messi a disposizione dall'IDE, personalizzabile permettendo di scegliere cosa visualizzare nell'albero e cosa no.

Fig. 5 - Ramo TForm1 del Code Explorer espanso mostra il componente Button1 da noi aggiunto. Facendo doppio clic con il mouse nel Code Explorer alla voce Button1, vedremo che il cursore nel Code Editor si posizioner dove nel codice definito il nostro componente Botton1. Ora espandiamo tutti i rami visualizzati nel code Explorer vediamo le corrispondenze con il codice.

Fig. 6 - Tutti i rami del Code Explorer espansi; possiamo vedere le corrispondenze con il codice nella destra Tutti gli elementi che abbiamo visto all'interno del codice sono riportati anche nella struttura ad albero del Code Explorer. Anche le unit incluse nella clausola Uses vengono riportate il tutto ordinato alfabeticamente. Ci risulta molto utile per ricercare funzioni, procedure, variabili ecc. e per posizionarsi rapidamente nel codice soprattutto in presenza di unit contenenti migliaia di righe di codice.
LEZIONE 9:

Il Code editor (parte seconda)

Tramite il Code Explorer anche possibile inserire definizioni di variabili o funzioni o procedure. Cliccando con il tasto destro del mouse e scegliendo la voce New viene aggiunto un nuovo item editabile nel ramo selezionato.

Fig. 7 - Aggiunta di un nuovo item al Code Explorer. Per inserire una funzione o una procedura, utilizzare la stessa sintassi che si utilizzerebbe per dichiararla all'interno del Code Editor. Riguardo al Code Editor bisogna ancora dire alcune cosette. Come visibile in tutte le figure rappresentanti il Code Editor, nella parte superiore della finestra che lo contiene sono presenti delle "linguette" (sulla sinistra) e delle freccie tipo Internet Explorer. La funzionalit di quest'ultime la stessa di quelle di Internet Explorer; infatti ci permettono di navigare nel codice seguendo gli spostamenti che abbiamo gi effettuato. N.B. Non corrisponde all'Undo e Redo per le modifiche apportate al codice! Per quanto riguarda le linguette, esse rappresentano i files aperti nell'Editor e ci permettono di passare ad editare un file o l'altro semplicemente cliccando su di esse. Per esempio, se nel nostro editor avessimo aperto una unit di nome Unit2, vedremo a fianco della linguetta Unit1 un'altra linguetta recante il nome della nuova unita aperta. Cliccando su di essa visualizzeremmo il codice in essa contenuto. Rimane ancora una parte relativa all'editor che non stata ancora trattata. quel piccolo riquadro presente in fondo alla finestra. Quest'area contiene, ovviamente al momento opportuno (dopo la compilazione per esempio), messaggi di errore, informazioni riguardanti la compilazione del codice o errori segnalati dal Class Completion. Sotto quest'area presente una barra di stato che contiene informazioni riguardo la posizione del cursore (riga: colonna), se sono state apportate modifiche al codice (modified) e lo stato della modalit di inserimento dei caratteri (insert, overwrite). Il Class Completion un'altra delle funzionalit, a mio parere molto comoda, che permette di appunto completare, in maniera quasi automatica, il codice che si sta scrivendo. Infatti, digitando il nome di una classe, o di una sua istanza, per esempio il nostro Button1 nelle figure precedenti, seguito dal punto (.), il nostro editor ci propone un elenco di propriet e metodi appartenenti a quella classe. possibile scegliere la voce che ci interessa tramite le frecce della tastiera o cominciare a digitare parte della propriet o metodo che ci interessa. Cos facendo, tramite un ricerca incrementale, l'editor ci porta alla voce di nostro interesse; battendo invio, l'editor terminer per noi la scrittura del codice.

Fig 8 - Il Class Completion in azione Un'altra funzionalit che merita di essere citata, quella che ci permette di saltare alla definizione di un identificatore come si farebbe cliccando su un collegamento ipertestuale in un Web Browser. Infatti, tenendo premuto il tasto Ctrl e puntando un identificatore nel codice, quest'ultimo si trasforma in un collegamento ipertestuale a tutti gli effetti. Cliccandolo, verremo trasferiti in quella parte di codice contenete la dichiarazione dell'identificatore puntato, anche se questo dichiarato in un'altra unit.

Fig. 9 - Un collegamento ipertestuale sul tipo di Button1 Un ultima caratteristica molto interessante del Code Editor la possibilit di definire dei templates di codice per richiamarli all'occorrenza. Tramite la voce Editor Properties->Code Insight accessibile dal menu Tool possibile definire il testo dei templates.

Fig. 10 - Definizione dei Code Template. Una volta fatto ci, possibile richiamare i code templates durante la scrittura del codice tramite la combinazione di tasti Ctrl + J; verr visualizzata una lista dei templates definiti. Cominciando a scrivere il nome di un template ed utilizzando la combinazione di tasti Ctrl + J verr visualizzata la lista dei templates corrispondenti alla parte di nome gi digitata e qualora si fosse digitato per intero il nome del template o alla parte di nome digitata corrispondesse solamente un template, questo verrebbe subito riportato nel codice. Per esempio, scrivendo "cases" e premendo la combinazione di tasti Ctrl + J apparir il seguente blocco di codice Case of : ; : ; End; con il cursore posizionato tra Case ed of, cio possibile definire nel template la posizione da cui si incomincer a digitare il codice mancante.
LEZIONE 10:

Il Project Manager

Il Project Manager strumento raggruppa in s tutti i file che compongono un progetto. Quest'ultimo pu essere composto anche da pi progetti. Questo potrebbe essere il caso di un'applicazione che includa delle DLL o pi eseguibili. I vari progetti vengono racchiusi in gruppi. Uno solo dei progetti appartenenti ad un gruppo pu essere attivo nello stesso momento e questo sar il progetto su cui si andr ad operare quando per esempio di proceder alla compilazione del codice.

Fig. 1 - Il Project Manager Nella figura precedente questo strumento inserito nella stessa finestra dell'Object Inspector ma anche possibile averlo come finestra a s o inserito nel Code Editor. Ecco come apparirebbe solo soletto.

Fig. 2 - Il Project Manager in una finestra a s Fondamentalmente si compone di un albero organizzato in gruppi e progetti. Nella figura 2 visibile il gruppo di progetto ProjectGroup1 contenente il progetto Project1. A fianco sono riportati i percorsi in cui sono stati salvati i file. Nella parte superiore presente una casella combinata che permette la selezione del progetto attivo e accanto alcuni bottoni che permettono di aggiungere nuovi progetti, di rimuoverli o di rendere attivo il progetto selezionato nell'albero. possibile anche operare tramite il menu contestuale che si attiva facendo click con il tasto destro del mouse sulla voce che interessa.

Fig. 3 - I vari menu contetsuali che appaiono cliccando sulle diverse voci dell'albero (Da sinistra: Project Group, Project, File del progetto, file sorgente ed altri) Le ultime tre voci sono presenti in tutti i menu e permettono di visualizzare o nascondere parti dell'interfaccia, come la Toolbar e la Status Bar, o di attivare/disattivare la funzionalit di docking che permette di inserire il Project Manager in altre finestre dell'IDE. Per aggiungere files al progetto anche possibile trascinarli da Esplora Risorse di Windows. Tramite questo strumento possibile gestire le opzioni relative al progetto selezionando la voce Options dal menu contestuale del progetto oppure scegliendo la stessa voce dal menu Project della finestra principale dell'IDE. Tra le opzioni possibile specificare informazioni riguardanti la versione dell'applicazione, direttive del compilatore, i percorsi in cui il compilatore andr a cercare le unit da compilare, scegliere l'icona dell'applicazione, il suo titolo, quale sar la finestra principale del programma, quali finestre saranno create automaticamente all'apertura del programmai, le opzioni del linker, quali packages sono utilizzati dall'applicazione e se l'applciazione deve essere compilata utilizzando i packages di run-time.

Fig. 4 - La finestra di dialogo opzioni del progetto.


LEZIONE 11:

Sintassi e Struttura del codice

Abbiamo visto in precedenza un esempio di applicazione console. Ora vedremo un altro esempio riguardante per un'applicazione per Windows ovvero quello che ci interessa di pi e per cui Delphi nato. Questo il codice: Program Editor;

Uses Forms, REAbout in 'REABOUT.PAS' {AboutBox}, REMain in 'REMAIN.PAS' {MainForm}; {$R *.RES} Begin Application.Title := 'Text Editor'; Application.CreateForm(TMainForm, MainForm); Application.Run; End.

Come potete vedere il codice leggermente diverso, come contenuto, da quello che abbiamo visto in precedenza ma la struttura la stessa. Abbiamo una intestazione del programma "Program Editor", una clausola "Uses" contenente i riferimenti per l'inclusione di altre unit nel programma, ed il blocco principale "Begin..End.". Solitamente in questo blocco sono presenti solamente chiamate a metodi dell'oggetto Application del progetto. Ogni progetto Delphi ha una variabile Application di tipo TApplication (o come vedremo TWebApplication, TserviceApplication in base al tipo di progetto scelto) che permette di accedere alle propriet e metodi base dell'applicazione stessa. Quindi la struttura di un file di progetto di Delphi pu essere riassunta cos (in rosso i nostri commenti): Program NomeProgramma; //Intestazione del programma Uses Unit1, Unit2, , UnitN; //Clausola uses per l'inclusione di altre unit nel programma {$R *.RES} Begin //Blocco principale del progetto ... End.

Struttura e Sintassi delle unit


Ogni unit contenuta in un file .PAS e pu contenere dichiarazioni di tipi, variabili, funzioni, procedure. Come il file di progetto, anche la unit inizia con una intestazione (che ha inizio con la parola chiave Unit e dal nome della unit stessa) seguita da altre sezioni come riportato di seguito: Unit Unit1; {Intestazione} Interface Uses {Lista di unit}

Implementation Uses {Lista di unit} {Codice per la definizione di tipi, variabili, funzioni e procedure} Initialization {Codice di inizializzazione della unit} Finalization {Codice di finalizzazione della unita} End. Come si pu vedere abbiamo: una sezione definita "Interface" che conterr le definizioni di tipi, variabili, dichiarazioni di funzioni e procedure che si vuole siano accessibili da altre unit; una sezione definita "Implementation" che conterr il codice "privato" della unit, non accessibile dall'esterno, consistente in dichiarazioni di tipi, variabili, funzioni, procedure e il codice di implementazione delle funzioni o procedure stesse. Le sezioni "Initialization" e "Finalization" sono facoltative e servono ad eseguire codice per l'inizializzazione della unit e per la finalizzazione della unit rispettivamente al momento del caricamento della unit stessa e al momento del rilascio. Se in una clausola uses sono presenti pi di una unit, la sequenza con cui saranno eseguite le sezioni "Initialization" sar la stessa di quella della clausola uses. Per esempio se la clausola uses riporta uses unit1, unit2, unit3; l'ordine con cui verranno eseguite le sezioni di inizializzazione sar: unit1, unit2, unit3. Per quanto riguarda le sezioni di finalizzazione l'ordine sar inverso rispetto a quello di inizializzazione. Con riferimento all'esempio di prima avremo: unit3, unit2, unit1. Riguardo alla clausola uses di un programma o libreria, come nell'esempio di programma Windows che abbiamo visto in precedenza, c' la possibilit di indicare il percorso in cui si trova la unit da includere nel progetto specificando dopo il nome della unit la parola chiave "in" seguita dal percorso assoluto o relativo racchiuso tra apici. Per esempio mettiamo che la unit "Unit1.pas" nel seguente progetto si trovi in un percorso diverso da quello in cui sitrova il file principale del progetto avremo: Program Program1; Uses Unit1 in 'c:\progetti\mioprogetto2\Unit1.pas';

In questo caso ho fatto riferimento ad un percorso assoluto ma avrei potuto specificare un percorso relativo. Per esempio, se il Program1 si trovasse nella directory "c:\progetti\mioprogetto1" e all'interno ci fosse una directory chiamata "extra" in cui ripongo delle unit, nell'esempio precedente avrei potuto scrivere: Uses Unit1 in 'c:\progetti\mioprogetto1\extra\Unit1.pas'; oppure Uses Unit1 in '..\extra\Unit1.pas'; considerando sempre il fatto che il file principale del programma si trovi in "c:\progetti\mioprogetto1".

Un'ultima cosa riguardo le unit. Come facilmente intuibile, nella clausole uses di una unit posso fare riferimento ad altre unit. In questo caso bisogna fare attenzione ai "riferimenti circolari", ovvero fare attenzione a non far riferimento in una unit ad un'altra che a sua volta si riferisce alla prima. Leggendo potrebbe venire in mente che chi sta scrivendo questo corso sia un pazzo. In effetti pi semplice capire ci che ho detto con un esempio. Program Program1; Uses Unit1; Const Pippo = Pluto; ... Unit Unit1; Interface Uses Unit2; Const Pluto = Paperino; ...

Unit Unit2; Interface Const Paperino = 1; ... Nell'esempio precedente non ci sono problemi di "riferimenti circolari" e tutto funziona bene. Program Program2; Uses Unit1; Const Pippo = Pluto; ...

Unit Unit1; Interface Uses Unit2; Const Pluto = Paperino; ...

Unit Unit2; Interface

Uses Unit1; Const Paperino = 1; ... In questo esempio invece abbiamo un problema di "riferimento circolare". Infatti la Unit1 fa riferimento nella sua clausola uses alla Unit2 che a sua volta fa riferimento nella sua clausola uses alla Unit1. Al momento della compilazione del progetto questo errore viene segnalato dal compilatore. Nel caso in cui sia necessario utilizzare questa struttura di progetto, per evitare un "riferimento circolare" tra la Unit1 e la Unit2 sufficiente spostare la clausola uses con il riferimento alla Unit 1 della Unit2 nella sezione "Implementation" come riportato di seguito: Program Program2; Uses Unit1; Const Pippo = Pluto; ... Unit Unit1; Interface Uses Unit2; Const Pluto = Paperino; ... Unit Unit2; Interface Const Paperino = 1; Implementation Uses Unit1; ... Se abbiamo due unit che dichiarano una variabile, costante, tipo, routine con lo stesso nome, possiamo accedere alla variabile, costane, tipo, routine che ci interessa fornendo anche il nome della unit nel modo seguente: Unit Unit1; Interface Const Pluto = 1; ... Unit Unit2;

Interface Const Pluto = 2; ... Uses Unit1, Unit2; ... Topolino := Unit1.Pulto; //La variabile Topolino assumer valore 1 Topolino := Unit2.Pluto; //La variabile Topolino assumer valore 2 ... N.B. Chi conosce gi il C++, pu notare che le unit del Delphi o pi in generale dell'Object Pascal corrispondono ai "NameSpace" e che la clausola uses corrisponde alla direttiva "#Include".
LEZIONE 12:

La sintassi (parte prima)

L'Object Pascal usa il set di caratteri ASCII, comprendente le lettere dalla A alla Z e dalla a a alla z, le cifre da 0 a 9 e gli altri caratteri standard. Non fa distinzione tra maiuscole e minuscole, come il C++ o il Java o Javascript. Tutti i caratteri quali lo spazio (ASCII 32) ed i caratteri di controllo, come il carattere di ritorno o di fine riga etc. (ASCII 0 a 31), vengono chiamati Blank. La pi piccola unit significativa di testo in un programma il token. Questo composto da una istruzione o da un'espressione o da una dichiarazione. Quindi un programma una sequenza di token separati da separatori (spazio o commento). quindi legale scrivere nel codice Pippo:=1;Pluto:=2; Per motivi di leggibilit e di convenzione, per, si preferisce utilizzare il formato seguente: Pippo := 1; Pluto : = 2; I token si suddividono in simboli speciali, identificatori, parole riservate, direttive, numerali, etichette, stringhe di caratteri. Quelli che seguono sono simboli speciali: #$&'()*+,-./:;<>=@[]^{} Anche la combinazione di alcuni di essi rientra nei simboli speciali: .. // (* (. *) .) := <> <= >= Non fanno parte dei simboli speciali i seguenti caratteri: ! "(doppio apostrofo) ? % \ _(sottolineatura)| (pipe)~ (tilde)

Identificatori

Gli identificatori indicano variabili, costanti, procedure, funzioni, tipi, etc. Possono avere una lunghezza qualunque e devono iniziare con una lettera o con un carattere di sottolineatura (_) e non possono contenere spazi. Se necessario, per una maggiore leggibilit del codice, si pu utilizzare il carattere sottolineatura (_). Alcuni esempi di identificatori: Count / Count / cOunt / counT / _Pippo / _pippo / Items_Count / Items_count / Search_for

Numerali
In Object Pascal i numeri reali ed interi possono essere rappresentati sia in forma decimale che in forma esadecimale. La prima forma composta da sequenze di numeri separati o no da punti e possono essere precedute dai segni + o - per indicarne il segno. Per i numeri interi non possibile utilizzare il punto (ovviamente, se sono interi!). Un esempio di numero intero 23989, +23989 oppure -56739. I numeri reali possono altres essere rappresentati in forma esponenziale inserendo all'interno della sequenze di numeri il carattere E o e seguito da un numero (positivo o negativo). Questo carattere sta ad indicare che il numero alla sua sinistra va moltiplicato per 10 elevato al numero alla sua destra. Per esempio: 7E-2 equivale a 7 x 10-2 12.25e6 e 12.25E+6 equivalgono entrambi a 12.25 x 106

Stringhe di caratteri
In Object Pascal le stringhe di caratteri sono racchiuse tra apostrofi ('). Due apostrofi che non racchiudono alcuna carattere identificano una stringa null. Ad esempio '' (occhio, sono due apostrofi adiacenti non le virgolette, quelle sopra il 2 nella tastiera!). Alcuni si chiederanno come possibile rappresentare all'interno di una stringa il carattere apostrofo visto che quest'ultimo delimita la stringa stessa. Bene, per rappresentare un apostrofo all'interno di una stringa basta ripeterlo due volte, per esempio 'L''anno scorso'. Una stringa composta solamente da un apostrofo appare cos ''''. Uno spazio, invece, apparir cos ' '. possibile inserire all'interno di una stringa di caratteri, sequenze di caratteri di controllo utilizzando il simbolo # seguito dal codice ASCII del codice stesso. Ad esempio, la stringa 'Questa stringa termina con un a capo ed avanzamento riga.'#13#10.

Commenti
Come in quasi tutti i linguaggi di programmazione, per documentare o "commentare" il codice sorgente, possibile inserire, tramite particolari costrutti, dei commenti. In Pascal esistono vari modi per inserire commenti all'interno del codice sorgente. Si pu far precedere il commento da //. Per esempio Var Pippo : Integer; //Commento In questo caso il commento terminer con la fine della riga del codice. Per utilizzare commenti su pi righe, si possono impiegare le parentesi graffe {}. Ad esempio

Unit Unit1; { Questo commento si sviluppa

su pi righe } ...

Direttive del compilatore


Le direttive del compilatore sono particolari direttive che non influiscono sull'esecuzione del programma ma sulla compilazione del codice sorgente. Queste sono dei commenti che hanno come primo carattere, all'interno delle parentesi graffe {} o dei simboli (* *), il carattere $ (dollaro). Un esempio di direttiva lo abbiamo gi visto nel primo esempio di programma all'inizio del corso {$APPTYPE CONSOLE} In questo caso la direttiva indica al compilatore che il programma dovr essere compilato per essere eseguito dalla console e non nell'ambiente grafico di windows. Esistono molte direttive per il compilatore e per un elenco completo si rimanda ai manuali del linguaggio od alla guida in linea.

Operatori
Un operatore si comporta come le funzioni che fanno parte del linguaggio. Per esempio se sia ha qualcosa del tipo (X + Y), che composta da due operandi (X , Y) e da un operatore (+), nel caso in cui X ed Y rappresentino dei numeri, l'espressione restituir la loro somma. In Object Pascal esistono vari tipi e categorie di operatori. Ci sono operatori aritmetici, per logica booleana, per la manipolazione logica a livello di bit, di stringa, di puntatore, per i set, relazionali e classe.

Operatori aritmetici
Segue un elenco degli operatori aritmetici definiti in Object Pascal:
Operatore + * / Div Mod Operazione Addizione Sottrazione Moltiplicazione Divisione (reale) Divisione (intera) Modulo Tipo Operandi Integer, real Integer, real Integer, real Integer, real Integer Integer Tipo Risultato Integer, real Integer, real Integer, real Real Integer Integer Esempio X+Y X -Y X*Y X/Y X div Y X mod Y

Come facilmente comprensibile, tutti gli operatori sopra riportati sono operatori binari, accettano cio due operandi. Per i prime quattro operandi non si sono problemi. Vediamo invece come si comporta l'operatore div. Questo operatore effettua la divisione tra operandi di tipo integer e restituisce un integer. In pratica questo operando restituisce il risultato di X / Y arrotondandolo all'intero per difetto. Vediamo un esempio: X / Y (con X = 9 e Y = 2) resituisce 4,5

X div Y (con X = 9 e Y = 2) resituisce 4 L'operatore mod restituisce il resto della divisione intera dei suoi operandi. Accetta operandi di tipo integer e il risultato di tipo integer. Con riferimento all'esempio precedente abbiamo che X mod Y (con X = 9 e Y = 2) resituisce 1 perch equivale a X - (X div Y) * Y = 9 - (9 div 2) * 2 = 1 (N.B. l'operatore di moltiplicazione, come altre che vedremo di seguito, ha la precedenza rispetto alla sottrazione quindi prima viene risolto il contenuto tra parentesi, poi viene effettuata la moltiplicazione tra il risultato precedente e 2 ed infine viene sottratto a 9 il risultato delle operazioni precedenti: 9 div 2 = 4; 4 * 2 = 8; 9 - 8 = 1) IMPORTANTE: da segnalare che nelle espressioni X / Y, X div Y, X mod Y, ove Y sia uguale a 0 (zero) si verifica un errore di divisione per zero L'operatore + e l'operatore - sono impiegati anche come operatori unari ovvero si applicano ad un solo operando. Essi servono a definire il segno di una espressione. Si applicano ad operandi di tipo integer o real e il risultato restituito dello stesso tipo dell'operando a cui si applicano ma con il segno opportunamente cambiato.

LEZIONE 13:

La Sintassi (parte seconda)

Segue un elenco degli operatori booleani definiti in Object Pascal:


Operatore Not And Or Xor Operazione Negazione Congiunzione Disgiunzione Tipo Operandi Tipo Risultato Esempio Boolean Boolean Boolean Boolean Boolean Boolean Boolean Not X X and Y X or Y X xor Y

Disgiunzione esclusiva Boolean

Poco c' da dire su questo tipo di operatori, essi seguono infatti le regole della logica booleana. Una considerazione importante va fatta sulle diverse modalit di valutazione che l'Object Pascal offre. Questa di default impostata sulla valutazione parziale, ma possibile impostare la valutazione totale utilizzando la direttiva del compilatore {$B+} e disattivarla con la direttiva {$B-}. La valutazione parziale da preferire perch garantisce tempi di esecuzione pi brevi, questo perch la valutazione dell'espressione viene eseguita rigidamente da sinistra verso destra interrompendola nel momento in cui la valutazione risulta completa. Questo tipo di valutazione si applica sia all'operatore and che all'operatore or. In pratica quando viene valutata una espressione del tipo X and Y e X assume valore False Y non viene valutata in quanto il risultato dell'espressione gi noto; infatti una espressione del tipo X and Y pu risultare True in un solo caso, ovvero quando X ed Y sono True, in tutti gli altri casi assume valore False.

Operatori logici con bit


Segue un elenco degli operatori logici applicabili ai bit definiti in Object Pascal:
Operatore Not And Or Xor Shl Operazione Negazione Congiunzione Disgiunzione Tipo Operandi Tipo Risultato Esempio Integer Integer Integer Integer Integer Integer Integer Integer Not X X and Y X or Y X xor Y X shl 3

Disgiunzione esclusiva Integer Spostamento a sinistra Integer

Shr

Spostamento a destra Integer

Integer

X shr 3

Gli operatori shl e shr eseguono uno spostamento di bit, pari al numero indicato dopo l'operando, rispettivamente a sinistra ed a destra. Per esempio, se X contiene il valore 20 (in binario 010100) X shl 1 restituir 40 (in binario 101000). Con riferimento all'esempio precedente, se al posto di shl ci avessimo shr il risultato ottenuto sarebbe stato 10 (in binario 001010). Quindi quando si utilizza l'operatore shl equivale a moltiplicare il primo operando per 2 elevato al secondo operando. Seguendo sempre l'esempio precedente questo equivale a X * 21, mentre nel caso dell'operatore shr l'equivalente operazione quella di divisione del primo operando per 21.

Operatori relazionali
Segue un elenco degli operatori relazionali definiti in Object Pascal:
Operatore = <> < > <= >= Operazione Uguaglianza Disuguaglianza Minore di Maggiore di Minore uguale a Tipo Operandi Vari Vari Vari Vari Vari Tipo Risultato Boolean Boolean Boolean Boolean Boolean Boolean Esempio X=2 X <>Y X<Y X>Y X <= 2 X >= 2

Maggiore uguale a Vari

Normalmente i tipi degli operandi devono essere compatibili, eccezion fatta per i tipi real ed integer che possono essere confrontati tra di loro. Per quanto riguarda le stringhe, vengono confrontate in base al set di caratteri ASCII esteso. Il confronto viene effettuato da sinistra verso destra, carattere per carattere. Ad esempio la stringa 'Delphi' minore della stringa 'Pascal' in quanto la lettera 'D' viene nel set di caratteri prima della lettera 'P'. Ovviamente qui la differenza tra le due stringhe immediata; se avessimo avuto una stringa 'Delphi' ed una stringa 'Delpho', quest'ultima sarebbe stata maggiore della prima ma solamente grazie all'ultimo carattere che costituisce la stringa. Nel caso di una stringa Str1 = 'Delphi' e di un'altra stringa Str2 = 'Delphi', il confronto Str1 = Str2 restituirebbe True. Alcuni degli operatori che abbiamo gi visto possono essere applicati anche in altri casi. Per esempio gli operatori +, -, *, <=, >=, = , <> si applicano anche ai set (insiemi) con rispettivamente i seguenti significati: unione, differenza, intersezione, sottoinsieme di, superset, uguaglianza, disuguaglianza. Per i tipi set esiste anche un altro operatore, l'operatore in che indica se un elemento presente in un insieme. Per esempio, se disponiamo di un set S composto dai seguenti elementi [1, 2, 3, 4] l'espressione 3 in S restituir True poich 3 un elemento del set S. Per quanto riguarda i puntatori, gli operatori applicabili a quest'ultimi sono: +, -, ^, =, <>. Il pi importante (o il pi usato) fra questi, l'operatore ^ che serve a dereferenziare un puntatore. Gli altri operandi servono ad aggiungere uno scostamento all'indirizzo puntato, a calcolare lo scostamento tra gli indirizzi puntati da due puntatori, a confrontare gli indirizzi puntati da due puntatori. Un altro operatore importante @ che serve a restituire l'indirizzo di una variabile, l'indirizzo di ingresso di una subroutine o di un metodo di classe.

Regole di precedenza degli operatori


Espressioni complesse vengono valutate seguendo delle regole per ciascuno degli operatori visti. Di seguito presente una lista degli operandi e dei rispettivi livelli di precedenza.
Operatore @, Not *, /, div, mod, and, shl, shr, as +, -, or, xor =, <>, <, >, <=, >=, in, is Precedenza Prima (pi alta) Seconda Terza Quarta (pi bassa)

In una espressione vengono valutati prima gli operatori con precedenza pi alta. Per esempio l'espressione 2 + 3 * 4 da come risultato 14 e non 20! Infatti viene valutata prima la moltiplicazione tra 3 e 4 (12) poich la moltiplicazione ha una precedenza pi alta ed in seguito viene aggiunto al risultato 2 (12 + 2 = 14). Per modificare la precedenza degli operatori, si utilizzano le parentesi tonde '(', ')'. Racchiudendo tra parentesi tonde una espressione, questa verr valutata per prima e tutto il resto a seguire seguendo sempre le regole di precedenza. Con riferimento all'esempio precedente, potremmo ottenere come risultato 20, modificandolo come segue: (2 + 3) * 4 Cos facendo, verr valutato prima il contenuto tra parentesi (2 + 3 = 5) ed il risultato moltiplicato per 4 (5 * 4 = 20). Le parentesi possono essere utilizzate anche quando la precedenza gi definita dall'operatore, senza influenzare la valutazione, consentendo una maggiore leggibilit ed interpretazione del codice. Sempre con riferimento all'esempio precedente scrivendo 2 + (3 * 4) il risultato non cambierebbe, mettendo bene in evidenza quale parte dell'espressione viene valutata prima. Per gli operatori che godono della stessa precedenza, la valutazione avviene normalmente da sinistra verso destra. Ad esempio 2+3-4=1 poich 2 + 3 = 5 e 5 - 4 = 1.

Conversione di tipi
Un'altra funzionalit che ci mette a disposizione il Delphi, la possibilit di effettuare delle conversioni di tipo. Ci possibile racchiudendo tra parentesi tonde l'espressione da convertire e facendo precedere il tutto (senza spazi) dal tipo in cui si vuole convertire l'espressione. La sintassi esatta la seguente tipo(espresione) Ad esempio potrebbe essere utile convertire in boolean un valore intero come 0 nel modo seguente: boolean(0).
LEZIONE 14:

I tipi (parte prima)

Nelle sezioni precedenti del corso abbiamo visto, in particolare nelle sezioni che spiegavano gli operatori, cose come Integer, String, Boolean. Si tratta dei tipi di dati. Un tipo essenzialmente un nome che identifica un particolare genere di dati. Un tipo determina anche il range di valori validi per quel tipo. Quando si definisce una variabile si deve specificarne il tipo. L'Object Pascal un linguaggio fortemente tipizzato. Ci significa che distingue un gran numero di dati. Solitamente, questa caratteristica permette al compilatore di trattare i dati in maniera intelligente e di poter rilevare degli errori in fase di compilazione che altrimenti sarebbero difficili da diagnosticare. Esistono diversi tipi di dati in Object Pascal; questi sono organizzati in tipi fondamentali (definiti direttamente dal linguaggio), generici, predefiniti e definiti dall'utente.

I tipi fondamentali sono implementati direttamente nel linguaggio Object Pascal; i loro formati e intervalli di validit sono gli stessi in tutte le implementazioni e sono indipendenti dalla CPU e dal sistema operativo. I tipi generici invece, possono variare da un'implementazione all'altra e sono specifici della piattaforma. consigliabile l'utilizzo dei tipi generici in quanto facilitano il porting tra piattaforme differenti. I tipi predefiniti sono automaticamente riconosciuti dal compilatore e non hanno necessit di essere dichiarati. I tipi Object Pascal possono essere classificati come segue: simple, string, structured, pointer, procedural, variant.

I tipi Simple
I tipi simple definiscono insiemi ordinati di valori. Appartengono a questa categoria di tipi, i tipi ordinal e real.

I tipi ordinal
I tipi ordinal definiscono insiemi ordinati di valori in cui tutti i valori hanno un predecessore ed un successore univoci ad esclusione del primo valore e dell'ultimo. Sono tipi ordinal i tipi integer, boolean, character, subrange. Ogni valore caratterizzato da una ordinalit che ne determina l'ordinamento nel tipo. Solamente per il tipo integer l'ordinalit corrisponde con il valore stesso. In tutti gli altri tipi, ad esclusione del subrange, il primo valore ha ordinalit 0, successivo 1 e cos a seguire. Delphi, o meglio l'Object Pascal, mettono a disposizione diverse funzioni predefinite che permettono di lavorare sulla ordinalit di questa categoria di tipi. Le pi importanti tra queste sono le seguenti:
Funzione Ord Pred Succ High Low Valore restituito restituisce l'ordinalit di un valore restituisce il predecessore del valore specificato restituisce il successore del valore specificato restituisce il valore pi alto nel tipo restituisce il valore pi basso nel tipo

Vediamo alcuni esempi: High(Byte) restituisce 255 in quanto il valore pi alto del tipo 255; Succ(4) 5 poich 5 il successore di 4. Esistono due funzioni deifnite in Object Pascal che che si usano per incrementare e decrementare il valore di una variabile ordinal e sono Inc e Dec. La prima incrementa il valore ed equivale al Succ del valore stesso, la seconda lo decrementa ed equivale al Pred del valore indicato.

I tipi Integer
I tipi Integer sono un sottoinsieme dei numeri interi. Esistono vari tipi di interi in Object Pascal che si differenziano per intervallo di valori rappresentabili e per formato. Per quanto riguarda i tipi interi generici abbiamo il tipo Integer e Cardinal. I tipi integer fondamentali sono: Shortint, Smallint, Longint, Int64, Byte, Word, Longword. Nella tabella seguente sono riportate le differenze tra i tipi menzionati.
Tipo Integer Cardinal Shortint Smallint Longint Int64 Byte Word Longword Intervallo -2147483648..2147483647 0..4294967295 -128..127 -32768..32767 -2147483648..2147483647 -263..263-1 0.255 0..65535 0..4294967295 32 bit 8 bit + segno 16 bit + segno 32 bit + segno 64 bit + segno 8 bit 16 bit 32 bit Formato 32 bit + segno

Incrementando l'ultimo valore o decrementando il primo valore di un tipo integer il risultato sconfina all'inizio od alla fine dell'intervallo valido per quel tipo. Ad esempio, nel caso del tipo Byte che ha come intervallo 0..255 l'esecuzione del codice
var

I : Byte;

... I := High(Byte); I := I + 1; assegna ad I il valore 0. Da notare che se stato attivato il controllo sugli intervalli, il codice precedente generer un errore di runtime.

I tipi character
Fanno parte dei tipi ordinal anche i tipi character i cui tipi fondamentali sono AnsiChar e WideChar. Gli AnsiChar sono caratteri che occupano un byte (8 bit) mentre i tipi WideChar sono caratteri che occupano due byte ovvero una word (16 bit). Il rpimo tipo ordinato secondo il set di caratteri esteso ANSI mentre il secondo ordinato secondo il set UNICODE i cui primi 256 caratteri corrispondono ai caratteri ANSI. Il tipi generico Char che nell'implementazione corrente equivale ad AnsiChar. Esistono anche per questo tipo delle funzioni predefinite dell'Object Pascal come la Chr che restituisce il carattere associato ad un intero compreso nell'intervallo AnsiChar o WideChar. Ad esempio Chr(65) restituisce 'A'. Come per tutti i tipi ordinali, possibile applicare al tipo character la funzione predefinita Ord che restituisce l'ordinale del valore character. Ad esempio, riprendendo l'esempio precedente, Ord('A') restituisce 65 che appunto l'ordinalit del carattere 'A' all'interno di AnsiChar o WideChar. Valgono anche per i tipi character gli sconfinamenti come visto per i tipi integer.

I tipi Boolean
I tipi boolean predefiniti sono quattro e sono: Boolean, ByteBool, WordBool e LongBool. Si utilizza preferibilmente il tipo boolean mentre gli altri esistono per compatibilit con altri linguaggi e con l'ambiente Windows. Una variabile boolean equivale a ByteBool ed occupa un byte (8 bit) mentre una WordBool occupa due byte (16 bit) ed una LongBool due word (32 bit). L'intervallo di valori per questo tipo composto da due costanti predefinite che sono True e False. Un valore boolean viene considerato True quando la sua ordinalit diversa da zero. Da notare che se questo valore compare in un contesto in cui atteso un valore boolean, questo viene automaticamente convertito dal compilatore in True la dove la sua ordinalit sia diversa da 0.

I tipi Enumerated
Il tipo enumerated serve a definire insiemi ordinati di valori. Da notare che l'ordinalit di questi valori segue l'ordine con cui sono stati elencati i valori al momento della dichiarazione del tipo. Per dichiarare un tipo enumerated si utilizza la seguente sintassi Type EnumeratedTypeName = (val1, val2, ..., valn); Un esempio pratico potrebbe essere la dichiarazione di un tipo enumersto "Seme" in un gioco di carte
Type

Seme = (Cuori, Quadri, Fiori, Picche);

Nel caso del nostro esempio, i valori validi per il tipi "Seme" da noi dichiarato sono: Cuori, Quadri, Fiori, Picche. In pratica quando si dichiara un tipo enumerated si dichiarano delle costanti (val) del tipo enumerated dichiarato (EnumeratedTypeName). Sempre facendo riferimento al nostro esempio, Cuori, Quadri, Fiori, Picche sono costanti di tipo Seme. Bisogna fare attenzione, al momento della dichiarazione del tipo, a non utilizzare, per gli identificatori dei valori, nome che possano andare in conflitto con altri nomi dichiarati nello stesso campo d'azione. Ad esempio, dichiarando un tipo
Type

TSound = (Click, Clack, Clock);

ed utilizzando il valore Click come nel codice seguente, si verificher un conflitto di nome; questo perch Click anche il nome di un metodo della classe TControl e di tutti i sui discendenti (nel senso degli oggetti che discendono da esso!).
Procedure Var

TForm1.Edit1Exit(Sender : TObject);

Snd : TSound;

Begin

... Snd := Click; ...


End;

Si pu aggirare questo tipo di inconveniente facendo riferimento al valore Click qualificandolo con il nome della unit in cui stato dichiarato. Ammettiamo che il tipo TSound sia dichiarato nella unit SoundFuncs, si potrebbe utilizzare il seguente codice Snd := SoundFuncs.Click; Comunque, bench sia possibile utilizzare il codice precedente, si consiglia di cercare di definire, per i valori dei tipi eneumerated, dei nomi che non entrino in conflitto con altri identificatori. Una buona pratica quella di anteporre al nome del valore enumerated le iniziali del nome del tipo enumerated. Nell'esempio precedente abbiamo dichiarato un tipo TSound; possiamo ridefinire i nomi dei suoi valori anteponendo "ts" al loro nome in questo modo
Type

TSound = (tsClick, tsClack, tsClock);

Come anche
Type

TMyType = (mtVal1, mtVal2, mtVal3);

I tipi Subrange
Come dice il nome stesso, questi tipi identificano dei sottoinsiemi di valori appartenenti ad un altro tipo ordinale che viene definito tipo base. Per definire un tipo subrange, si utilizza la seguente sintassi Type SubrangeTypeName = Low..High; dove Low ed High appartengono allo stesso tipo ordinal e Low minore di High. Questo costrutto definisce quel sottoinsieme di valori compresi tra Low ed High. Per esempio

Type

PrimiDieciNumeri = 1..10;

definisce un tipo subrange di nome PrimiDieciNumeri che conterr i valori 1, 2, 3, 4, 5, 6, 7, 8, 9, 10. Non valgono per questo tipo gli sconfinamenti visti per i tipi precedenti.
LEZIONE 15:

I tipi (parte seconda)

I tipi real
I tipi real definiscono insiemi di numeri che possono essere rappresentati con la notazione in virgola mobile. I tipi real fondamentali sono riportati di seguito
Tipo Intervallo Cifre Dimensione significative in byte 11-12 7-8 15-16 19-20 19-20 19-20 6 4 8 10 8 8

Real48 2.9 x 10-39..1.7 x 1038 Single 1.5 x 10-45..3.4 x 1038 Double 5.0 x 10-45..1.7 x 10308 Extended 3.6 x 10-4951..1.1 x 104932 Comp -263 + 1..263 - 1 -922337203685477.5808.. Currency 922337203685477.5807

Il tipo real generico equivale al double. Seguono alcune considerazioni sui tipi real fondamentali Il tipo Real48 viene mantenuto per compatibilit con il tipo real delle versioni precedenti di Object Pascal. Non essendo un tipo che utilizza un formato di memorizzazione non nativo per le CPU intel determina prestazioni pi lente. Extended il tipo real che permette maggiore precisione ma a sua volta meno portabile. Il tipo Comp (computazionale) nativo per le CPU Intel e rappresenta un integer a 64 bit. Pur essendo un tipo integer viene classificato come real in quanto non si comporta come tipo ordinal; non infatti possibile incrementare o decrementare un valore comp. Viene mantenuto per compatibilit con le versioni precedenti. Al suo posto si consiglia l'uso del tipo Int64. Currency un tipo di dati che viene utilizzato per i calcoli valutari poich ne riduce al minimo gli errori di arrotondamento. caratterizzato da punto decimale fisso e viene memorizzato come un integer a 64 bit scalato, con le ultime quattro cifre meno significative che rappresentano i decimali. Valori di questo tipo vengono automaticamente moltiplicati o divisi per 10000 quando vengono usati in espressioni con altri tipi real.

I tipi String
L'Object Pascal possiede, a differenza di altri linguaggi come ad esempio il C ed il C++, tipi di dati dedicato alla gestione dell stringhe. I tipi string supportati dall'Object Pascal sono i seguenti
Tipo Lunghezza massima Memoria richiesta da 2 a 256 byte da 4 byte a 2GB da 4 byte a 2GB Usato per compatibilit con versioni precedenti caratteri a 8 bit (ANSI) caratteri UNICODE; server e interfacce COM

ShortString 255 caratteri AnsiString ~231 caratteri WideString ~230 caratteri

AnsiString il tipo preferito per la maggior parte della applicazioni. A volte viene anche chiamato LongString.

I tipi string possono essere mischiati nelle assegnazioni e nelle espressioni in quanto il compilatore effettua automaticamente le conversioni del caso. Bisogna invece fare attenzione nel passaggio di parametri alle funzioni ed alle procedure che deve rispettare il tipo dei parametri dichiarati insieme alla funzione o procedura. Il tipo generico per il tipo string string. Non si tratta proprio di un tipo ma di una parola chiave del linguaggio che identifica un tipo di dati contenete una stringa. Come stato predefinito la parola chiave string si riferisce al tipo AnsiString (se non seguita da un numero fra parentesi quadre). possibile cambiare string in ShortString tramite la direttiva del compilatore {$H-}. Anche per il tipo string esistono funzioni predefinite come Length che restituisce la lunghezza in caratteri di una stringa e SetLength che ne regola la lunghezza. Due variabili di tipo string possono essere confrontate tramite gli operatori relazionali. In tal caso il confronto avviene confrontando l'ordinalit dei caratteri che occupano la stessa posizione. Ad esempio la stringa "AB" risulter minore di "AC". Confrontando stringhe di differente lunghezza, la dove i caratteri della stringa pi lunga non hanno corrispondenza nella stringa pi corta, vengono considerati maggiori. Di conseguenza la stringa "AB" risulter maggiore della stringa "A". Una variabile string pu essere indicizzata come se si trattasse di un array. I tal caso possibile accedere i caratteri di una stringa utilizzando la seguente sintassi. S[i] dove S una variabile di tipo string e i un intero indicante la posizione all'interno della stringa S. Per cui, S[2] := 'A' assegna alla posizione 2 della stringa S il carattere 'A'. Molta attenzione va fatta nella manipolazione delle stringhe tramite la tecnica vista in precedenza in quanto, in presenza di stringhe di tipo AnsiString o WideString che in realt rappresentano stringhe terminate con null, la sovrascrittura dell'ultimo carattere (il null appunto) potrebbe causare un errore di violazione di accesso.

I tipi Short String


Come visto nella tabella dei tipi string, il tipo ShortString pu essere lungo da 0 a 255 caratteri. Mentre la sua lunghezza varia dinamicamente, la memoria che occupa viene allocata staticamente e corrisponde a 256 byte; all'interno del primo byte riportata la lunghezza in caratteri della stringa. Ne consegue che per ottenere la lunghezza di una stringa ShortString possibile leggere il valore ordinale del carattere presente alla posizione 0 secondo la rappresentazione ad array. L'operazione Ord(S[0]) equivalente a Length(S) dove S una variabile string. L'Object Pascal supporta anche dei sottotipi di ShortString la cui lunghezza varia da 0 a 255. Questi tipi vengono definiti specificando un numerale tra parentesi quadre dopo la parola chiave string. Per esempio
Type

String50 = String[50];

definisce un tipo String50 la cui lunghezza massima di 50 caratteri. Questo tipo di dichiarazione permette di risparmiare spazio in memoria poich per contenere un variabile del tipo String50 verranno allocati solamente 51 byte a differenza dei 256 del tipo base ShortString.

I tipi Long String (AnsiString)


Come accennato in precedenza il tipo AnsiString viene anche definito LongString. Il tipo AnsiString rappresenta una stringa allocata dinamicamente la cui lunghezza massima limitata solamente dalla memoria disponibile. Una variabile di questo tipo non altro che un puntatore che occupa 4 byte. Il sistema utilizzato, totalmente gestito in maniera invisibile all'utente, simile ad una lista di caratteri. Ci permette appunto di avere una lunghezza dipendente solamente dalla memoria disponibile nel sistema. Un'altra caratteristica di questo tipo che, trattandosi di puntatori, due o pi variabili di questo tipo possono far riferimento allo stesso valore senza spreco aggiuntivo di memoria.

I tipi Wide String

L'allocazione di variabili di questo tipo il medesimo di quello per variabili di tipo AnsiString con la differenza che le stringhe rappresentate utilizzano caratteri UNICODE a 16 bit. Si differenzia solamente per efficienza che risulta minore in quanto non utilizza accorgimenti come il contatore dei riferimenti utilizzato invece dal tipo AnsiString. Questo tipo utilizzato prevalentemente per la compatibilit con i tipi nelle funzionalit COM.

I tipi strutturati
Fanno parte di questa categoria di tipi i Set, gli Array, i Record, i file oltre alle classi ed alle interfacce. Una istanza di tipo strutturato raccoglie sotto di se pi valori. I tipi Set possono a loro volta contenere valori di tipo strutturato eccezion fatta per il tipo Set che pu contenere solamente valori di tipo ordinale.

Set
Il tipo set implementa quello che in matematica viene definito insieme. Un set pu contenere solamente valori dello stesso tipo ordinale senza seguire alcun ordinamento intrinseco. Altres non ha significato che un elemento sia inserito due volte in un Set. L'intervallo di validit dei valori di un tipo Set dipende dall'intervallo di validit del tipo ordinal utilizzato per creare il Set. Questo tipo ordinale che origina il Set viene chiamato tipo base. Il costrutto utilizzato per dichiarare un tipo Set il seguente
Set of

TipoBase

Esempi di dichiarazione di tipi Set sono i seguenti:


Type

TChars = 'A'..'Z'; TSetOfChars =


Set of

TChars;

Per assegnare dei valori a varibili di tipo Set, facendo riferimento all'esempio precedente, si procede come segue: Var Caratteri : TSetOfChars; ... Caratteri := ['A', 'F', 'B', 'T', 'X']; Con analogia alla matematica ed agl'insiemi, un tipo Set pu essere vuoto e viene rappresentato con [].

Array
Gli array rappresentano una raccolta indicizzata di elementi tutti appartenenti ad uno stesso tipo che, come per i tipi Set, prende il nome di Tipo Base. A differenza dei tipi Set, un array pu contenere, proprio perch indicizzato, pi volte lo stesso valore. I tipi base utilizzabili nella dichiarazione di array, possono essere qualsiasi dai tipi ordinal ai tipi string, puntatori, altri array etc. Gli array possono essere dichiarati staticamente o dinamicamente ed essere mono o multi dimensionali. Un array monodimensionale rappresenta un vettore di elementi, mentre array multidimensionali rappresentano matrici di elementi.

Array statici
Il costrutto che si utilizza per dichiarare array statici il seguente array [TipoIndice1, ..., TipoIndicen] of TipoBase; Il numero di elementi che pu contenere un array cos definito dato dal prodotto delle dimensioni dei TipoIndice racchiusi tra parentesi quadre.

Gli array monodimensionali o vettori, hanno un solo TipoIndice come in questo esempio
Type

TVettore100 =

Array

[1..100]

of

Integer;

In questo esempio stato definito un vettore che pu contenere al massimo 100 valori integer. Per accedere ai valori di una variabile array come sopra dichiarata si utilizza la sintassi MioVettore[50] dove MioVettore il nome della variabile di tipo TVettore100 e 50 l'indice dell'elemento 50 all'interno dell'array. Quando si definisce una variabile di tipo array e non si assegnano valori a tutti gli elementi, gli elementi non assegnati vengono comunque allocati e conterranno valori non definiti. Si possono anche dichiarare array multidimensionali come di seguito
Type

TMatrice10X10 =

Array

[1..10, 1..10]

of Integer;

In questo caso stato dichiarato un tipo TMatrice10X10 composto da 10 righe e 10 colonne. La dichiarazione precedente equivale a dichiarare un tipo array di array come di seguito
Type

TMatrice10X10 =

Array

[1..10]

of Array

[1..10]

of Integer;

Per accedere i valori di una variabile dichiarata come TMatrice10X10 si procede in maniera simile agli array monodimensionali tranne per il fatto che, essendo questi a pi dimensioni, bisogna indicare un indice per ognuna delle dimensioni dell'array. Nel caso dell'esempio precedente MiaMatrice[2, 3], dove la variabile MiaMatrice di tipo TMatrice10X10, acceder al valore contenuto alla riga 2 colonna 3 della matrice. In Object Pascal sono definite funzioni per operare con gli indici e le dimensioni degli array statici. Con Low e High si ottiene rispettivamente il limite inferiore e quello superiore del primo tipo dell'array. Con Length si ottiene il numero di elementi nella prima dimensione dell'array.

Array dinamici
A differenza degli array statici, gli array dinamici non hanno dimensioni e lunghezza fisse e quindi la memoria viene riallocata quando si assegna un valore all'array o quando si utilizza la funzione SetLength per impostarne le dimensioni. Per dichiarare deigli array dinamici si utilizza il costrutto Type NomeTipo = Array of TipoBase; Quando si dichara un array dinamico questo non occupa memoria fino a quando non si effettua una chiamata alla funzione SetLength. Per esempio, se stata dichiarata una variabile MioArray di tipo TMioArray = Array of Integer per creare effettivamente l'array in memoria effettuando la chiamata SetLength(MioArray, 100), si allocher memoria per un array di 100 elementi integer. Da ricordare che l'indicizzazione degli array dinamici si basa sempre su integer e comincia da 0. In pratica gli array dinamici sono puntatori similmente ai LongString. Per disallocare un array dinamico, possibile assegnare alla variabile che lo referenzi il valore Nil oppure passare la variabile stessa alla funzione Finalize; questo verr disallocato solamente nel caso in cui non siano presenti altri riferimenti ad esso. Array di lunghezza 0 assumono il valore Nil. Anche per gli array dinamici sono disponibili le funzioni standard Length, Low ed High. Queste restituiscono rispettivamente il numero di elementi dell'array, 0 in ogni caso, e l'indice superiore dell'array. Nel caso di array vuoti High restituisce -1. Interessante notare che si avr in questo caso High < Low (-1 < 0).

Record
Per fare subito un confronto con altri linguaggi, tra cui C e C++, questo tipo corrisponde ai tipi struct (Struttura). Esso rappresenta un insieme di elementi eterogenei fra loro, ovvero un tipo record pu raggruppare

in se tipi diversi di elementi. Gli elementi di un record vengono detti campi; nella dichiarazione di un tipo record occorre specificare un nome per il tipo record ed un nome ed un tipo per ciascuno dei suoi campi. Ecco la struttura della dichiarazione di un record Type NomeTipoRecord = Record Campo1 : Tipo1; ... Campon : Tipon; End; Come per gli array dinamici, la dichiarazione di una un tipo record, non alloca memoria per i campi in esso contenuti. La memoria viene allocata al momento della dichiarazione di una variabile di quel tipo record. Un esempio di tipo record
Type

TPersona =

Record

Nome : String[40]; Cognome : String[40]; Eta : Integer;


End;

Per accedere ai campi del record si utilizza la sintassi seguente NomeVariabile.Nomecampo Dall'esempio precedente Var Persona : TPersona; ... Persona.Nome := 'Carlo'; Persona.Cognome := 'Marona'; Persona.Eta := 25; Se si devono accedere pi volte i campi del record possibile utilizzare il costrutto With..do nel modo seguente
With

Persona

do

Begin

Nome := 'Carlo'; Cognome := 'Marona'; Eta := 25;


End;

Quando si dichiara un tipo record, si ha la possibilit di inserire nella sua dichiarazione delle varianti alla sua struttura. Queste varianti vanno inserite dopo aver dichiarato tutti i campi del record. L sintassi la seguente

Type NomeTipoRecord = Record Campo1 : Tipo1; ... Campon : Tipon; Case SelettoreVariante : TipoOrdinale of ListaCostanti1 : (Variante1); ... ListaCostantin : (Varianten); End; Il SelettoreVariante facoltativo; se si omette necessario omettere anche i due punti successivi. Il SelettoreVariante deve essere di tipo ordinal. Ogni ListaCostanti pu essere composta da una costante dello stesso tipo di SelettoreVariante o da un elenco di costanti, sempre del tipo di SelettoreVariante, separate da virgole. Ogni Variante un elenco di dichiarazioni di con la stessa sintassi vista per la dichiarazione dei campi della parte principale del record. Le parti variant dei record condividono lo stesso spazio di memoria. Si pu leggere o scrivere nei campi variant dei record in qualsiasi momento, tenendo conto che se si scrive in un campo di un variant e subito dopo si scrive in un campo di un altro variant, i dati possono essere sovrascritti. Se si specifica il un SelettoreVariante, questo funziona come un campo normale nella parte principale del record. Per un trattazione pi approfondita si rimanda alla guida al linguaggio.

Tipi file
I tipi file rappresentano un insieme ordinato di elementi tutti dello stesso tipo. Un tipo file viene dichiarato come segue Type NomeTipoFile = File of Tipo; dove NomeTipofile un nome di identificatore valido e Tipo un tipo di dato qualsiasi di dimensioni fissate. Non possono essere utilizzati quindi nelle dichiarazioni di tipi file puntatori, array dinamici, long string, altri file o qualsiasi altro tipo strutturato che contenga uno qualsiasi dei tipi precedentemente elencati. Con riferimento all'esempio per i tipi record possiamo dichiarare un tipo file come segue
Type

TFileElenco =

File of

TPersona;

...
Var

FileElenco : TFileElenco;

Dichiarare un tipo od una variabile facendo uso solamente della parola file dar origine ad un tipo di file non tipizzato, senza tipo.

LEZIONE 16:

I tipi (parte terza)

Probabilmente saprete gi cosa sono i puntatori. La programmazione ad oggetti, tipica oramai di tutti i pi recenti linguaggi ad alto livello, fa grande uso dei puntatori. Un puntatore una variabile che indica un indirizzo di memoria. Si dice che un puntatore punta ad una variabile quando contiene l'indirizzo in memoria della variabile stessa. Conseguentemente il puntatore fa riferimento alla memoria ed ai dati di quella variabile. Per i tipi strutturati, come array, record ecc., un puntatore ad una variabile di questo tipo punta all'indirizzo del primo elemento della struttura. Anche i puntatori hanno un tipo che serve ad indicare il tipo di dati a fanno riferimento. Esiste comunque un tipo generico di puntatore, il tipo pointer che pu puntare a qualsiasi tipo di dato. La memoria occupata dai puntatori equivale a 4 byte. Per definire un puntatore ad un tipo si utilizza la sintassi Type NomeTipoPuntatore : ^Tipo; per esempio Type PInteger : ^Integer; definisce un tipo puntatore puntatore ad un tipo integer. L'operatore "^" pu essere utilizzato in due differenti modi: uno quello che abbiamo gi visto, l'altro utilizzarlo per dereferenziare il puntatore, cio per accedere al valore contenuto all'indirizzo di memoria indicato dal puntatore stesso. Per esempio, avendo una variabile I di tipo PInteger che punta ad un indirizzo contenente il valore 2, I^ restituisce 2. Esiste anche un altro operatore ed "@" che viene utilizzato per ottenere l'indirizzo di una variabile. Sono definite anche alcune funzioni standard dell'Object Pascal per operare con i puntatori e sono: New, GetMem, Addr, Ptr. Le prime due funzioni assegnano ad un puntatore esistente un indirizzo di memoria (vengono utilizzate per creare dinamicamente delle variabili), le altre due restituiscono puntatori rispettivamente ad un indirizzo o ad una variabile. La costante speciale Nil, gi vista in precedenza, serve ad indicare che un puntatore non fa riferimento a nulla e pu essere assegnata a qualsiasi tipo di puntatore.

Tipi procedurali
I tipi procedurali vengono utilizzati per trattare le funzioni e le procedure come valori che possono essere assegnati alle variabili o ad altre procedure o funzioni. Per dichiarare un tipo procedurale, per un certo tipo di funzione o procedura, basta prendere la dichiarazione della procedura o funzione ed eliminarne l'identificatore dopo la parola procedure o function. Ad esempio, avendo una funzione dichiarata come
Function

Bisestile(Anno :

Integer)

Boolean;

potremmo dichiarare un tipo


Type

TBisestileFunc =

Function

(Anno :

Integer)

Boolean;

dichiarare una variabile


Var

FBsisestile : TBisestileFunc;

ed assegnarle la funzione Bisestile come segue FBisestile := Bisestile; Quelli visti possono essere definiti tipi procedurali semplici ma esiste un altro tipo procedurale, il tipo puntatore a metodo. Vengono utilizzati per fare riferimento a metodi di classi e per la loro definizione occorre aggiungere le parole chiave "of object" dopo la dichiarazione del tipo.
Type

TNotifyEvent = Procedure (Sender : TObject) of object;

In questo esempio si definito un tipo TNotifyEvent (tipo gi esistente in Delphi ed utilizzato dai componenti della VCL) come puntatore ad un metodo. Ritroveremo i tipi puntatore a metodo pi avanti quando tratteremo la stesura di componenti. Nell'esempio precedente abbiamo visto come assegnare ad una variabile di tipo procedurale l'indirizzo di una procedura. Approfondiamo il comportamento dei tipi procedurali nelle assegnazioni e nelle espressioni. 1. Nelle assegnazioni, l'interpretazione viene determinata dal tipo della variabile presente a sinistra dell'operatore ":=". Se la variabile di tipo procedurale e, ovviamente, a destra abbiamo una funzione, avremo l'assegnamento alla variabile procedurale dell'indirizzo della funzione specificata a destra come gi visto. Se la variabile a sinistra non una variabile procedurale allora, se il tipo della variabile corrisponde al tipo restituito dalla funzione a cui fa riferimento la variabile specificata alla destra dell'operatore ":=" allora avremo l'assegnazione alla prima variabile del valore restituito dalla funzione riferita dalla seconda. 2. Nelle espressioni, viene sempre eseguita la funzione e viene poi valutata l'espressione in base al valore restituito dalla funzione stessa. Quindi confrontando in una espressione, ad esempio in una istruzione If, due variabili procedurali puntanti a funzioni, verranno confrontati i valori restituiti dalle rispettive funzioni e non le variabili stesse. 3. Per confrontare il contenuto di due variabili procedurali, occorre ricorrere all'operatore "@". Per ottenere l'indirizzo di memoria di una variabile procedurale, occorre utilizzare un doppio operatore "@". Vediamo alcuni esempi FBisestile := Bisestile; // Assegna a FBisestile l'indirizzo della funzione Bisestile

Var B : Boolean; F : TBisestileFunc; ... F := Bisestile; B := F; // Assegna a B il valore restituito dalla funzione Bisestile riferita da F

If F1 = F2 then... // In questo caso vengono confrontati i risultati delle funzioni riferite dalle due variabili

If @F1 = @F2 then... // Vengono confrontati i valori contenuti nelle variabili F1 ed F2

If @@F1 = @@F2 then... //

Vengono confrontati gli indirizzi di memoria delle variabili F1 ed F2

Come i tipi pointer una variabile procedurale pu contenere il valore nil indicando pio che la non punta a niente. Effettuare una chiamata ad una variabile procedurale puntante a nil provoca un errore. Per verificare se un variabile procedurale non contiene il valore nil si utilizza la funzione Assigned che restituisce false qualora la variabile contenga il valore nil. Il costrutto If Assigned(NomeEvento) then NomeEvento(Parametri) viene molto utilizzato durante la stesura dei componenti per testare se ad una propriet evento stata assegnata un procedura per la gestione dell'evento stesso ed evitare quindi di generare errori.

Tipi variant
I tipi variant, come dice il nome stesso, sono tipi che possono variare ovvero una variabile di tipo variant pu assumere valori appartenenti a tipi diversi. Vengono utilizzati quando non si pu determinare in fase di compilazione il tipo che verr assegnato alla variabile. Questi tipi offrono una grande flessibilit ma occupano pi memoria e rallentano l'elaborazione. Si ha anche maggiore probabilit di provocare errori runtime poich non possibile individuarli al momento della compilazione proprio perch non hanno un tipo specifico. Vengo utilizzate soprattutto negli oggetti COM, OLE. Il valore speciale Null indica valori sconosciuti o mancanti. I tipi variant occupano 16 byte di memoria e al momento della loro creazione vengono inizializzati al valore Unassigned. Per una trattazione approfondita di questo tipo si rimanda ai manuali del linguaggio.
LEZIONE 17:

Le variabili

Cos' una variabile probabilmente lo sapete gi; si tratta in definitiva di segnaposto per accedere, sia il lettura che scrittura, a una sepcifica locazione di memoria. Per ogni variabile necessario specificare il tipo di dati che questa conterr. In Object Pascal, una variabile viene dichiarata con il costrutto

Var NomeVariabile : Tipo; Si possono anche definire contemporaneamente pi variabili dello stesso tipo specificando al posto del NomeVariabile un elenco di nomi validi separati da virgole. Quando si effettuano dichiarazioni consecutive di variabili, non occorre ripetere la parola chiave Var.

Var

I : Integer; A, B : Real;

Nome : String; Le variabili all'interno di un programma si dividono in globali e locali. Le variabili locali sono quelle variabili definite all'interno di procedure o funzioni, mentre le globali sono tutte le altre. Al momento dalla loro dichiarazione, le variabili globali possono essere inizializzate fornendo un valore dopo la loro definizione.

Var

I : Integer = 10;

Se queste non vengono esplicitamente inizializzate, il compilatore le inizializza tutte a 0. Non possibile fornire un valore di inizializzazione la dove si utilizzata una dichiarazione multipla di variabili come nell'esempio precedente per le variabili A, B. A differenza delle variabili globali, le variabili locali non possono essere inizializzate ne vengono inizializzate dal compilatore; esse conterranno dei valori non definiti, casuali. Quando si dichiara una variabile, si riserva della memoria sufficiente a contenere un dato del tipo spcificato per la variabile. Questa memoria viene liberata solamente quando il programma non fa pi uso di quelle variabili. Per le varibili locali questo avviene solamente quando l'esecuzione del programma esce dalla funzione o procedura in cui sono state dichiarate. In Object Pascal possibile dichiarare variabili che facciano riferimento a determinate locazioni di memoria, tecnica utilizzata fondamentalmente per la scrittura di drivers di dispoditivi. Per dichiarare una variabile di questo tipo occorre specificare dopo il tipo la parola chiave absolute e l'indirizzo di memoria a cui deve fare riferimento.

Var

CrtMode : Byte

absolute

$0040;

Dichiarazioni di variabili di questo tipo non possono contenere inizializzazioni. Non sempre le variabili occorrono essere create al momento della dichiarazione. Potrebbe essere il caso di liste di valori create dinamicamente a run-time. Per creare varaibili a run-time, ovvero variabili dinamiche, esistono diverse funzioni che allocano memoria per un certo tipo di variabile e ritornano un puntatore per accedere a quella locazione di memoria. Queste funzioni sono GetMem, New. Queste variabili vengono allocate sullo heap e non vengono gestite automaticamente; ci significa che una volta che queste non sono pi necessarie occorre liberare la memoria da loro occupata con le funzioni FreeMem, se la variabile stata creata con GetMem, oppure con Dispose se la variabile stata creata con New.
LEZIONE 18

Le costanti

Come in molti altri linguaggi anche in Object Pascal possono essere definite delle costanti. Alcune costanti sono definite dal linguagio stesso, come abbiamo visto per le costanti true, false e nil altre sono, diciamo per natura, costanti come i numerali e i letterali stringa, per esempio 'Hello World!'. Esistono due tipi di costanti in Object Pascal, le costanti vere e quelle con tipo. Una costante vera un identificatore il cui valore, dichiarato al momento della dichiarazione della costante, non pu cambiare. Il tipo della costante, viene definito automaticamente dall'espressione presente alla destra dell'operatore "=".

Const

Retry = 5;

Per dichiarare una costante occorre specificare un nome di identificatore valido dopo la parola chiave Const, seguito dal segno di uguale ed una espressione costante che ne identifica il valore. Per espressione costante si intende una espressione valutabile dal compilatore senza bisogno di eseguire il programma. Sono espressioni costanti i numerali, le stringhe di caratteri, le costanti vere, i valori di tipi enumerati, le costanti

speciali ed espressioni costruite con questi elementi. Ovviamente non possono comparire in questo tipo di espressioni delle variabili od altri elementi non calcolabili senza eseguire il programma. Sono invece ammesse le seguenti funzioni predefinite: Abs, Exp, Length, Ord, Sqr, Addr, Frac, Ln, Pred, Sqrt, ArcTan, Hi, Lo, Round, Succ, Chr, High, Low, Sin, Swap, Cos, Init, odd, SizeOf, Trunc. Le costanti con tipo si comportano in maniera differente dalle variabili vere. Queste possono contenere elementi come array, record, procedurali, pointer. Con le impostazioni standard, il compilatore permette di assegnare alle costanti con tipo valori nuovi in fase di esecuzione del programma. Questo comportamento pu essere modificato con la direttiva del compilatore {$J-}, per disattivarlo, {$J+} per riattivarlo. Una costante con tipo pu essere dichiarata come segue

Const NomeCostante : Tipo = Valore;


LEZIONE 17:

Procedure e Funzioni

A differenza di altri linguaggi, come il C o il C++, l'Object Pascal fa distinzione tra funzioni e procedure. Queste sono definite a livello generale come routines o subroutines. Esse sono blocchi di codice indipendenti che vengono utilizzate per centralizzare del codice che necessita di essere utilizzato pi volte in punti diversi del programma. Ci permette di ridurre la quantit di codice da scrivere e riduce il numero di errori. L'Object Pascal distingue tra funzioni, che restituiscono dei valori, e le procedure che non restituiscono alcun valore. Per dichiarare una funzione o una procedura nessario indicarne in nome, ed i parametri ed i rispettivi tipi che verranno passati ad essa, il blocco di codice che implemeter le funzionalit della routine. Nel caso di una funzione, occorre anche indicare un tipo per il valore restituito. Ecco la sintassi per la dichiarazione di funzioni e procedure

Procedure
Per dichiarare un procedura si utilizza la sintassi seguente

Procedure NomeProcedura(ListaParametri); DichiarazioniLocali; Begin CodiceProcedura; End; dove NomeProcedura un nome valido di identificatore, ListaParametri una lista di parametri e relativi tipi, DichiarazioniLocali sono dichiarazioni di variabili, tipi, altre funzioni o procedure locali alla procedura stessa e CodiceProcedura il codice principale della procedura. La lista dei parametri pu essere formata da un elenco di identificatori separati da virgole e dal tipo a cui appartengono; pu essere un elenco di identificatori di tipo diverso accompagnati dai rispettivi tipi, separati da punti e virgola; una combinazione delle possibilit precedenti. Strutturalmente

Elenco di identificatori dello stesso tipo: A, B, C : Integer Elenco di identificatori di tipo diverso: A : Integer; B : Real; C : String Una combinazione dei precedenti: A, B : Integer; C : String I parametri passati ad una procedura possono essere di due tipi: per riferimento o per valore. Per passare un parametro per valore sufficiente utilizzare la sintassi precedentemente vista. Per passare parametri per riferimento occorre specificare la praola chiave Var prima del nome dell'identificatore che rappresenta il parametro da passare per riferimento. Procedure MiaProcedura(A, B : Integer; Var C : Real); Nell'esempio, A e B sono passate per valore, ovvero come costanti, C per riferimento. I parametri passati per valore sono costanti e partecipano all'elaborazione in localmente ovvero, modifiche a loro apportate non vengono trasferite all'esterno della procedura. Questo perch al momento della chiamata alla procedura viene allocata memoria per questi parametri e viene liberata all'uscita della procedura provocando la perdita dei valori in essi contenuti. Il passaggio per riferimento, permette di trasmettere le modifiche effettuate sul valore del parametro per riferimento all'estreno della procedura una volta terminata la sua esecuzione. Con riferimento all'esempio precedente, si potrebbe pensare alla MiaProcedura come una procedura che restituisca il risultato della divisione di A per B attraverso il parametro C. In questo caso A, B sono locali alla procedura mentre C viene restituito alla fine dell'esecuzione. In alcuni casi, dopo la dichiarazione della procedura necessario indicare una direttiva che specifichi il comportamento a livello di chiamata della procedura. Ci necessario ad esempio nelle librerie a collegamento dinamico, dove per permettere a programmi scritti in linguaggi differenti dal Delphi, si utilizza la direttiva StdCall che garantisce la compatibilit del passagio di parametri. Per un approfondimento delle direttive si rimanda alla documentazione del linguaggio.

Funzioni
Le dichiarazioni di funzioni sono in tutto e pertutto uguali alle dichiarazioni di procedure tranne per il fatto che per le prime occorre specificare il tipo del valore restituito. Function NomeFunzione(ListaParametri) : TipoRestituito; DichiarazioniLocali; Begin CodiceFunzione; End; Per assegnare il valore da restituire, si possono seguire due strade: una assegnare il valore al nome della funzione; l'altra utilizzare la variabile Result. Esempio Function Bisestile(Anno : Integer) : Boolean; Begin ... Bisestile := ...; End; oppure Function Bisestile(Anno : Integer) : Boolean; Begin ... Result := ...; End; il risultato lo stesso.
LEZIONE 19:

Gestire il flusso: le istruzioni condizionali

Durante l'esecuzione del codice, possibile modificarne il percorso di esecuzione tramite istruzioni condizionali o cicliche. Le istruzioni condizionali ci permettono di effettuare delle scelte tra differenti strade da seguire per proseguire nell'esecuzione del codice. Le istruzioni cicliche ci permettono di ripetere alcune istruzioni o gruppi di istruzioni pi volte senza la necessit che queste vengano scritte nel codice tante volte quante devono essere eseguite. L'Object Pascal dispone di due tipi di istruzioni condizionali e tre tipi di istruzioni cicliche che sono rispettivamente: istruzione IF, CASE, REPEAT, FOR, WHILE.

Le istruzioni condizionali
Le istruzioni condizionali presenti in Object Pascal sono due: IF, CASE. La prima istruzione consente di effettuare una scelta in base ad una espressione condizionale e permette di scegliere tra due blocchi di istruzioni da eseguire. L'espressione condizionale che definisce il blocco da eseguire deve restituire necessariamente un valore booleano: vero o falso. Questo ci fa capire anche perch questa istruzione ci permette di scegliere solamente due strade da seguire. La sintassi dell'istruzione IF la seguente

If

Espressione

then

BloccoEspressioneVera
else

BloccoEspressioneFalsa Per blocco ovviamente si intende un insieme di istruzioni racchiuse tra le parole chiave Begin..End, ma potrebbe anche trattarsi di una sola istruzione ed in tal caso l'uso delle parole chiave Begin..End sarebbe facoltativo. Una importante precisazione va fatta per quanto riguarda la riga di codice che precede la parola ELSE; questa infatti non deve mai terminare con il punto e virgola (;). La ragione di ci sta nel fatto che il punto e virgola indica la fine di una istruzione di codice ed in questo caso l'istruzione comprende anche la parola ELSE percui il punto e virgola andr inserito solamente alla fine dell'istruzione condizionale. Qualora si inserisse il punto e virgola prima della parola ELSE, il compilatore lo segnalerebbe in fase di compilazione del codice. N.B. All'interno del blocco di codice del ramo THEN, tutte le istruzioni devono terminare con il punto e virgola! Vediamo degli esempi per chiarire le idee

If

A < B

then

Begin

A := A + 1; B := B - 1;
End

//Notate la mancanza del punto e virgola

Else Begin

B := B + 1; A := A - 1;
End;

If

A < B

then

Scrivi('A minore di B')


Else

Scrivi('A maggiore di B'); Il ramo ELSE facoltativo quindi potremmo avere istruzioni IF con solamente il ramo THEN.

If

A < B

then

A := A + 1; Si possono avere anche istruzioni IF nidificate che possono portare qualche difficolt in pi. Un esempio di istruzione IF nidificata potrebbe essere la seguente

If

A > B A < 4

then then

If

Scrivi('A maggiore di B e minore di 4')


Else

Scrivi('A maggiore di B ma maggiore di 4'); Non c' alcun segnale che indichi al compilatore come considerare i blocchi di istruzioni all'interno di questo costrutto. Il codice precedente infatti potrebbe essere interpretato in due modi differenti

A > B then [If A < 4 then Scrivi('A maggiore di B e minore di 4') Else Scrivi('A maggiore di B ma maggiore di 4')];
If

oppure

A > B then [If A < 4 then Scrivi('A maggiore di B e minore di 4')] Else Scrivi('A maggiore di B ma maggiore di 4');
If

Il compilatore segue sempre la divisione dell'istruzione nel primo modo. Per evitare problemi con le istruzioni IF nidificate consigliabile racchiudere in blocchi Begin..End il codice da eseguire, come stato evidenziato in precedenza con le parentesi quadre. Nel primo caso avremmo

If

A > B

then

Begin If

A < 4

then

Scrivi('A maggiore di B e minore di 4')


Else

Scrivi('A maggiore di B ma maggiore di 4')


End;

nel secondo
If

A > B

then

Begin If

A < 4

then

Scrivi('A maggiore di B e minore di 4')


End Else

Scrivi('A maggiore di B ma maggiore di 4'); Ovviamente il codice pu essere indentato come si vuole, potrebbe anche essere scritto tutto su una linea ma per motivi di leggibilit l'indentazione del codice che trovate in queste lezioni quella da me utilizzata nel codice che scrivo normalmente e ritengo che sia sufficientemente leggibile. L'istruzione CASE si differenzia dalla precedente istruzione poich permette di scegliere tra pi di due blocchi di codice. Ci dettato dal fatto che che l'espressione condizionale di tipo ordinale e quindi potr seguire tante strade differenti quanti sono i valori del tipo ordinale. La sintassi dell'istruzione CASE la seguente

Case

EspressioneOrdinale

of

Caso1 : BloccoIstruzioni1; Caso2 : BloccoIstruzioni2; Caso3 : BloccoIstruzioni3; ... CasoN : BloccoIstruzioniN;

Else

BloccoIstruzioniCasoGenerale;
End;

Occhio al tipo di espressione che deve essere di tipo ordinale quindi non possiamo utilizzare tipi string come espressioni condizionali per l'istruzione CASE come accade per alcuni altri linguaggi. Funzionalmente, l'esecuzione dell'istruzione CASE provoca un salto alla riga di codice contente alla sinistra dei due punti il valore restituito dall'espressione condizionale. Ad esempio

Case

of

//dove N contiene il valore 3

1 : ...; 2 : ...; 3 :
Begin

//Questo il codice che verr eseguito

...
End;

4 : ..;
Else

Scrivi('Valore non valido');


End;

Se il valore restituito dall'espressione condizionale non presente nella lista di valori dell'istruzione CASE, viene eseguito il codice contenuto nel ramo ELSE. Ci utile per gestire tutti i casi che non rientrano nell'elenco fornito nell'istruzione come visto nell'esempio precedente. Anche nell'istruzione CASE il ramo ELSE facoltativo ma in caso di omissione, bisogna fare attenzione al valore assunto dall'espressione condizionale specificata; qualora il valore restituito da questa non fosse presente nella lista dei valori possibili, l'esecuzione del codice continuerebbe dall'istruzione immediatamente successiva all'istruzione CASE. L'istruzione CASE equivalente ad un condizionale IF annidato Come valori alla sinistra dei due punti possono essere specificati anche elenchi di valori

Case

of

1, 2, 4 : ...; //Blocco di istruzioni valido per il valori 1, 2, e 4 3 : ...; 5 : ..;


End;

oppure

Case

of

1..4 : ...; //per tutti quei valori che cadono nell'intervallo 1-4 //estremi compresi 5 : ...;
End;

LEZIONE 20:

Gestire il flusso: le istruzioni cicliche

Le istruzioni cicliche
Object Pascal mette a disposizione tre tipi di istruzioni per il controllo dei cicli: FOR, WHILE, REPEAT. Sono istruzioni che si differenziano molto l'una dall'altra per quanto riguarda sintassi e logica di esecuzione. L'istruzione FOR richiede la specificazione del numero di ripetizioni da effettuare. La sua sintassi la seguente

For

Contatore := ValoreIniziale

to

ValoreFinale

do

BloccoIstruzioniDaRipetere; oppure
For

Contatore := ValoreIniziale

downto

ValoreFinale

do

BloccoIstruzioniDaRipetere; L'istruzione For..To assegna inizialmente a Contatore il valore specificato in ValoreIniziale ed esegue il BloccoIstruzioniDaRipetere incrementando (nel caso di For..To) o decrementando (nel caso di For..downto) dopo ogni esecuzione il valore di Contatore fino a raggiungere il valore indicato in ValoreFinale. scontato ricordare che i valori di ValoreIniziale e di ValoreFinale devono essere compatibili con il tipo di Contatore. Quando il valore di Contatore raggiunge il valore indicato in ValoreFinale l'esecuzione prosegue eseguendo ancora una volta il BloccoIstruzioniDaRipetere e quindi quest'ultimo viene eseguito in totale tante volte quanti sono i valori contenuti nell'intervallo ValoreIniziale-ValoreFinale. Per esempio

For

I := 1

to

10

do

... eseguir il codice contenuto nel blocco da ripetere per 10 volte. Ci possono essere dei casi in cui il ciclo For non venga eseguito neanche una volta e ci pu capitare quando il ValoreIniziale maggiore (nel caso di For...To) o minore (nel caso di For...downto) di ValoreFinale.

All'interno del ciclo For possibile utilizzare il valore che la variabile contatore assume ad ogni iterazione

For

I := 1

to

10

do

Begin

A := 11 - I; Writeln('Valore di A:' + IntToStr(A));


End;

Il codice in esempio restituisce la stampa a video dei seguenti valori: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1.

For

I := 10

downto

do

Begin

A := 11 - I; Writeln('Valore di A:' + IntToStr(A));


End;

Il codice precedente restituisce la stampa a video dei seguenti valori: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10. Attenzione al valore del contatore al termine del ciclo For; questo ha volre non definito quindi non consigliabile utilizzare il valore in esso contenuto fuori dal blocco di istruzioni da ripetere. Il valore delle espressioni ValoreIniziale e ValoreFinale vengono valutate solamente una volta ed all'inizio dell'esecuzione del ciclo. Ne deriva che l'esecuzione del ciclo non influenzata da possibili alterazioni dei valori citati come invece pu accadere nei cilci WHILE. Il ciclo WHILE si differenzia da ciclio for per il tipo di espressione di controllo e per la valutazione della stessa che avviene all'inizio di ogni iterazione. La sintassi dell'istruzione WHILE la seguente

While

Espressione

do

BloccoIstruzioniDaRipetere; L'espresione dell'istruzione While deve essere di tipo boolean e l'esecuzione del codice prosegue fintanto che l'espressione Espressione verificata, ovvero restituisce True. Si dice che l'istruzione While ripete per Vero. L'esecuzione del codice termina quando l'espressione Espressione assume valore False. Esempio

While

A < B

do

Inc(A);

Il codice precedente porta all'esecuzione dell'istruzione Inc(A) tante volte quante ne sono necessarie per portare il valore di A ad essere uguale al valore di B. Nel caso in cui A sia maggiore od uguale a B il ciclo non viene eseguito neanche una volta. L'istruzione REPEAT si differenzia dal WHILE fondamentalmente per la valutazione dell'espressione di controllo. Questa viene infatti verificata dopo ogni esecuzione e la ripetizione del ciclo avviene finch l'espressione restituisce un valore False. Si dice che l'istruzione Repeat ripeta per Falso. La sua sintassi la seguente

Repeat

BloccoIstruzioniDaRipetere;
Until

Espressione;

Espressione deve essere come per il While di tipo boolean e affinch il ciclo venga ripetuto deve assumere valore False; quando assume valore True, il ciclo termina. Caratteristica interessante da notare, che l'istruzione Repeat viene eseguita almeno una volta al contrario dell'istruzione While. Questo perch, appunto, l'espressione di controllo viene valutata alla fine del ciclo. Riscrivendo l'esempio precedente per l'istruzione While utilizzando l'istruzione Repeat avremo

Repeat

Inc(A);
Until

A = B;

A differenza dell'istruzione While,questa volta anche se A gi uguale a B prima dell'esecuzione del ciclo, dopo l'esecuzione di quest'ultimo A assumer comunque valore A + 1. Ammettendo che A prima del ciclo abbia valore 5 e B abbia valore 5 dopo il ciclo avremo che A assumer comunque il valore 6 appunto per il fatto che l'istruzione Repeat viene eseguita almeno una volta. Il flusso delle istruzioni di ciclo, pu essere controllato tramite due altre istruzioni ovvero Break e Continue. La prima interrompe l'esecuzione del ciclo in cui si trova, la seconda riprende l'esecuzione dall'istruzione successiva nella sequenza del ciclo stesso.
LEZIONE 22:

Classi ed Oggetti: introduzione

Classi ed Oggetti Una classe una struttura composta da campi, metodi e propriet, detti anche componenti o membri. Una istanza di una classe detta oggetto. Un campo assimilabile ai campi di un tipo record e rappresenta gli elementi di dati presenti ciascuna istanza di classe. Un campo come una varaibile che appartiene all'oggetto e pu essere di qualsiasi tipo. Questi devono trovarsi prima di qualsiasi dichiarazione di propriet o di metodo. Essi sono collegati in modo statico ed i relativi collegamenti sono determinati in fase di compilazione. Un metodo una procedura o funzione che associata ad una classe e pu operare su oggetti (istanze di classe) o sul tipo class stesso. Quest'ultimi vengono chiamati metodi di classe. All'interno di una dichiarazione di classe, essi appaiono come intestazioni di funzioni o procedure. Questo dovr poi essere implementato in una qualsiasi parte del modulo in cui stata dichiarata la classe di appartenenza con una definizione di

dichiarazione all'interno della sezione implementation del modulo. Nella definizione della dichiarazione del metodo, viene riportato il nome del metodo cos come stato dichiarato nella dichiarazione di classe, preceduto dal nome della classe stessa separati da un punto. Es. Function MyClass.GetWidth : Integer; Una propriet assimilabile ad un campo tranne per il fatto che differisce nell'implementazione. La propriet infatti soprattutto una interfaccia verso i dati contenuti nei campi dell'oggetto, mascherando le modalit di acesso ad essi. Infatti queste hanno degli specificatori di accesso che definiscono la maniera in cui i dati vengono scritti o letti nella propriet. Tutti gli oggetti vengono allocati dinamicamente nella memoria, utilizzando strutture caratteristiche dipendenti dalla definizione del tipo class di appartenenza. Ogni oggetto ha la sua copia univoca dei campi definiti nella classe, tutte le istanze di una classe condividono gli stessi metodi. Per la creazione e distruzione degli oggetti, esistono dei metodi speciali definiti Costruttori e Distruttori. In definitiva, una variabile di tipo class effettivamente un puntatore ad un oggetto. A differenza dei normali puntatori, questi non necessitano della dereferenziazione per accedere ai metodi, propriet e campi del tipo class a cui appartiene l'oggetto. Non necessario scrivere MioOggetto^.MioMetodo, ma sufficiente scrivere MioOggetto.MioMetodo. Prima di essere istanziato, un tipo class deve essere dichiarato. La dichiarazione di tipi class pu avvenire solamente nella sezione Type di livello pi alto del programma o della unit. Pertanto non possibile dichiarare tipi class nella sezione di dichiarazione di funzioni o procedure. La sintassi per la dichiarazione di un tipo class la seguente:
Type

NomeClasse =

Class(ClasseAntenato)

ElencoMembri
End;

NomeClasse ovviamente un qualsiasi identificatore riconosciuto valido dal compilatore; ClasseAntenato indica la classe da cui eredit la nuova classe e pu essere facoltativo, con il risultato che in tal caso la classe erediter direttamente dalla classe predefinita TObject; ElencoMembri contiene l'elenco di tutti i campi, propriet e metodi che andranno costituire la nuova classe. Quest'ultimo, pu essere vuoto ed in tal caso possibile omettere la parola chiave end. La struttura di ElencoMembri simile a quella vista nella dichiarazione di tipi record con alcune differenze riguardanti il campo di visibilit dei membri, la dichiarazione di propriet (non presenti nei tipi record) e nella dichiarazione dei metodi. Una dichiarazione di classe, organizza i suoi membri in tre, quattro o cinque sezioni definite come campi di visibilit. Queste sezioni indicano infatti il campo di visibilit dei membri in esse contenuti. I campi di visibilit disponibili sono: Private, Protected, Public, Published, Automated. Tutti i membri che si trovano all'interno di una sezione Private sono invisibili dall'esterno della unit o programma in cui la classe viene dichiarata. Vengono normalmente dichiarati in questa sezioni i campi della classe. Tutti i membri che si trovano all'interno di una sezione Protected sono visibili ed accessibili all'interno della unit o programma in cui la classe viene dichiarata e all'interno di qualsiasi classe discendente indipendentemente dal modulo in cui dichiarata. Tutti i membri che si trovano all'interno di una sezione Public sono visibili ed accessibili da qualsiasi punto sia possibile referenziare la classe stessa. Tutti i membri che si trovano all'interno di una sezione Published hanno la stessa visibilit dei membri Public con la differenza che il compilatore genera per questi le informazioni RTTI (Informazioni Runtime di Tipo) che lo stesso Delphi utilizza in fase di sviluppo per accedere per esempio alle propriet dall'Object Inspector.

Queste informazioni infatti permettono ad un'applicazione di interrogare i campi per ottenerene in modo dinamico i metodi. Non tutti i tipi di dati possono essere utilizzati in una sezione Published; tipi validi sono: tipo ordinal, string, class, interface, puntatori a metodi. Anche i tipi Set possono essere utilizzati a condizione che questi possano essere contenuti in un Byte o in una Word o in una DoubleWord. Tutti i membri che si trovano all'interno di una sezione Automated hanno la stessa visibilit dei membri Public con la differenza che le informazioni di tipo Automation vengono generate solamente per i membri automated. Per la trattazione di questo tipi di sezione si rimanda alla guida del linguaggio.
LEZIONE 23:

Classi ed Oggetti: i metodi

Metodi
Abbiamo visto in precedenza cosa e come viene definito un metodo. Ora vediamo di approfondire l'argomento. In Object Pascal ci sono due parole chiave che rientrano nella creazione ed utilizzo dei metodi; queste sono Inherited e Self. permette di richiamare i metodi dell'antenato da cui deriva la classe a cui appartiene il nuovo metodo. Per meglio dire, se la classe da cui abbiamo ereditato definisce un metodo MioMetodo scrivendo il codice seguente richiamo quel metodo
Inherited Procedure Begin Inherited; End;

MiaClasse.MioMetodo(Valore :

Integer);

Ci permette di aggiungere funzionalit al metodo di base ereditato. Non specificando nessun identificatore dopo la parola riservata Inherited, questa far riferimento al metodo dell'antenato avente lo stesso nome del metodo in cui si trova la parola chiave Inherited. Se dopo di essa non viene specificato alcun parametro, verranno passati al metodo ereditato gli stessi parametri passati al metodo della classe che si sta definendo. Se invece la parola Inherited seguita da un identificatore di metodo, questa effettuer una chiamata al metodo corrispondente all'identificatore indicato ricercandolo a partire dall'antenato immediato. La parola chiave Self permette di fare riferimento, all'interno dell'implementazione di un metodo, all'oggetto dal quale il metodo viene chiamato.

Collegamento dei metodi


I metodi possono essere di tre tipi: statici, virtuali, dinamici. Quando un metodo viene definito, per impostazione predefinita statico. Nella chiamata di un metodo statico, l'implementazione da attivare viene determinata in base al tipo, dichiarato in fase di compilazione, della classe che viene utilizzata per la chiamata del metodo. Quindi per esempio, avendo le dichiarazioni di classi seguenti
Type

TClasse1 =
Procedure End;

Class

Metodo1;

TClasse2 =
Procedure End;

Class(TClasse1)

Metodo1;

ed eseguendo il seguente codice


Var

Classe1 : TClasse1; Classe2 : TClasse2;

Begin

Classe1 := TClasse1.Create; Classe1.Metodo1; //L'implementazione utilizzata quelle di TClasse1 Classe1.Destroy; Classe1 := TClasse2.Create; Classe1.Metodo1; //L'implementazione utilizzata quelle di TClasse1 TClasse2(Classe1).Metodo1; Classe1.Destroy; Classe2 := TClasse2.Create; //L'implementazione utilizzata quelle di TClasse2 Classe2.Metodo1; Classe2.Destroy;
End;

avremo che l'esecuzione della seconda chiamata "Classe1.Metodo1" determiner l'esecuzione dell'implementazione del metodo nella classe "TClasse1" anche se la variabile "Classe1" fa riferimento ad un oggetto di tipo "TClasse2". I metodi dichiarati virtual o dynamic possono essere ridefiniti nelle classi discendenti. Per dichiarare dei metodi come virtual o dynamic occorre specificare dopo la loro dichiarazione le parole chiave virtual o dynamic. A differenza dei metodi static la determinazione dell'implementazione del metodo da utilizzare nella chiamata viene determinata in base al tipo run-time. Per ridefinire un metodo virtual o dynamic occorre specificare, nella classe discendente, un metodo con lo stesso nome del metodo della classe antenato e utilizzare la parola chiave override dopo la sua dichiarazione. Apportando le modifiche del caso, riprendiamo l'esempio visto in precedenza per i tipi static e vediamo le differenze
Type

TClasse1 =
Procedure End;

Class

Metodo1;

TClasse2 =

Class(TClasse1)

Procedure End;

Metodo1;

override;

Var

Classe1 : TClasse1; Classe2 : TClasse2;

Begin

Classe1 := TClasse1.Create; Classe1.Metodo1; //L'implementazione utilizzata quelle di TClasse1 Classe1.Destroy; Classe1 := TClasse2.Create; Classe1.Metodo1; //L'implementazione utilizzata quelle di TClasse2 TClasse2(Classe1).Metodo1; Classe1.Destroy; Classe2 := TClasse2.Create; Classe2.Metodo1; //L'implementazione utilizzata quelle di TClasse2 Classe2.Destroy;
End;

Come ripostato nel commento del codice, la seconda chiamata a "Classe1.Metodo1" utilizzer l'implementazione di "TClasse2" poich il tipo run-time al momento della chiamata "TClasse2". Le differenze tra metodi virtuali e dinamici consiste essenzialmente nell'implementazione dellq chiamata al metodo in fase di esecuzione. I metodi viruali sono ottimizzati per velocit, mentre i metodi dinamici per dimensione. Un'altro tipo interessante di metodo e quello dei metodi astratti. I metodi astratti permettono di definire metodi senza alcuna implementazione del metodo stesso. Ci risulta utilie quando si vogliono creare delle classi base che stabiliscano l'interfaccia base dei metodi ma che permettano alle classi discendenti di definire l'implementazione di quei metodi. Questo tipo di metodo un particolare tipo di metodo astratto o dinamico. Per dichiarare un metoto astratto, occorre specificare dopo le parole chiave virtual o dynamic la praola chiave abstract. Altri due tipi di metodi importanti nella programmazione ad oggetti sono i metodi costruttori e distruttori. Questi si occupano, come dicono le parole stesse, di creare ed inizializzare un'istanza di classe o di distruggere e liberare la memoria occupata dall'istanza di classe precedentemente creata. I metodi costruttori sono caratterizzati dalla parola chiave constructor al posto della parola chiave procedure o function. L'implementazione di questi metodi, prevede la creazione dell'istanza di classe allocando memoria nello heap, ed l'inizializzazione automatica a zero dei valori ordinal, a nil dei campi puntatore e classe, l'azzeramento di tutti i campi string. Solitamente all'interno di un metodo costruttore viene prima di tutto chiamato il metodo costruttore ereditato dall'antenato per inizializzare il codice comune, dopo di che viene

eseguito il codice necessario ad inizializzare il codice specificao per la classe discendente. Potrebbe capitare che durante l'esecuzione del codice di inizializzazione si generino delle eccezioni; in tal caso verr eseguito automaticamente il distruttore destroy per disturggere l'oggetto non completato. Nella creazione di un oggetto, occorre chiamare il metodo costruttore facendolo precedere dal nome della classe di appartenenza dell'oggetto che si vuole creare. In tal modo, il metodo costruttore restituir, al termine della chiamata, un puntatore all'istanza appena creata. Se il metodo costruttore viene chiamato utilizzando un riferimento ad un oggetto, questo non restituisce nulla. I metodi distruttori seguono le stessa regola dichiarativa dei metodi costruttori tranne per il fatto che utilizzano la parola chiave destructor al posto di constructor. I metodi distruttori si occupano di distruggere l'oggetto e deallocare la memoria da esso occupata. Per chiamare un metodo distruttore bisogna fare riferimento ad un oggetto istanza come per esempio MioOggetto.Destroy; La chiamata al metodo destroy produce l'immediata esecuzione del codice contenuto nell'implementazione del metodo destroy stesso. All'interno del metodo distruttore viene eseguito tutto quel codice atto ad eliminare eventuali altre istanze di oggetti incorporati e nella liberazione della memoria da loro occupata. L'ultima istruzione di un metodo destroy , solitamente una chiamata al metodo destroy ereditato in maniera da eliminare i campi e ripulire la memoria occupata dal codice ereditato. Come detto in precedenza, se durante la creazione di un oggetto viene sollevata una eccezione, viene eseguito il metodo destroy. In tal caso, questo metodo deve essere pronto ad interagire con codice non completamente inizializzato che, in particolare per i campi di tipo class, significa controllare se il cmapo ha valore Nil prima di effettuare la chiamata al metodo destroy della classe di quel campo. Per questo Delphi mette a disposizione il metodo Free definito nella classe TObject che permette di gestire in automatico il controllo sulla presenza di valori Nil prima di eseguire la distruzione dell'oggetto.

Gestori di messaggi
Un altro tipo di metodo messo a disposizione in Delphi il gestore di messaggi. Un gestore di messaggi sono metodi che rispondono ai messaggi inviati da Windows, da altre applicazioni o dall'applicazione che stiamo realizzando. Esistino messaggi predefiniti e messaggi che possono essere dichiarati dallo sviluppatore. Per dichiarare un gestore di messaggi, si include la direttiva message dopo la dichiarazione del metodo seguito da un identificatore integer costante di messaggio il cui valore deve essere compreso tra 1 e 49151. Alcuni dei messaggi utilizzati da Windows e quelli utilizzati dai controlli della VCL sono definiti nella unit messages. Nella definizione di ogni messaggio necessario dichiarare una costante contenente l'ID del messaggio ed una struttura (un record) che descriva il contenuto del messaggio. Ecco la dichiarazione di un gestore di messaggio er il messaggio WM_CHAR
Type

TTextBox =
Private Procedure

Class(TCustomControl)

WMChar(Var Message : TWMChar);

message

WM_CHAR;

...
End;

Il codice seguente, ripreso dal manuale del linguaggio, mostra come implementare il gestore di evento dichiarato in precedenza affinch venga eseguito un codice particolare alla pressione del tasto invio in un controllo di tipo TTextBox e l'esecuzione del codice ereditato per tutti gli altri tasti
Procedure Begin If

TTextBox.WMChar(Var Message : TWMChar);

Chr(Message.ChrCode) = #13

then

ProcessEnter

Else Inherited; End;

Tramite l'istruzione Inherited viene ricercato nelle classi antenato una implementazione del gestore per quel messaggio e qualora non ne sia disponibile una, viene eseguito il metodo gestore predefinito DeafultHandler definito nella classe TObject. Per richiamare un gestore di messaggio, si utilizza il metodo Dispatch dichiarato nella classe TObject. Questo metodo richiede che gli venga passato un record il cui primo campo sia di tipo cardinal e contenga l'ID del messaggio.
LEZIONE 24:

Classi ed Oggetti: le propriet

Le propriet sono simili ai campi di un oggetto. La differenza tra i campi e le propriet sta nel fatto che i primi fanno riferimento a delle locazioni di memoria mentre le seconde possono definire delle azioni per l'accesso in lettura e scrittura del loro contenuto. La sintassi di dichiarazione di una propriet il seguente
Property

NomeProprieta[Indici]: Tipo index CostanteIntera Specificatori;

NomeProprieta , neanche a dirlo, un identificatore riconosciuto valido che identifica la propriet. Indici facoltativo ed utilizzato nella dichiarazione di propriet di tipo array ed indica una sequenza di dichiarazioni di parametri del tipo identificatore1, identificatore2 : tipo separate da punti e virgola. Anche Index CostanteIntera facoltativo e serve definire degli specificatori di indice utili per condividere gli stessi metodi di accesso durante la rappresentazione di valori differenti. Specificatori una sequenza dei seguenti specificatori: read, write, stored, default, nodefault, implements. In ogni dichiarazione di propriet necessario indicare almeno uno specificatore read o write. N.B. Non possibile utilizzare nelle propriet l'operatore @ ne possono essere passate come parametri var; questo perch non detto che la propriet esista in memoria, la potrebbe aver un metodo read che recupera il valore per esempio da un database o da altra struttura di dati. Per gli specificatori di accesso read e write possiamo dire che questi possono fare riferimento a metodi o a campi. Nel caso di metodi, questi dovranno essere definiti nella stessa classe in cui stata dichiarata la propriet. Per lo specificatore read nel di un metodo, questo dovr essere una funzione senza parametri il cui tipo restituito sia dello stesso tipo della propriet. Per i metodi relativi allo specificatore write, questi dovranno essere procedure che abbiano un solo parametro d'ingresso dello stesso tipo della propriet. Quello che segue un esempio do dichiarazione di propriet
Property

TimeOut :

Cardinal read

GetTimeOut

write

SetTimeOut;

La dichiarazione dei metodi degli specificatori read e write la seguente


Function

GetTimeOut :

Cardinal; Cardinal);

Procedure

SetTimeOut(Value :

Il codice riportato potrebbe far parte di un controllo che implementi al suo interno un timer di tipo TTimer che permette di impostare e leggere il valore di intervallo del timer stesso. Avendo la classe TTimer una sua propriet Interval i metodi dichiarati sopra potrebbo essere implementati come segue considerando che l'istanza della classe TTimer si chiami FTimer

Function Begin

TMioControllo.GetTimeOut :

Cardinal;

Result := FTimer.Interval;
End; Procedure Begin If

TMioControllo.SetTimeOut(Value :

Cardinal);

Value <> FTimer.Interval

then

FTimer.Interval := Value;
End;

Quando tra gli specificatori in una dichiarazione di propriet compare solamente lo specificatore read, la propriet sar di sola lettura e di sola scrittura nel caso compaia solamente lo specificatore write. Un esempio di dichiarazione di propriet sola lettura
Property

TimeElapsed :

Boolean read

FTimeElapsed;

Come visto all'inizio della trattazione delle propriet, queste possono contenere nella dichiarazione dei parametri aggiuntivi come una sequenza di dichiarazione di parametri. In questo caso abbiamo a che fare con propriet indicizzate che possono rappresentare elementi di un elenco (TStringList.Items), controlli figli o i pixel di una bitmap. Ecco alcune dichiarazioni di propriet array (dal manuale del linguaggio)
Property

Objects[Index : Pixels[X, Y :

Integer]

: TObject : TColor
string]

read

GetObject

write

SetObject;

Property

Integer]

read

GetPixel

write

SetPixel;

Property

Values[const Name :

: GetValue
write

string read

SetValue;

Il formato dell'elenco di parametri lostesso di quello utilizzato nella dichiarazione di procedure e funzioni eccezion fatta per le parentesi quadre al posto delle tonde. Abbiamo praticamente a che fare con degli array dove per mentre nel vero tipo array gli indici possono essere di tipo ordinal solamente per quanto riguarda le propriet array questi possono essere di qualsiasi tipo. Le propriet array non ammettono negli specificatori di accesso dei campi, ma solamente dei metodi. Questo per via degli indici che vengono automaticamente passati al metodo che si occuper di recuperare o di scrivere i dati all'indice richiesto. Un metodo per lo specificatore read deve necessariamente essere una funzione che accetta lo stesso numero e tipo di parametri elencati nella lista degli indici della propriet, passatigli nello stesso ordine; in oltre il tipo restituito dalla funzione dovr essere dello stesso tipo della propriet. Il metodo dello specificatore write dovr essere una procedura che accetti come parametri lo stesso numero e tipo di parametri elencati nella lista degli indici della propriet, passatigli nello stesso ordine ed in pi il classico parametro valore contenente il valore da settare nella propriet. Ecco alcuni esempi di metodi per propriet array che fanno riferimento ai metodi dell'esempio precedente
Function Function Function

GetObject(Index : GetPixel(X, Y :

Integer)

: TObject;

Integer)

: TColor; : String; Value : TObject);

GetValue(const Name : SetObject(Index :

String)

Procedure

Integer;

Procedure Procedure

SetPixel(X, Y :

Integer;

Value : TColor);
String);

SetValue(const Name, Value :

Le propriet array possono essere accedute come classicamente si accede alla locazione di un array nel seguente modo Canvas.Pixel[5, 45] := clGreen; quando si definisce una propriet array, si ha la possibilit di impostarla come propriet predefinita della classe a cui appartiene. Ci permette di accedere alla propriet specificando immediatamente dopo il nome della classe l'elenco di indici. Per diefinire una propriet predefinita per un classe occorre specificare dopo la dichiarazione della propriet la direttiva default. Ad esempio avendo una dichiarazione del tipo
Type

TStringArray =
Public Property

Class

Strings[Index :

Integer]

String

...;

default;

...
End;

Supponendo che l'istanza della classe TStringArray si chiami StringArray potremmo accedere alla propriet Strings della classe nel seguente modo StringArray[10] := 'Carlo'; al posto di StringArray.Strings[10] := 'Carlo'; Per ovvi motivi, una classe pu avere solamente una propriet predefinita.
LEZIONE 25:

Classi ed Oggetti: gli specificatori

Abbiamo visto che Delphi permette di specificare anche altri parametri nella dichiarazione delle propriet di una classe e tra questi ci sono gli specificatori di indice che permettono la condivisione dello stesso metodo a pi propriet. L'indice serve, appunto, a distinguere per quale propiet il metodo sta lavorando. Infatti l'indice specificato nella dichiarazione della propriet viene automaticamente passato al metodo che decidar le istruzioni da eseguire in base al valore dell'indice. Per definire uno specificatore di indice occorre indicare nella dichiarazione della propriet, dopo il tipo della propriet stessa, la direttiva index seguita da una costante integer. Anche per gli specificatori di indice per gli specificatori di accesso possono essere definiti solamente dei metodi che per lo specificatore di accesso read devono contenere come ultimo parametro passato alla funzione un parametro aggiuntivo di tipo integer e per lo specificatore write deve essere una procedura che tra i parametri passatigili contenga un ulteriore parametro, sempre di tipo integer, posizionato per dal secondo all'ultimo posto nella lista dei parametri e comunque prima del parametro contenente il valore da assegnare alla propriet.

Gli specificatori di memorizzazione


Gli specificatori di memorizzazione servono ad indicare a Delphi come gestire il salvataggio delle informazioni RTTI , ovvero se salvare o meno i valori delle propriet published nei file form (.DFM). Questi sono: stored, default, nodefault e sono specificatori facoltativi.

La direttiva stored indica a Delphi se il valore di quella propriet deve essere salvato o no nel file form. Questa deve essere seguita dal valore boolean True o False. Se non viene specificato alcun specificatore stored Delphi salva automaticamente il valore della propriet. La direttiva default indica a Delphi quale il valore di di default per quella propriet se nel file form non specificato alcun valore. Abbiamo visto l'uso di questa direttiva nelle propriet array, ma in questo caso si comporat diversamente. Infatti in questo caso va seguita da una costante dello stesso tipo della propriet; lo specificatore di default valido solamente per i tipi ordinal e set sempre che i loro valori massimi e minimi siano compresi tra 0-31. Se nella dichiarazione della propriet non viene indicata ne la direttiva default o nodefault, Delphi considera come se fosse stata utilizzata la direttiva nodefault.

Operatori di classe
Delphi mette a disposizione degli opreratori speciali per operare sulle classi. Questi operatri utilizzano dei metodi definiti nel tipo TObject e che vengono ereditati da ogni classe. Questi operatori sono is e as e fanno uso dei metodi derivati da TObject, ClassType, ParentClass, InheritsFrom. L'operatore is esegue il controllo dinamico del tipo di un oggetto in fase di esecuzione per verificare la classe attuale dell'oggetto stesso. L'utilizzo dell'operatore is il seguente Oggetto is Classe Questa espressione restituisce True se l'istanza di Oggetto un'istanza della classe Classe o da uno dei suoi discendenti. Se Oggetto vale nil il risultato della valutazione False. Il tipo di Oggetto e Classe devono essere correlati fra loro ovvero essere uno l'antenato dell'altro, altrimenti in fase di compilazione viene generato un errore. L'operatore as serve ad eseguire conversioni di tipo ovvero dato un'istanza di Oggetto ed un classe Classe l'espressione Oggetto as Classe restituisce un riferimento all'oggetto Oggetto ma con il tipo indicato da Classe. Come per l'operatore is, l'istanza Oggetto e la classe Classe devono essere correlati ovvero essere uno l'antenato dell'altro altrimenti si avr la generazione di un errore di compilazione. In fase di esecuzione Oggetto deve essere un'istanza della classe Classe o nil pena il sollevamento di un'eccezione. Un esempio dell'utilizzo dell'operatore as (Sender
as

TLabel).Caption := 'Elaborazione in corso...';

Le regole di precedenza dell'operatore as richiedono a volte che l'intera espressione contenente l'operatore venga racchiusa tra parentesi. Nessun problema comunque se si utilizza normalmente questa pratica per evitare errori e perdita di tempo nel debug del codice.
LEZIONE 26:

Gestione degli errori: Eccezioni

Gli errori in Delphi vengono gestiti attraverso le eccezioni. Quando si verifica un errore in fase di esecuzione, viene sollevata un'eccezione. Un eccezione non altro che un oggetto che contiene informazioni sull'errore (come per esempio il messaggio di errore) che vengono passate ad un gestore di eccezioni. Ci permette di distinguere la logica di esecuzione del codice da quella che gestisce gli errori. Ogni volta che si utilizza la unit SysUtils, tutti gli errori di runtime vengono trasformati automaticamente in eccezioni. Esistono in Delphi diverse classi di eccezioni predefinite, ma anche possibile definirne di nuove. La dichiarazione di una eccezione uguale alla dichiarazione di un normale classe. In genere tutte le eccezioni derivano dalla classe Exception definita nella unit SysUtils. Come per una normale classe, possibile raggruppare le eccezioni utilizzando l'ereditariet. Le seguenti dichiarazioni sono prese dalla unit SysUtils Type EMathError =
Class(Exception)

EInvalidOp = EZeroDivide =

Class(EMathError); Class(EMathError);

Proprio come in una normale classe, all'interno della dichiarazione di una eccezione possono essere dichiarati dei campi che possono contenere informazioni addizionali sull'errore come per esempio il codice dell'errore.
Type

EInOutError =

Class(Exception)

ErrorCode : Integer;
End;

Per creare un oggetto eccezione occorre utilizzare la seguente sintassi Raise Oggetto at Indirizzo oppure Raise Oggetto.Create at Indirizzo All'interno dell'istruzione raise occorre secificare un oggetto eccezione oppure chiamare il costruttore della classe eccezione. Il parametro Indirizzo pu contenere un puntuntatore ad una procedura o funzione, utile per sollevare l'eccezione dal punto dello stack precedente a quello in cui si verificata l'eccezione. Sia il parametro Oggetto che Indirizzo sono facoltativi. Se non viene indicato nessun parametro dopo l'istruzione raise si avr come risultato la rigenerazione dell'eccezione attiva. Quando viene sollevata un'eccezione tramite l'istruzione raise il controllo dell'esecuzione viene trasferito al gestore di eccezioni, ricercandolo partendo dal gestore pi interno alla classe data (ad esempio un blocco Try...Except). Le eccezioni generate vengono automaticamente distrutte dopo essere state gestite; quindi non si deve mai eliminare manualmente l'eccezione creata.

Try...Except
L'istruzione Try...Except permette di definire un gestore di eccezioni. Il formato dell'istruzione Try Codice Except GestioneEccezioni End; In pratica il codice eseguito tra le parole Try ed Except protetto dal sollevamento delle eccezioni. Qualora un'eccezione fosse sollevata all'interno del codice Codice il controllo dell'esecuzione viene passato al codice rappresentato da GestioneEccezioni. Il blocco GestioneEccezioni pu essere composto da una sequenza di istruzioni o da una sequenza di gestori di eccezioni. Per definire un gestore di eccezione per una certa eccezione, occorre utilizzare la seguente sintassi On Identificatore : TipoClasseEccezione do Codice Identificatore facoltativo e permette di definire un riferimento all'oggetto eccezione che valido solamente per il codice contenuto nel blocco Codice.

TipoClasseEccezione indica invece il tipo della classe a cui corrisponde l'eccezione da gestire. Codice il blocco di codice che viene eseguito se il tipo dell'eccezione sollevata corrisponde al tipo indicato in TipoClasseEccezione o ne un antenato. Facoltativamente pu essere definito dopo l'elenco dei gestori di eccezione un blocco else che gestica tutte le eccezioni non gestite dai gestori definiti. Ovviamente, se non si verisicano eccezioni durante l'esecuzione del codice Codice l'esecuzione prosegue fino all'istruzione precedente la parola except e quindi salta all'istruzione immediatamente successiva alla parola chiave end. Ecco due esempi che raccolgono ci che abbiamo visto

Try

...
Except On On On Else

EZeroDivide EOverFlow EMathError


do

do

GestisciZeroDivideException;

GestisciOverFlowException; GestisciMathErrorException;

do

GestisciTutteLeAltreEccezioni;
End;

Try

...
Except

GestisciLeEccezioni;
End;

Try...Finally
Il blocco Try...Finally assicura che una determinata sequenza di codice venga eseguita indifferentemente dal fatto che sia stata sollevata un'eccezione o no. Per esempio utile in alcuni casi assicurarsi di liberare le risorse occupate in ogni caso. La sintassi simile a quella di Try..Except Try Codice Finally CodiceDaEseguireComunque End;

L'esecuzione del codice procede dalla prima istruzione del blocco di codice Codice. Se si verificano eccezioni, l'esecuzione del codice Codice viene interrotta e riprende con la prima istruzione contenuta nel blocco CodiceDaEseguireComunque. Al termine dell'esecuzione del codice CodiceDaEseguireComunque, l'eccezione viene sollevata di nuovo. Se non si verifica alcuna eccezione, l'esecuzione procede fino all'ultima istruzione nel codice Codice e prosegue con l'esecuzione del codice contenuto nel blocco CodiceDaEseguireComunque.

LEZIONE 27:

La VCL (Visual Component Library)

Questa sezione del corso tratter della libreria di componenti (visibili e non) e delle classi messeci a disposizione da Delphi. Questa libreria prende il nome di VCL da Visual Component Library. Essa una gerarchia di classi che derivano tutte dalla classe TObject. Di questa libreria fanno parte sia i componenti visibili nella componente palette dell'IDE che le altre classi di uso generale. Come gi detto, tutte le classi della VCL discendono da TObject. Questa classe ha dunque un'importanza particolare poich ci permette per esempio di utilizzare il tipo TObject in sostituzione di un qualsiasi altro tipo di classe. Ci visibile per esempio nelle implementazioni dei gestori di eventi che contengono nella loro dichiarazione un parametro (Sender) di tipo TObject che permette appunto il passaggio di qualsiasi classe. Affianco a questo vantaggio, c' lo svantaggio di dover conoscere il tipo dell'oggetto per operare su di esso. Infatti, avendo un riferimento di tipo TObject ad un qualsiasi oggetto, non possibile accedere direttamente alle propriet specifiche di quell'oggetto. Come sempre per Delphi ci viene in contro e ci mette a disposizione dei metodi per risalire al tipo dell'oggetto riferito. Questi metodi sono implementati nella calsse TObject e quindi disponibili per tutti gli oggetti di qualsiasi classe. Questo l'elenco dei metodi: ClassName, ClassNameIs, ClassParent, ClassInfo, ClassType, InheritsFrom, InstanceSize. Vediamo a cosa servono questi metodi.
ClassName:

questo metodo resituisce il nome della classe in formato stringa verifica che il nome della classe corrisponda a quello passatogli ClassParent: questo metodo restituisce un riferimento alla classe base ClassInfo: Restituisce un puntatore ai dai Run Time Type Information relativi alla classe ClassType: pu essere applicato solamente agli oggetti e restituisce un riferimento alla classe dell'oggetto InheritsFrom: verfica se una classe deriva sia in maniera diretta che non dalla classe passatagli come parametro. Questo metodo viene chiamato anche quando si utilizza l'operatore is. InstanceSize: metodo molto interessante in quanto restituisce la dimensione dell'oggetto a run time altrimenti non reperibile. Infatti l'istruzione SizeOf restituirebbe la dimensione del riferimento all'oggetto, quindi la dimensione di un puntatore che rimane fissa a 4 byte.
ClassNameIs:

ClassParent e ClassType sono molto importanti poich ci permettono di operare sulla classe a run time.
La gerarchia delle classi di Delphi pu essere raggruppata in tre aree principali: oggetti in genere, componenti, eccezioni. I componenti sono quelli che generalmente si usano in maniera visuale nell'IDE di Delphi, tramite il Form Designer e l'Object Inspector. Tutte le altre classi sono accessibili tramite codice.

Componenti
I componenti rappresentano il fulcro della programmazione visuale in Delphi. Quando si programma in Delphi, normalmente non si fa altro che scegliere dei componenti e definirne le interazioni tra loro. La maggior parte di questi componenti sono accessibili tramite l'IDE di Delphi nella Component Palette, ma ce ne sono alcuni accessibili solamente da codice come TForm e TApplication. Tutti i componenti derivano da

TComponent che a sua volta deriva da TPersistent. Ci permette all'oggetto di essere memorizzato nel file DFM grazie alle funzionalit di streaming della classe TPersistent. Questi possono essere manipolati a Design Time in maniera visuale tramite gli strumenti messi a disposizione dall'IDE di Delphi. I componenti possono contenere propriet di tipo Published che verranno visualizzate nell'Object Inspector dell' IDE. Abbiamo visto che la gerarchia delle classi di pu dividere in tre aree; anche per i componenti possibile fare un raggruppamento. I componenti si dividono in Controlli o Componenti visuali Componenti non visibili

I Controlli possono a loro volta dividersi in Controlli Windows Controlli Grafici

I controlli sono tutte quelle classi che derivano da TControl e cha hanno le caratteristiche di occupare una posizione specifica nello schermo, avere dimensioni proprie, essere manipolati a Design Time. I controlli Windows o windowed, sono tutti quei componenti visuali che utilizzano finestre del sistema operativo per la loro visualizzazione. Tecnicamente, questi controlli possiedono un Handle assegnato loro dal sistema operativo e derivano da TWinControl. Per quanto riguarda la funzionalit, tutti questi controlli, possono ricevere il fuoco e, in alcuni casi, contenere altri controlli. Appartengono a questa area, tutti i controlli che rappresentano i tipici controlli di windows, come Edit Box, ListBox, ComboBox, ecc. Questi controlli sono in effetti delle classi wrapper pre i common controls ci windows. Windows infatti possiede dei componenti che vengono chiamati controlli. Questi controlli sono, tecnicamente, una finestra che mostra un comportamento specifico e possiede alcuni stili. Altra caratteristica importante di questi controlli che sono in grado di rispondere ai messaggi specifici inviati loro dal sistema operativo. Molti di questi controlli sono stati ereditati da Windows 3.1 e nelle versioni successive, da Windows 95 in poi, ve se ne sono affiancati molti altri. I controlli grafici o non windowed, non possiedono una finestra e quindi non hanno un Handle. Sono sempre controlli visuali e la loro caratteristica che non possono ricevere il fuoco. Sono molto importanti quando si ha la necessit di risparmiare risorse. Derivano tutti da TGraphicControl e vengono gestiti direttamente dalla form che li contiene la quale invia loro tutti gli eventi relativi al disegno ad al mouse. I componenti non visibili sono tutte quelle classi che non sono, per l'appunto, visibili e che derivano quindi da TComponent ma non da TControl. A design time questi si presentano sotto forma di icone mentre a run time possono essere visibili o no come per esempio i box di dialogo per l'apertura ed il salvataggio dei files.

Dalla struttura della gerarchia di classi che abbiamo visto, deriva che molti componenti anno propriet comuni; queste riguardano soprattutto propriet fisiche degli oggetti (dimensioni, colore, stili, cursore, posizione), il nome dell'oggetto stesso (name), l'owner ed il parent. Quest'ultimi due hanno particolare importaza poich rappresentano il proprietario dell'oggetto ed il contenitore dello stesso. La differenza tra Owner e Parent importante; l'owner chi ha creato l'oggetto ed il parent il contenitore dell'oggetto. Un esempio potrebbe essere una form di nome Form1 che crea un componente Button1. In questo caso la form Form1 sia Owner che Parent. Se la form Form1 spostasse il componente Button1 nella form Form2, quest'ultima sarebbe il parent di Button1, ma Form1 ne rimarrebbe sempre proprietaria. Anche la propriet name importantissima; essa infatti permette di identificare l'oggetto ed accedere ai suoi metodi e propriet. Viene utilizzata anche dall'IDE per definire automaticamente i nomi dei gestori degli eventi. Questa propriet deve essere conforme alle specifiche definite per qualsiasi identificatore Object Pascal valido. Non pu quindi contenere spazi o caratteri non validi. Oltre a propriet comuni, gli oggetti della VCL hanno anche eventi e metodi comuni. Risultando troppo lunga la trattazione di questo argomento in questa sede, si rimanda al testo di Marco Cant "Programmare con Delphi 5".
LEZIONE 28:

Le interfacce grafiche!

Tramite Delphi costruire un'interfaccia grafica di una semplicit e velocit estrema. sufficiente selezionare i componenti da utilizzare dalla component palette e sistemarli sulle schede (Forms). Quando si crea un nuovo progetto, Delphi crea automaticamente una scheda detta principale (MainForm) che sar la prima ad essere creata in fase di esecuzione. Aggiungendo altre schede al progetto, si ha la possibilit di decidere quale tra questa sar la scheda principale. Per default Delphi crea in fase di esecuzione tutte le schede definite in fase di progetto a meno che non venga indicato altrimenti. In base all'applicazione da realizzare si decide se occupare o no memoria con schede che magari non hanno un uso continuo ed in questo caso vengono eliminate dalla liste delle schede da creare automaticamente e si proceder poi tramite codice a creare quest'ultime al momento opportuno. Per decidere quali schede creare automaticamente in esecuzione, scegliere dal menu "Project" la voce "Options..."; solitamente, la finestra che compare visualizza per prima il foglio riguardante le Forms ed qui che possizmo decidere, spostando dalla lista di sinistra a quella di destra le forms da creare manualmente o vice versa. Sempre tramite questa finestra possibile selezionare la form che sar la MainForm dell'applicazione. Per creare manualmente una form tramite codice utilizzare il codice seguente Application.CreateForm(TForm1, Form1); Ovviamente TForm1 e Form1 andranno sostituiti secondo le proprie esigenze. Insieme alla classe TForm che rappresenta le schede (o le finestre) dell'applicazione, ci sono due altre classi di rilevante importanza: TApplication e TScreen. La prima raccoglie le propriet fondamentali di un'applicazione, come l'icona, il titolo dell'applicazione, il file di help associato, eventi riguardanti l'applicazione stessa come OnActivate, OnMessage, OnIdle ed altri. La classe TScreen incapsula le caratteristiche dello schermo sul quale viene eseguita l'applicazione. TScreen permette anche di recuperare l'elenco dei monitor e le loro dimensioni in caso di utilizzo di pi monitor per la visualizzazione. Proviamo a costruire una semplice applicazione con una semplicissima interfaccia grafica. Creiamo un nuovo progetto, se nell'IDE non ve ne gi uno nuovo, scegliendo dal menu "File" il comando "New Application". Avremo cos visualizzata una scheda vuota pronta per ricevere i componenti per l'interfaccia grafica. Selezioniamo la form nominata Form1 cliccandovi una volta ed andiamo ad impostare le sue dimensioni nell'Object Inspector; assegnamo alla propriet height il valore 250 e alla propriet width il valore 350. Ora prendiamo dalla Component Palette (vedi lezioni riguardanti l'IDE) un componente Button cliccando sull'icona e mettiamolo nella nostra form cliccandoci semplicemente sopra. Vedremo il componente

posizionato nella form nel punto in cui abbiamo cliccato. Posizioniamolo al centro della form facendo clic con il tasto destro del mouse sul componente Button appena piazzato e scegliamo il comando "Align".

Nella finestra che compare , nel gruppo "Orizontal" e "Vertical" scegliamo "Center in Window"

Scegliendo "OK" il nostro bottone verr automaticamente centrato nella form. Ora modificahiamo il testo visualizzato sul tasto modificando, con lo stesso procedimento utilizzato per modificare le dimensioni della form, la propriet Caption impostandola a "Press me".

Ora scegliendo il metodo che pi vi comodo tra quelli visti nelle lezioni riguardanti l'ambiente di sviluppo, associamo del codice all'evento che si attiver alla pressione del nostro bottone; in questo caso, sapendo che l'evento predefinito per il componente TButton l'evento OnClick faccio doppio click su di esso perch Delphi crei il codice base dell'evento.

Scriviamo il seguente codice nel blocco del gestore dell'evento OnClick appena creato

MessageDlg('Hai premuto un tasto', mtInformation, [mbOk], 0); In questo modo alla pressione del bottone verr visualizzata una finestra di dialogo modale recante il messaggio "Hai premuto un tasto". Da notare che la natura modale della finestra di dialogo visualizzata arresta l'esecuzione del programma fintanto che l'utente non ha premuto il buttone "OK". Eseguiamo il codice appena scritto premendo il tasto rapido F9 oppure cliccando sul tasto degli strumenti dell'IDE, oppure ancora scegliendo il comando "Run" dal menu "Run". Ecco il risultato del nostro breve e semplice lavoro posto nella bara

Ora apportiamo una modifica al codice per dimostrare che l'esecuzione del codice si arresta fino a che non viene premuto il bottone "OK" nella finestra di dialogo. Aggiungiamo dopo la riga di codice scritta in precedenza la seguente riga Beep; Questa riga far emettere un beep, o dalla scheda audio o dallo speaker del PC a seconda della configurazione del sistema, dopo che avrete premuto il tasto "OK" nella finestra di dialogo. Proviamo ora ad aggiungere un'altro componente e a d aggiungere altro codice. Aggiungiamo un componente Label che utilizzeremo per visualizzare il risultato di una nostra elaborazione. Con lo stesso procedimento seguito per aggiungere alla form il componente Button, aggiungiamo il componente Label rappresentato nella Component Palette con l'icona . Ora modifichiamo la sua posizione all'interno della form impostando per la propriet Left il valore16 e per la propriet Top il valore 16. Ora modifichiamo il testo della Label impostando la sua propriet Caption a "Elapsed time (ms):". Prendiamo un altro componente Label e posizioniamolo a 104, propriet Left, 16, propriet Top. Lasciamo la propriet Caption cos come . Ecco come si presenta la nostra form dopo queste modifiche.

Ora aggiungiamo del codice all'evento OnClick del nostro bottone, per calcolare il tempo che trascorre tra la visualizzazione della finestra di dialogo e la chiusura della stessa dopo che l'utente ha premuto il tasto "OK" contenuto in essa. Modifichiamo il codice del gestrore dell'evento OnClick del bottone nel modo seguente:
procedure

Var Start, begin

TForm1.Button1Click(Sender: Elapsed : Integer;

TObject);

Start := GetTickCount; MessageDlg('Hai premuto un tasto', mtInformation, [mbOk], 0); Elapsed := GetTickCount - Start; Label2.Caption := IntToStr(Elapsed); Beep; end; La funzione GetTickCount una funzione di Windows che restituisce il numero di millisecondi trascorsi dall'avvio di Windows stesso. In Delphi questa funzione si trova, insieme a tutte o quasi le altre funioni di windows, nella unit "windows.pas". La variabile Start conterr il numero di millisecondi trascorsi dall'avvio di Windows fino al momento precedente la chiamata alla funzione MessageDlg. Elapsed ci serve per contenere il numero di millisecondi trascorsi dal momento predente la chiamata alla funzione MessageDlg alla chiusura della stessa. Il calcolo molto semplice ed il risultato quello della differenza tra il numero di millisecondi trascorsi dall'avvio di Windows e il numero di millisecondi trascorsi un momento prima di chiamare la funzione MessageDlg. La penultima riga assegna, effettuando una conversione da intero a stringa, il risultato dell'eleborazione alla propriet Caption dell'etichetta nominata Label2. Aggiungiamo anche un gestore per l'evento OnShow della form nel quale puliamo il valore della propriet Caption di Label2. Dopo aver selezionato la form e tramite la pagina Events dell'Object Inspector creato il gestore per l'evento OnShow dovremmo inserire il codice seguente (badate non l'intestazione della procedura, solo la riga di codice tra Begin e End; il resto dovrebbe averlo creato automaticamnte Delphi tramite l'Object Inspector)
procedure begin end;

TForm1.FormShow(Sender:

TObject);

Label2.Caption := '';

Ora eseguiamo il programma. Ecco il risultato dell'esecuzione del programma. Provate ad aprire a chiudere la finestra di dialogo pi volte e vedrete il valore riportato vicino a Label1 cambiare.

LEZIONE 29:

Approfondimento sulle Forms

Dopo l'esempio di semplicissima applicazione realizzato nella sezione riguardante le interfacce grafiche, vediamo ora di approfondire un po' il discorso sulle forms presentandone le propriet i metodi e gli eventi pi interessanti.

Propriet
Come abbiamo visto le forms sono principalmente dei contenitori per gli altri oggetti dell'interfaccia grafica. Alucne delle loro propriet permettono di adattarle alle esigenze del progettista. Una form per esempio caratterizzata da un titolo. Questo titolo pu essere impostato sia a design time che a run time agendo sulla propriet Caption della form. La propriet Caption una delle propriet comuni a molti dei componenti della VCL e fa riferimento di solito a testo che viene visualizzato come intestazione del componente stesso (TLabel, TForm, TButton, ecc). Nella barra del titolo, dove compare appunto il titolo della form, compaiono anche dei pulsanti il comportamento quello standard di tutte le finestre di Windows: massimizzare la finestra, minimizzarla, chiuderla. Vi anche un altro pulsante che non sempre compare ed quello dell'help contestuale. I Delphi, possibile decidere quali pulssanti visualizzare e quali no agendo sulla propriet BorderIcons della form. La propriet BorderIcons di tipo Set e pu contenere tutti, alcuni o nessuno dei pulsanti standard. Tramite questa propriet anche possibile decidere se disattivare o no il menu di sistema che compare cliccando con il mouse sull'icona della finestra in alto a sinistra (o tramite tastiera); questo contiene l'elenco dei comandi che possono essere applicati alla finestra, come la massimizzazione, minimizzazione, la chiusura, ecc. Tramite codice anche possibile aggiungere comandi speciali a questo menu. Da notare che le voci di menu rispecchiano lo stato dei pulsanti sulla destra; percui se il tasto minimizza disattivato, lo sar anche la relativa voce nel menu di sistema.

Tra le caratteristiche di una form c' anche il bordo. Per tipo di bordo in Delphi si intende la funzione che il bordo avr. Una Form pu avere un bordo che permetta di ridimensionare la finestra, pu non avere bordo (per esempio una splash form), pu avere un bordo non ridimensionabile, pu avere un bordo che dia alla form le caratteristiche tipiche di una finestra di dialogo (come quella utilizzata nell'esempio delle interfacce grafiche) oppure il bordo pu fare assumere alla form altre caratterische come per esempio cambiare il tipo di finestra in ToolWindow sia ridimensionabile che non (le tool window sono quelle finestre utilizzate da molti software di grafica per contenere gli strumenti di lavoro, pennelli, colori, ecc). Una propriet molto importante che influisce sul comportamento della finestra la propriet FormStyle che permette di definire se la form sar una finestra di una applicazione SDI (Single Document Interface), come per esempio il Notepad di windows, oppure comportarsi come una finestra di un'applicazione MDI (Multiple Document Interface) come Word. In questo caso abbiamo due tipi di finestre MDI una finestra detta frame (cornice) che il contenitore per le altre form definite Children. L'impostazione di default quando si crea una nuova Form fsNormal che indica che la finestra si comporter come SDI. C' anche un'altro valore che pu essere impostato per questa propiet ed fsStayOnTop che indica che la finestra sar visualizzata sempre al disopra di tutte le altre. Ritorneremo sulle applicazioni SDI ed MDI pi avanti dove approfondiremo il discorso e realizzeremo degli esempi. Un form caratterizzata oltre ad una dimensione, anche da una posizione. Per posizionare un Form nello schermo sufficiente modificare i valori delle propriet Top e Left della form. Queste sono le coordinate espresse in pixels relative all'angolo superiore dello schermo ed all'angolo superiore sinistro della form. Vi un'altra propriet che influisce sul posizionamento della form nello schermo, Position i cui valori validi sono: poDefault, poDefaultPosOnly, poDefaultSizeOnly, poDesktopCenter, poMainFormCenter, poOwnerFormCenter, poScreenCenter. Con poDesigned la form appare posizionata nello schermo e con la stessa larghezza ed altezza definita a design time. Con poDefault la form appare nello schermo in una posizione e con delle dimensioni definite da Windows. Ad ogni avvio dell'applicazione, la form si muove verso il basso e verso destra. Con poDefaultPosOnly la form viene visualizzata con le dimensioni con cui stata creata a design

time, ma la posizione definita da Windows. Ad ogni avvio dell'applicazione, la forma si sposta verso il basso e verso destra. Con poDefaultSizeOnly la form appra nella posizione in cui stata lascita a design time, ma la dimensione stabilita da Windows. Con poScreenCenter la form mantine le dimensioni definite a design time, ma viene posizionata al centro dello schermo. Con poDesktopCenter la form mantiene le dimensioni definite a design time, ma viene posizionata al centro del desktop. Con poMainFormCenter la form mantiene le dimensioni assegnatele a design time, ma posizionata al centro della form principale dell'applicazione. Questa posizione pu essere solamente usata con le forms secondarie. Se utilizzata in una form principale l'effetto lo stesso di poScreenCenter. Con poOwnerFormCenter la form mantiene le dimensioni assegnatele a design time, ma posizionata al centro della form indicata nella propriet Owner della form stessa. Se in Owner non viene specificata alcuna form, questa impostazione restituisce lo stesso risultato di poMainFormCenter. Si pu anche decidere di rendere una form autodimensionante in base ai componenti in essa contenuti o allo spostamento di essi. Ci possibile impostando la propriet AutoSize della form stessa. Normalmente questa propriet impostata a False e fa si che qualora dei componenti non risultassero visibili nell'area client della form, vengano visualizzate le barre di scorrimento orizzontale e verticale. Si pu decidere il comportamento delle barre di scorrimento sia orizzontale che verticale. Se viene impostata la propriet AutoSize della form a True, non si ha la comporsa automatica delle barre di scorrimento ma la form verr ridimensionata sia in orizzontale che verticale per accogliere i componenti che prima non erano completamente visibili nell'area client. Le Form di Delphi permettono anche di definire su aquale monitor visualizzare la form nel caso di un ambiente fornito di pi monitor, come nel caso delle schede Matrox DualHead. Per fare ci bisogna impostare adeguatamente la propriet DeafultMonitor della form che vuole venga visualizzata in un monitor particolare. Un'altra propriet interessante quella che permette di stabilire come una form viene visualizzata alla sua prima visualizzazione. La propriet WindowState permette di scegliere tra wsNormal, che indica lo stato di visualizzazione normale della form, wsMaximized che indica che la form massimizzata o wsMinimized che indica che la form minimizzata. Questa propriet pu anche essere letta per verificare lo stato attuale della form. Ancora pi interessante la propriet KeyPreview che peremtte di intercettare la pressione dei tasti della tastiera prima che il componente attivo nella form si accorga dell'accaduto. In poche parole quando viene premuto un tasto sulla tastiera, il componente attivo in quel momento riceve un messaggio di Windows che lo informa dell'accaduto. Impostando la propriet KeyPreview a True, questo comportamento viene alterato permettendo alla form di intercettare il messaggio di Windows prima che questo giunga al componente attivo. Vediamo ancora un'altra propriet delle forms ed la propriet Icon che permette di definire l'icona visualizzata in alto a sinitra nella barra del titolo della form. Se a questa propriet non viene assegnata una icona, la form prender l'icona dell'applicazione, che di default Delphi 5 imposta a .

Metodi
Dopo aver visto le propriet pi interessanti delle froms, vediamone ora i metodi. Sicuramente il pi importatnte il metodo Create comune a tutti i componenti della VCL. Questo metodo crea un oggetto di classe TForm. Da notare che il metodo crea solamento l'oggetto, percui non ci si deve aspettare di vedere visualizzare la form dopo la sua chiamata. Della visualizzazione della form si occupano due metodi: Show e ShowModal. Il primo visualizza la form e restituisce il controllo all'applicazione, mentre il secondo visualizza la form e attende che questa venga chiusa prima di restituire il controllo all'applicazione. L'effetto quello che abbiamo visto nell esempio delle interfacce grafiche: la finestra di dialogo. Il metodo ShowModal restituisce anche un valore che identifica il tipo di chiusura della finestra. Un elenco dei valori validi : mrNone, mrOk, mrCancel, mrAbort, mrRetry, mrIgnore, mrYes, mrNo, mrAll. Per chiudere una finestra modale sufficiente assegnare alla propriet ModalResult della stessa un valore diverso da zero Il metodo Hide nasconde la form. Da notare che la form viene solamente nascosta ma non viene eliminata dalla memoria. Quindi, giocando con i metodi Show e Hide, si pu visualizzare e nascondere la form fino a che

la forma non sar chiusa. Per eliminare dalla memoria la form, chiamare il metodo Free, come per tutti i componenti della VCL. La chiamata a questo metodo imposta la propriet Visible della form a False. Il metodo Close chiude la form. Anche questo metodo non distrugge l'oggetto form. per distruggerlo occorre chiamare il metodo Free oppure assegnare alla variabile Action nell'evento OnClose della form il valore caFree. Cos alla chiusura della form questa verr anche eliminata dalla memoria automaticamente.

Eventi
Sicuamente l'evento pi utilizzato di una form l'evento OnShow che viene invocato quando appunto la form viene visualizzata tramite il metodo Show o impostando la propriet visible della form a True. Come per il metodo Show esiste l'evento OnShow, cos per il metodo Hide esiste il metodo OnHide. Questo viene appunto attivato quando viene chiamato il metodo Hide della form oppure impostata a False la sua propriet Visible. L'evento OnCreate permette di effettuare operazioni speciali durante la creazione della form. Se vengono creati degli oggetti in questo evento, questi dovranno essere eliminati nell'evento OnDestroy della form. Evitare di lavorare su componenti inseriti nella form in questio evento poich potrebbero non essere accessibili e si avrebbe il sollevamento di una eccezione di "Access Violation". Se la propriet Visible della form impostato a True, si ha la seguente sequenza di eventi alla creazione della stessa: OnCreate, OnShow, OnActivate, OnPaint. L'evento Onclose viene attivato quando la form viene chiusa. Questo viene invocato immediatamente prima della chiusura della form. In questo evento disponibile un parametro che permette di definire l'azione da eseguire in chiusura della form. Valori validi sono: caNone, caHide, caFree, caMinimize. L'evento OnCloseQuery viene invocato prima dell'evento OnClose e permette di eseguire del codice per decidere se la form pu o no chiudersi. Potrebbe essere il caso di un editor di testo che controlla se il contenuto del file aperto stato modificato e visualizza una finestra di dialogo che chiede se salvare o no le modifiche. In questo evento presente un parametro che permette appunto di annullare la chiusura della form. Per fare ci occorre impostare il parametro CanClose a False. Da notare che il parametro viene passato all'evento con il valore True, percui se non si deve arrestare la chiusura della form non occorre assegnargli questo valore. L'evento OnActivate viene invocato quando la form riceve il fuoco. Una form riceve il fuoco quando l'utente vi fa click con il mouse. Nel caso di form di tipo MDIChild, l'evento OnActivate della form si genera solamente quando il fuoco cambia da una form child all'altra. L'evento OnResize permette di eseguire del codice speciale quando la form viene ridimensionata. Per l'esesattzza, l'evento viene attivato a conclusione del ridimensionamento della form. L'Evento OnResize una degli eventi comuni a molti controlli della VCL. Parallelamente all'evento OnResize, c' l'evento OnCanResize che permette di intercettare il ridimensionamento della form e di decidere se il ridimensionamento richiesto debba essere consolidato oppure annullato. Ci sono poi gli eventi attivati in risposta alle azioni generate dal mouse, come OnClick relativo a quando un utente fa click con il mouse sulla form, OnMouseMove, relativo al movimento del mouse sopra la form, OnDblClick che intercetta il doppio click con il mouse sopra la form, OnMouseDown ed OnMouseUp che vengono generati in risposta realtivamente alla pressione del tasto del mouse ed al suo rilascio. Come sono presenti eventi collegati alle azioni del mouse cos sono presenti eventi collegati alle azioni relative alla tastiera Quindi c' l'evento OnKeyPress, OnKeyDown ed OnKeyUp che rispettivamente si riferiscono alla pressione generica di un tasto sulla tastiera, alla pressione ed al rilascio di un tasto. Ovviamente quelli trattati in questa sezione del corso sono solo una parte delle propriet, dei metodi e degli eventi definiti dalla classe TForm e per una trattazione pi dettagliata si rimanda ai manuali sia in linea che cartacei del linguaggio.
LEZIONE 30:

Componenti standard

In questa sezione daremo un'occhiata agli oggetti messi a disposizione da Delphi cos come viene fornito. Come gi precisato all'inizio del corso, si fa riferimento alla versione 5 di Delphi Professional. Con molta probailit, essendo questa una versione intermedia tra la Standard e la Enterprise, negli oggetti presentati in questa sezione potrebbero essercene alcuni in pi od in meno se si usa una versione differente. Per la maggior parte, i componenti messici a disposizione da Delphi non sono altro che dei wrapper dei Common Controls forniti con le varie versioni di Windows. Come visto nella presentazione dell'ambiente di sviluppo, i componenti sono organizzati nella Component Palette in pagine. Nell'installazione tipica abbiamo 15 pagine che raccolgono i componenti in base a categorie. Ecco un elenco delle pagine standard nella Component Palette: Standard, Additional, Win32, System, Data Access, Data Control, Interbase, Internet, Fastnet, QReport, Dialogs, Win3.1, Samples, ActiveX, Servers. Ecco come appaiono i componenti nelle rispettive pagine

Standard

Fig. 1
Additional

Fig. 2
Win32

Fig. 3
System

Fig. 4
Data Access

Fig. 5
Data Control

Fig. 6
Interbase

Fig. 7
Internet

Fig. 8
FastNet

Fig. 9
QReport

Fig. 10

Dialogs

Fig. 11
Win 3.1

Fig. 12
Samples

Fig. 13
ActiveX

Fig. 14
Servers

Fig. 15 Come potete vedere c' praticamente quasi tutto per cominciare a realizzare delle applicazioni complete. Vi sono componenti per l'accesso ai databases, per l'accesso ai dati nei databases, per l'inserimento di testo, componenti a scelta multipla a tendina (ComboBox), liste, liste ad albero, liste di elementi organizzabili in diversi modi (ad elenco, ad icone grandi, con dettagli, simile alla sezione destra di esplora risorse), componenti per la generazione di report da databases, etc. Molti di questi componenti, soprattutto quelli visibili, sono identici ai componenti solitamente visibili nelle applicazioni Windows. Questo proprio perch derivano da classi che inglobano i common controls di windows. Non possibile in questa sede trattare questi componenti uno ad uno in maniera completa ma cercher di illustrare gli aspetti pi interessanti dei componenti pi utilizzati.

La pagina standard
Nella pagina standard sono contenuti la maggior parte dei componenti da pi tempo inseriti in Windows. Abbiamo le caselle di testo (TEdit), le etichette (TLabel), i menu (TMainMenu), i menu popup (TPopupMenu), caselle di testo su pi righe (TMemo), i bottoni (TButton), i le caselle di spunta (TCheckBox), i radio bottoni (TRadioButton), le listbox (TListBox), le caselle di testo combinate (TComboBox), pannelli (TPanel), i pannelli di raggruppamento (TGroupBox). Insieme a questi componenti derivanti direttamente dall'ambiente Windows, ce ne sono due che invece sono implementati direttamente da Delphi: i frames (TFrame) e la lista delle azioni (TActionList). Il primo di quest'ultimi due, introdotto a partire dalla versione 5 di Delphi, permette, in un certo qual modo, di poter creare visualmente componenti composti. Caratteristica molto interessante che permette di ridurre notevolmente i tempi di sviluppo considerando che per realizzare un componente composto bisogna, come per tutti i componenti, lavorare solamante a livello di codice, con tutti i problemi di implementazione e debug che ne derivano. Un frame si presenta sottoforma di una form con la differenza che l'area visualizzata a run time sar solamente l'area client della form a design time. Un frame non ha una barra del titolo. La lista della azioni invece un sistema che permette di centralizzare il codice che viene richiamato da pi componenti. Si pu ad esempio definire una azione "Apri" il cui codice associato viene eseguito dalla voce di menu apri, dal bottone rapido presente nella toolbar e da qualsiasi altro controllo che lo richiede. Altra possibilit offerta dalle azioni quella di poter gestire lo stato di tutti i controlli associati a quell'azione. Riferendoci all'esempio precedente, se per qualsiasi logica implementativa del programma l'azione "Apri" fosse momentaneamente non disponibile, non dovremo andare a modificare lo stato dei controlli associati a quell'azione uno ad uno, ma sar sufficiente impostare la propriet giusta dell'oggetto azione ed automaticamente tutti i controlli verranno aggiornati. Le propriet pi importanti di una azione (TAction) sono: Caption, Checked, Enabled, Hint, HelpContext, Visible, ImageIndex, ShortCut. Queste sono le stesse propriet della maggior parte dei controlli che possono essere collegati alle azioni. Perci, tornando al nostro esempio, per disattivare tutti i controlli collegati all'azione "Apri", sar sufficiente impostare la propriet Enabled dell'azione a False.

Creare un menu in Delphi veramente semplice. Tramite il componente TMainMenu, sufficiente, dopo aver inserito il componente nella form in cui si vuole creare il menu, agire tramite l'editor di propriet del componente stesso ( possibile visualizzarlo facendo doppio click con il mouse sul componente MainMenu inserito nella form) inserendo le voci di menu che interessano, inserire separatori tra una voce e l'altra, assegnare delle azioni alle varie voci di menu, creare sottomenu, assegnare del codice da eseguire a ciascuna voce. LEZIONE 31:

Applicazioni MDI (parte prima)

Le interfacce grafiche utente realizzabili in Windows sono fondamentalmente raggruppabili in due tipologie: SDI e MDI. Le interfacce SDI (Single Document Interface) sono tutte quelle interfacce, come quelle viste fino ad ora negli esempi di questo corso, che sono costituite da una sola form e che visualizzano a richiesta le altre forms come fossero "slegate" dall'applicazione stessa. Un esempio di applicazione SDI pu essere il Notepad di Windows che permette l'apertura di un solo documento all'interno della stessa istanza dell'applicazione. Le applicazioni con interfaccia MDI (Multiple Document Interface) sono quelle applicazioni costituite da una finestra principale definita "cornice" e che visualizzano le finestre secondarie all'interno di questa cornice. Un esempio di applicazione MDI il famoso Microsoft Word, che permette l'apertura di pi documenti all'interno della stessa istanza del programma. A livello puramente tecnico-implementativo, la struttura MDI fornisce automaticamente al programmatore servizi e funzioni che agevolano la gestione della struttura stessa. Alcune di queste funzioni sono la possibilit di affiancare sia in orizzontale che verticale, le finestre "figlie", di gestire automaticamente un elenco delle finestre figlie. Anche la gestione dei menu, il merge, viene gestito in maniera automatica. Tutto questo, se realizzato direttamente con chiamate dirette alle API di Windows comporta un discreto lavoro; in Delphi non cos, tutto pi semplice. In questa sezione realizzeremo un semplicissimo editor di testo clone di notepad ma strutturato come MDI. Vedremo anche l'impiego di alcuni componenti che non abbiamo visto fino ad ora. Per prima cosa creiamo una nuova applicazione scegliendo il comando "New Application" dal menu "File". Salviamo il progetto assegnando alla form principale il nome "FMain.pas" e al progetto il nome "NotePadX.prj". Cominciamo con il modificare la propriet FormStyle della form principale a fsMDIForm. Cos facendo, diciamo a Delphi che questa sar la finestra frame che conterr le altre finestre dell'applicazione. Diamo un nome alla form assegnando alla propriet Name della stessa il valore "Main". Inseriamo un componente TActionList nella form scegliendolo come solito dalla Component Palette nella pagina Standard. Definiamo quindi le azioni da assegnare al menu della finestra richiamando l'editor di propriet del componente TActionList facendo doppio click su di esso. Aggiungiamo quattro azioni con i seguenti nomi: actNew, actSave, actSaveAs, actOpen. Assegnamo alla propriet Category di ognuna di esse il valore Commands per raggrupparle all'interno della categoria comandi. Modifichiamo per ognuna di esse la propriet Caption assegnadogli rispettivamente i seguenti valori: "&New", "&Save", "Save &As...", "&Open...". Ovviamente, non dovranno essere digitate le virgolette. Definiamo anche delle combinazioni di tasti scorciatoia a alla propriet ShortCut di ciascuna azione. Per actNew inseriamo "Ctrl+N", per actOpen "Ctrl+O", per actSave "Ctrl+S", sempre senza virgolette. Ora aggiungiamo un componente menu. Tramite l'editor di menu attivabile facendo doppio click sul componente menu nella form, inseriamo un menu "File" . Per permettere all'utente di accedere ai menu attraverso la tastiera, nel definire il nome dei menu nella propriet Caption del menu, inseriamo il carattere "&" prima della lettera che vogliamo utilizzare come scorciatoia per accedere al menu. Nel nostro caso scriveremo "&File". Attenzione, all'utilizzo di questa funzionalit poich non possono esistere due voci di menu con lo stesso carattere scorciatoia nella stessa sezione di menu. Assegnamo alla form principale il colore di sfondo clAppWorkSpace agendo sulla propriet Color della stessa. Selezionando la voce di menu vuota che compare sotto la voce di menu "File", cominciamo a comporre la struttura del nostro menu. Assegnamo alla nuova voce, tramite la propriet Action, l'azione actNew che abbiamo definito in precedenza. Cos facendo, vedremo che automaticamente verr creata una voce di menu con la caption impostata nell'azione actNew ed una nuova voce. proseguendo con lo stesso metodo, aggiungiamo le altre azioni nel seguente ordine: actOpen, actSave, actSaveAs. A questo punto non rimane che aggiungere una voce di menu per chiudere l'applicazione. prima per, aggiungiamo una linea per separare le altre voci da quest'ultima. Per fare ci digitiamo il carattere "-" (meno) nella propriet Caption della voce di menu vuota. Vedremo comparire una linea orizzontale che fa da separatore. Semplice no! Ora non rimane che aggiungere l'ultima voce assegnando alla caption del menu item vuoto, la caption "&Exit" e la scorciatoia "Alt+F4" nella propriet ShortCut. Facendo doppio click sulla voce di

menu "Exit" appena creata assegneremo ad essa il gestore di evento OnClick che dovr gestire la chiusura dell'applicazione. Ecco il codice da inserire procedure TMain.Exit1Click(Sender: TObject); begin Close; end; La nostra applicazione pu gi essere eseguita per vedere come si presenta. Notiamo che nel menu "File", le voci sono tutte disabilitate all'infuori della voce "Exit". Questo perch non ancora stato assegnato del codice alle azioni. Per chiudere la nostra applicazione scegliamo il comando "Exit" dal menu "File" oppure premiamo i tasti "Alt" e "F4", oppure clicchiamo sul pulsante con la "X" nella bara del titolo. Ora aggiungiamo alla nostra applicazione una nuova finestra tramite la voce "New Form" dal menu file oppure tramite il corrispondente tasto nella toolbar. Assegnamo alla sua propriet FormStyle il valore fsMDIChild, alla propriet Name il valore "EditorWin" e salviamola con il nome "FEditorWin.pas". Se ora riavviamo la nostra applicazione, ci accorgiamo che la nuova finestra inserita verr visualizzata subito all'avvio dell'applicazione. Questo non il comportamento che noi vogliamo. Per cui, chiudiamo l'applicazione e modifichiamo questo comportamento agendo sulle propriet del progetto accedendovi tramite il comando "Options" nel menu "Porject". Nella pagina Forms della finestra che compare, passiamo da sinistra a destra la form EditorWin. In questo modo, per aprire la finestra, sar necessario crearla esplicitamente tramite codice. Costruiamo la struttura sia grafica che funzionale della nuova form. Aggiungiamoci un componente Memo che conterr il testo dei documenti ed impostiamo la sua propriet Align al valore alClient. Cos facendo il componente Memo andr automaticamente ad occupare l'intera area client della finestra e si ridimensioner assieme ad essa. Eliminiamo dalla propriet Lines dell'oggetto Memo la stringa che riporta il suo nome. Assegnamo alla sua propriet Name il valore Doc. Quindi il nostro componente Memo ora si chiamer Doc. Manca ancora un componente da aggiungere alla nostra finestra secondaria: un componente SaveDialog per acquisire il nome del documento da salvare quando si utilizza la funzione "Save As" oppure si salva un nuovo documento. Una volta aggiunto alla form cmbiamogli il nome in SaveDialog agendo sulla sua propriet Name. Ora passiamo alla stesura del codice che far funzionare la nostra form. Ecco cosa dobbiamo aggiungere nella definizione della classe TEditorWin della nostra form

type TEditorWin = class(TForm) Doc: TMemo; SaveDialog: TSaveDialog; procedure FormShow(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); private { Private declarations } FDocCount: Integer; FNewDoc : Boolean; FFileName : TFileName; function GetModified: Boolean; Function OpenFile(FileName : TFileName) : Boolean; Function SaveFile(FileName : TFileName) : boolean; Function SaveFileAs(FileName : TFileName) : Boolean; Procedure ResetModified; public { Public declarations } Constructor Create(AOwner : TComponent; Count : Integer; NewDoc : Boolean; FileName : TFileName); Function Function Property Property SaveDoc : Boolean; SaveDocAs : Boolean; DocCount : Integer read FDocCount; Modified : Boolean read GetModified;

Property FileName : TFileName read FFileName; end; Le parti evidenziate in rosso non vanno esplicitamente scritte, ma sono create automaticamente da Delphi. Doc e SaveDialog, sono i due componenti che abbiamo aggiunto alla form. Il resto rappresentano i gestori di eventi della form. Selezionando la form EditorWin, e scegliendo il suo nome all'interno della tendina dell'Object Inspector, nella pagina Events dello stesso Object Inspector, andiamo a definire il gestore per l'evento OnShow facendo doppi click sulla sua casella. Dovremo digitare il codice seguente

procedure TEditorWin.FormShow(Sender: TObject); begin If FNewDoc then Begin Caption := 'New document' + IntToStr(FDocCount); FFileName := Caption + '.txt'; End Else Begin If Not OpenFile(FFileName) then Begin MessageDlg('Error opening "' + FFileName + '"!', mtError, [mbOk], 0); Close; End Else Caption := FFileName; End; end; Seguendo lo stesso metodo digitiamo il seguente codice per il gestore di evento OnClose

procedure TEditorWin.FormClose(Sender: TObject; var Action: TCloseAction); begin Action := caFree; end; Alla chiusura della finestra necessario rimuoverla dalla memoria e per ridurre il quantitativo di memoria richiesta e perch, una finestra MDIChild quando viene chiusa non viene nascosta coma una normale form, ma viene ridotta. Per il gestore OnCloseQuery abbiamo

procedure TEditorWin.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin If Modified then Case MessageDlg('Save changes to "' + FFileName + '"?', mtConfirmation, [mbYes, mbNo, mbCancel], 0) of mrYes : If FNewDoc then Begin

If SaveFileAs(FFileName) then CanClose := True Else CanClose := False; End Else Begin If SaveFile(FFileName) then CanClose := True Else CanClose := False; End; mrNo : CanClose := True; mrCancel : CanClose := False; End; end; Le parti evidenziate in verde vanno invece digitate sia nella dichiarazione della classe sia per esteso nella sezione implementation. Ecco il codice

constructor TEditorWin.Create(AOwner: TComponent; Count : Integer; NewDoc: Boolean; FileName: TFileName); begin Inherited Create(AOwner); FDocCount := Count; FNewDoc := NewDoc; FFileName := FileName; end; function TEditorWin.GetModified: Boolean; begin Result := Doc.Modified; end; function TEditorWin.OpenFile(FileName: TFileName): Boolean; begin Try Doc.Lines.LoadFromFile(FileName); Result := True; Except Result := False; End; end; function TEditorWin.SaveFile(FileName: TFileName): boolean; begin Try Doc.Lines.SaveToFile(FileName); FNewDoc := False; ResetModified; Result := True; Except Result := False; End; end; function TEditorWin.SaveFileAs(FileName: TFileName): Boolean; begin

SaveDialog.FileName := FileName; If SaveDialog.Execute then Begin Result := SaveFile(SaveDialog.FileName); If Result then Begin FNewDoc := False; ResetModified; FFileName := SaveDialog.FileName; Caption := FFileName; End; End Else Result := False; end; procedure TEditorWin.ResetModified; begin Doc.Modified := False; end; function TEditorWin.SaveDoc: Boolean; begin If FNewDoc then SaveFileAs(FFileName) Else SaveFile(FFileName); end; function TEditorWin.SaveDocAs: Boolean; begin SaveFileAs(FFileName); end; Con questo codice abbiamo completato la nostra form. Nella prossima lezione ne spiegheremo il funzionamento. In particolare ci soffermeremo sulle funzioni che gestiscono l'apertura ed il salvataggio dei documenti. LEZIONE 32:

Applicazioni MDI (parte seconda)

La procedura OpenFile si occupa di caricare il contenuto del file il cui nome gli viene passato come parametro. Il caricamento avviene sfruttando il metodo LoadFromFile della propriet Lines (che di tipo TStrings) dell'oggetto Doc (TMemo). Anche la procedura SaveFile utilizza un metodo del componente Doc. In questo caso si tratta del metodo SaveToFile della propriet Lines. La procedura SaveFileAs utilizza prima di chiamare la procedura SaveFile una chiamata al metodo Execute dell'oggetto SaveDialog che permette, tramite la finestra di dialogo standard di windows, di selezionare il nome del file da assegnare al documento. Se il salvataggio avviene con successo, viene anche aggiornato il titolo della finestra contenente il documento con il nuovo nome di file. In questa form, abbiamo anche un esempio di ridefinizione del costruttore della form. La propriet Modified definita in questa form, ci servir per controllare lo stato di abilitazione delle azioni nella form principale. Il valore di questa propriet viene restituito dal metodo GetModified che controlla se la propriet dell'oggetto Doc (TMemo) True o False. La classe TMemo infatti, sa riconoscere da sola quando il suo contenuto stato modificato. Ecco invece come sono state impostate le propriet dell'oggetto SaveDialog

DefaultExt = '.txt' Filter = 'Text files (*.txt)|*.txt|INI files (*.INI)|*.INI|

NFO files (*.NFO)|*.NFO|Any file (*.*)|*.*' Title = 'Save as' Per impostare la propriet filter disponibile un editor di propriet che si attiva cliccando sul tastino con i tre puntini vicino alla casella del valore della propriet stessa. I filtri, sia nel componente OpenDialog che SaveDialog, servono a filtrare appunto i files in base alla loro estensione. La propriet DefualtExt definisce invece l'estensione di default che verr aggiunta al nome del quando questo verr salvato senza specificarne l'estensione. Passiamo ora a completare la form principale. Aggiungiamo prima altre azioni prendendole questa volta tra quelle predefinite che ci mette a disposizione Delphi. Sempre dall'editor di propriet dell'Oggetto ActionList, scegliamo "New standard action" dal menu a tendina che viene visualizzato premendo il tasto con la freccia verso il basso posto vicino al tasto "New action". Selezioniamo tutte le azioni che fanno parte della categoria "Window" selezionando la prima e tenendo premuto shift selezionando l'ultima. In questo modo avremo creato delle azioni che serviranno, senza scrivere nemmeno una riga di codice a gestire la funzioni relative alla finestre figlie della nostra applicazione. Aggiungiamo ancora un componente, il dialogo di apertura file (TOpenDialog) chiamandolo OpenDialog e settando le propriet seguenti

DefaultExt = '.txt' Filter = 'Text files (*.txt)|*.txt|INI files (*.INI)|*.INI| NFO files (*.NFO)|*.NFO|Any file (*.*)|*.*' Title = 'Open' Come per la form EditorWin, vediamo quale il codice da inserire nella definizione della classe TMain

type TMain = class(TForm) MainMenu1: TMainMenu; File1: TMenuItem; ActionList1: TActionList; actOpen: TAction; actSave: TAction; actSaveAs: TAction; actNew: TAction; New1: TMenuItem; Save1: TMenuItem; SaveAs1: TMenuItem; N1: TMenuItem; Exit1: TMenuItem; OpenDialog: TOpenDialog; Open1: TMenuItem; Windows1: TMenuItem; WindowArrange1: TWindowArrange; WindowCascade1: TWindowCascade; WindowClose1: TWindowClose; WindowMinimizeAll1: TWindowMinimizeAll; WindowTileHorizontal1: TWindowTileHorizontal; WindowTileVertical1: TWindowTileVertical; Arrange1: TMenuItem; Cascade1: TMenuItem; MinimizeAll1: TMenuItem; TileHorizontally1: TMenuItem; TileVertically1: TMenuItem; N2: TMenuItem; Close1: TMenuItem;

procedure CheckSaveEnabledState(Sender: TObject); procedure Exit1Click(Sender: TObject); procedure actNewExecute(Sender: TObject); procedure actOpenExecute(Sender: TObject); procedure FormCreate(Sender: TObject); procedure actSaveExecute(Sender: TObject); procedure actSaveAsExecute(Sender: TObject); private { Private declarations } NewDocCount : Integer; Function CheckModified : Boolean; Function NewDocument : Boolean; Function OpenDocument(FileName : TFileName) : Boolean; public { Public declarations } end; Anche qui il codice in rosso quello gestito automaticamente da Delphi e quello in verde quello che invece dobbiamo inserire noi. Ecco di seguito il codice da inserire

function TMain.CheckModified: Boolean; begin Result := False; If Assigned(ActiveMDIChild) then Result := TEditorWin(ActiveMDIChild).Modified; end; procedure TMain.CheckSaveEnabledState(Sender: TObject); begin TAction(Sender).Enabled := CheckModified; end; procedure TMain.Exit1Click(Sender: TObject); begin Close; end; procedure TMain.actNewExecute(Sender: TObject); begin If Not NewDocument then MessageDlg('Error creating new document!', mtError, [mbOk], 0); end; function TMain.NewDocument: Boolean; Var NewForm : TEditorWin; begin Try Inc(NewDocCount); NewForm := TEditorWin.Create(Self, NewDocCount, True, ''); NewForm.Show; Result := True; Except Result := False; End; end;

procedure TMain.actOpenExecute(Sender: TObject); Begin If OpenDialog.Execute then OpenDocument(OpenDialog.FileName); end; function TMain.OpenDocument(FileName : TFileName): Boolean; Var OpenForm : TEditorWin; begin Try OpenForm := TEditorWin.Create(Self, 0, False, FileName); OpenForm.Show; Result := True; Except Result := False; End; end; Procedure TMain.FormCreate(Sender: TObject); begin Caption := Application.Title; end; procedure TMain.actSaveExecute(Sender: TObject); begin If Assigned(ActiveMDIChild) then TEditorWin(ActiveMDIChild).SaveDoc; end; procedure TMain.actSaveAsExecute(Sender: TObject); begin If Assigned(ActiveMDIChild) then TEditorWin(ActiveMDIChild).SaveDocAs; end; Il metodo privato CheckModified si occupa di controllare se la finestra EditorWin correntemente attivata abbia o no delle pendenze riguardo a modifiche apportate al contenuto del documento. Tramite la propriet ActiveMDIChild della form frame, si ottiene un riferimento di tipo TForm alla form MDIChild attiva, ovvero la finestra che possiede il fuoco. Poich la nostra finestra child una classe di tipo TEditorWin, per accedere alla sua propriet Modified, non presente nella classe TForm, occorre fare un type cast, una conversione di tipo a TEditorWin. Lo stesso metodo viene utilizzato anche negli altri metodi che ottengono il riferimento tramite ActiveMDIChild. Nella funzione NewDocument che si occupare di creare un nuovo documento vuoto, abbiamo utilizzato un contatore per distinguere i diversi documenti creati nell'ambito della stessa sessione di lavoro. Questo viene passato al costruttore della form EditorWin che lo utilizzer per aggiornare il titolo della finestra stessa impostandone la propriet Caption. Questo abbozzo di editor pu essere ampliato aggiungendo toolbars, statusbar, la stampa e cos via. Potrebbe essere un buon esercizio completarlo con quello che manca. I file del progetto, per una consultazione pi accurata sono disponibili qui.

Un approfondimento sulle finestre MDI. Abbimo visto che per utilizzare questo tipo di struttura in Delphi molto semplice, sufficiente impostare per la finestra frame il valore della propriet FormStyle a fsMDIForm e per le finestre figlie a fsMDIChild. Tecnicamente, la prima finestra, quella che contiene le altre, formata da due finestre sovrapposte. La prima il frame vero e proprio, la tipica form di Delphi, l'altra una finestra che non ha bordo ne titolo che ricopre l'intera area client della finestra frame. Tutte le finestre figlie che vengono create, sono figlie della finestra senza bordi, chiamata Client. Per cambiare il colore dello fondo della finestra Client, ovvero della area client della nostra finestra frame, sufficiente, in Delphi, impostare la propriet Color della form Frame, come

visibile nei sorgenti dell'esempio realizzato. In definitiva possiamo riassumere cos la gerarchia delle finestre MDI Finestra Frame -> Finestra Client -> Finestre Figlie Ecco alcune propriet e metodi della classe TForm che non sono stati utilizzati nel nostro esempio ma che meritano ci essere mensionati. La classe TForm definisce due metodi che permettono di selezionare le finestre figlie attive relativamente alla finestra attiva. Per la precisione, il metodo Next attiva la finestra figlia successiva, nella lista interna delle finestre figlie, a quella correntemente attiva. Al contrario, il metodo Previus permette di selezionare la finestra precedente a quella correntemente attiva. La propriet ClientHandle contiene il riferimento (Window Handle) alla finestra Client di cui abbiamo parlato sopra. Le propriet MDIChildCount e MDIChildren permettono di operare sulle finestre figlie. In paricolare, MDIChildCount restituisce il numero corrente delle finestre figlie e MDIChildren un array di riferimenti alle finestre figlie. Queste due propriet utilizzate in combinazione, permettono di scorrere ed operare sulle finestre figlie correnti. Un'altra caratteristica molto interessante, che non riguarda solamente le applicazioni MDI, la possibilit di gestire l'automerge dei menu delle varie finestre. Avendo una finestra principale con un menu, possibile far disporre automaticamente il menu di una finestra secondaria definendo un ordinamento per i singoli menu. Per la trattazione di questo argomento si ramanda alla documentazione del linguaggio oppure alla ottima guida in linea dell'IDE. Una cosa da tenere presente durante la realizzazione di una applicazione MDI che, per il funzionamento visto precedentemente, per applicare una immagine di sfondo alla finestra principale dell'applicazione (finestra frame), non sufficiente disegnare l'immagine sulla form poich questa viene coperta dalla finestra Client. Per ovviare a questo problema occorre ricorre a una pratica, diciamo di basso livello. Chi programma in ambiente Windows sa che ogni finestra legata ad una Window Procedure. Che cos' una window procedure? Una window Procedure non altro che la procedura che consente alla finestra di gestire i messaggi a lei inviati: ridimensionamento, ridisegno, posizionamento, etc. Questa pratica prevede di sostituire questa Window Procedure per alterare il comportamento standard della finestra. Quello che dobbiamo fare intercettare il messaggio WM_ERASEBKGND inviato alla nostra finestra quando necessario il ridisegno dello sfondo. Per fare ci, occorre salvare il riferimento alla vecchia Window Procedure e sostituire la procedura originale con la nostra che sar in grado di gestire disegno dell'immagine nella finestra Client. Il procedimento completo rintracciabile sul libro di Marco Cant "Programmare con Delphi 5". LEZIONE 33:

Sviluppo di applicazioni Multithread

I sistemi operativi moderni moderni permettono l'esecuzione contemporanea (quasi!!) di pi applicazioni e per questo vengono detti Multitask. Una applicazione pu quindi essere eseguita contemporaneamente ad un'altra (per esempio quando navighiamo su internet e contemporaneamente controlliamo la posta, oppure ricerchiamo un file sul disco). Durante la sua esecuzione, una applicazione deve bloccare completamente l'esecuzione quando aspetta che terminino processi lenti come l'accesso ai dischi o la comunicazione con altri computer. Molte volte le applicazioni possono essere organizzate in maniera tale da ovviare a questi colli di bottiglia o in maniera da suddividere la loro esecuzione in pi processi, magari ripartiti su pi processori in sistemi multiprocessore. Ecco che in alcuni dei moderni sistemi operativi ci vengono in contro i Thread. In questo caso avremo a che fare con sistemi operativi multithread. La differenza tra questi due tipi di sistemi operativi sta nel fatto che nel primo tipo, l'unit elementare di esecuzione il task (processo), mentre nel secondo il thread. Nell'ultimo tipo di sistema operativo, possiamo avere dei tasks consistenti in pi trhead. La differenza fondamentale tra task e trhead sta nel fatto che i primi non possono condividere lo stesso spazio di indirizzamento mentre invece i secondi, ovviamente se appartenenti alle stesso task (o processo), si. La creazione di un nuovo thread non richiede l'impiego di grandi risorse di sistema. Normalmente in una applicazione si ha un thread definito principale, ed quello che gestisce i messaggi di Windows e l'esecuzione del codice principale e nessuno, uno o pi trhead secondari. La definizione, la creazione e la gestione di thread non proprio semplice ed intuitiva. Delphi per, come sempre, ci viene incontro semplificando notevolmente le cose mettendoci a disposizione la classe TThread.

L'uso di questa classe molto semplice. Questa contiene alcune propriet e metodi tra cui il principale il metodo Execute. Questo il metodo il cui codice viene eseguito all'interno del thread. Una volta definita una nuova classe ereditata da TThread, per la creazione della classe necessario effettuare una chiamata al metodo Create della classe la quale ha il seguente struttura

Create(Suspended : Boolean); Il parametro Suspended da passare al metodo, permette di definire il comportamento del thread in seguito alla sua creazione. Impostando questo parametro a False (valore predefinito) il thread viene attivato immediatamente dopo essere stato creato. Se il parametro Suspended ha valore True, il thread viene creato e immediatamente sospeso, ovvero la sua esecuzione non ha inizio fino ad una chiamata al metodo Resume della classe TThread. Per sospendere di nuovo il thread dopo il suo avvio, disponibile il metodo Suspend. Esistono altri metodi che permettono di controllare l'esecuzione del thread come Terminate che decreta l'arresto definitivo del thread. I metodi descritti precedentemente sono affiancati da alcune propriet che permettono di acquisire informazioni riguardo allo stato di esecuzione del thread. La propriet Suspended ci informa sul fatto che il thread stato sospeso; la propriet Terminated ci indica che il thread stato terminato; Priority permette di impostare la priorit di esecuzione del thread su 7 livelli differenti di priorit da un minimo di tpIdle ad un massimo di tpTimeCritical; FreeOnTerminate che permette di specificare se l'oggetto thread creato deve essere distrutto al termine dell'esecuzione del codice del thread. Vediamo ora come va implementato il codice da eseguire all'interno del thread vero e proprio. Quando si definisce una classe erede di TThread, occorre anche riscrivere il metodo Execute (che nella classe TThread Virtual e Abstract, ovvero non ha implementazione) facendo l'ovverride del metodo ereditato. Il codice che andremo a scrivere all'interno del metodo Execute, sar il codice che verr eseguito all'interno del thread. Ecco come si presenta la struttura del metodo Execute

Type NomeClasse = class(TThread) private { Private declarations } protected { Public declarations } Procedure Execute; override; end; Implementation Procedure NomeClasse.Execute; Begin //Inserire qui il codice da eseguire nel thread End; Da notare che il codice del metodo Execute, viene eseguito una volta sola quindi raggiunta l'ultima istruzione del metodo Execute, l'esecuzione del thread si arresta. Per eseguire pi volte lo stesso codice all'interno del thread, occorre ricorrere ad un ciclo e, cosa importante, ad ogni esecuzione del ciclo controllare il valore della propriet Terminated del thread per interrompere il ciclo quando l'esecuzione del thread viene interrotta. Una tipica implementazione del metodo Execute la seguente

Procedure NomeClasse.Execute; Begin While not Terminated do Begin //Inserire qui il codice da eseguire nel thread End;

End; Una importante considerazione da fare riguardo al metodo Execute, sta nel fatto che non si pu eseguire codice che fa riferimento ad oggetti della VCL. Questo perch gli oggetti della VCL vengono eseguiti in un thread differente da quello in esecuzione all'interno del metodo Execute e quindi si potrebbero avere problemi di accesso alle loro propriet ed ai lor metodi. Per evitare ci, la classe TThread mette a disposizione il metodo Synchronize che permette appunto di sincronizzare i due threads in maniera tale da paermettere l'accesso sicuro alle propriet ed ai metodi dell'oggetto voluto. Esistono anche oggetti della VCL chiamati Thread-Safe che sono stati realizzazti appositamente per l'utilizzo in ambienti multithread e che permettono di gestire gli accessi al loro contenuto in maniera concorrente. Un esempio di questi oggetti la ThreadList. Al termine dell'esecuzione del codice thread, possibile eseguire del codice di pulizia scrivendo un gestore di evento per l'unico evento che mette a disposizione la classe TThread, l'evento OnTerminate. La classe TThread permette anche di sincronizzare l'esecuzione del suo thread con altri thread. Tramite il metodo WaitFor della classe TThread possibile attendere il termine dell'esecuzione di un'altro thread. Questo metodo non restituisce il controllo finch l'altro thread non terminato. Potrebbe capitare la necessit di intercettare quando un thread ha terminato una certa operazione piuttosto che attendere che l'intero thread completi la sua esecuzione. In questo caso esistono gli oggetti evento (TEvent). Quando un thread deve comunicare ad altri di aver completato la un certa operazione, chiama il metodo SetEvent dell oggetto evento. Per disattivare la segnalazione dell'evento sufficiente effettuare una chiamata al metodo ResetEvent dell'evento. Gli oggetti evento devono essere creati in un campo d'azione globale per far si che tutti i thread interessati possano accedervi. Il thread che necessita di attendere il completamento di una determinata operazione, crea ed avvia i thread interessati ed effettua una chiamata la metodo WaitFor dell'oggetto evento indicando un intervallo di tempo da attendere per la ricezione del segnale. Il tempo da attendere per il settaggio del segnale espresso in millisecondi e pu essere impostato anche su INFINITE. Al ritorno dalla sua chiamata, il metodo WaitFor restituisce uno dei seguenti valori: wrSignaled, wrTimeout, wrAbandoned, wrError. L'oggetto evento, o meglio la classe TEvent, non altro che una struttura wrapper per gli oggetti event di Windows. Altre strutture permettono di semplificare la gestione di multithread. Per esempio, quando gli oggetti su cui si sta operando non possiedono metodi o propriet specifiche per operare in ambineti multithread, si pu ricorrere alle sezioni critiche. Le sezioni critiche permettono di far accedere ai metodi o alle propriet di un determinato oggetto un solo thread alla volta, onde evitare conflitti di accesso. Questa struttura definita nella classe TCriticalSection che possiede due metodi: Acquire, Release. Quando un thread deve accedere alla sezione critica, deve prima chiamare il metodo Acquire per bloccare l'accesso agli altri threads e rilasciare tale blocco tramite il metodo Release al termine dell'operazione.

LEZIONE 34:

Liste ed oggetti contenitore

Delphi mette a disposizione dello sviluppatore alcune classi che permettono di semplificare la gestione di insiemi di valori. La gestione di questi insiemi di valori avviene tramite delle classi lista. La pi generica, poich permette di gestire insemi di valori di tipo non omogeneo, la classe TList che definisce una lista di puntatori. Esiste una classe specizlizzata nella gestione di liste di stringhe, TStringList derivata da TStrings. Un'altro gruppo di classi per la gestione di insiemi riguarda le collezioni. Questo gruppo si compone fondamentalmente di due classi, TCollection e TCollectionItem. La classe TCollection permette di gestire una lista di oggetti dello stesso tipo, di cui la stessa lista proprietaria. Tutte queste liste mettono a disposizione dello sviluppatore una serie di metodi e propriet per manipolare il contenuto della lista stessa. Alcuni di questi metodi sono Insert, Add, Delete, Remove per l'inserimento, aggiunta e l'eliminazione degli elementi della lista. Sono disponibili alcuni metodi come IndexOf per la ricerca di elementi all'interno della lista. Sono disponibili anche propriet che permettono l'accesso agli elementi della lista in base ad un indice come se si stesse lavorando con un array. Per esempio, per accedere al secondo elemento di una lista di stringhe (TStringList) possibile utilizzare la propriet Strings[1]. Da notare l'indice 1 per identificare il secondo elemento della lista, poich l'indice della lista "zero based" cio il primo indice

(ovvero il primo elemento) 0. Quindi, se si vuole accedere all'ultimo elemento della lista, sfruttando anche un'altra propriet comune a tutte le classi lista viste, utilizzando la propriet Count che restituisce il numero di elementi contenuti nella lista, dovremo scrivere Strings[NomeClasseLista.Count - 1]. Le liste di stringhe hanno altres la possibilit di associare un riferimento ad oggetto per ciscuna stringa inserita nella lista attraverso il metodo AddObjects(Stringa, PuntatoreObj). Cos facendo verr inserita una stringa con il valore contenuto in Stringa e contemporaneamente le verr associato il riferimento PuntatoreObj. Parallelamente, per accedere ai valori della lista di oggetti di pu ricorrere alla propriet Objects che funziona come la propriet Strings gi vista. Quindi all'indice 0 corrisponder il riferimento oggetto relativo alla prima stringa e cos via. Con l'arrivo di Delphi 5 sono state introdotte alcuni nuovi tipi di liste con caratteristiche molto interessanti definite classi-contenitore. Fondamentalmente estendono le funzionalit di TList, aggiungendo il concetto di propriet degli oggetti contenuti e regole perticolari di estrazione ed inserimento. Una di queste nuove classi TObjectList che dal nome stesso una lista che accetta come elementi solamente oggetti derivati da TObject e non semplici puntatori. Caratteristica di questa classe la propriet OwnsObjects che se impostata a true produce la distruzione automatica dell'oggetto quando questo viene rimosso dalla lista. Esistono anche altre classi come TComponentList per la gestione di liste di componenti, TClassList per la gestione di liste di class reference. Di particolare interesse ritengo siano le classi TStack e TQueue. Dal nome di queste si intuisce gi il loro scopo e funzionamento. La classe TStack implementa una lista di puntatori in cui l'inserimento e l'estrazione degli elementi avviene in testa alla lista. Si tratta dell'implementazione di una struttura denominata pila in cui gli elementi vengono inseriti in cima alla pila e rimossi sempre dalla cima. Questa strategia di inserimento ed estrazione denominata LIFO (Last In, First, Out). Per aggiungere elementi alla lista si fa ricorso al metodo Push e per rimuovere elementi al metodo Pop. Si dispone anche del metodo Peek per leggere l'elemento correntemente in cima alla pila senza estrarlo da essa. Una variante della classe TStack la classe TObjectStack che implementa un struttura simile tranne per il fatto che gli elementi sono di tipo TObject. L'altra classse, TQueue implementa un'altra struttura molto conosciuta in informatica, ovvero la coda. Come per TStack, si tratta di una lista di puntatori la cui aggiunta segue la strategia FIFO (First In, First Out) ovvero il primo elemento inserito viene estratto per primo. Possiede gli stessi metodi della classe TStack. Come per la classe TStack esiste una variante per la gestione di liste di oggetti il cui nome della classe TObjectQueue. A differenza della TObjectList, le classi TObjectStack e TObjectQueue non hanno la possibilit di distruggere gli oggetti al momenti dell'estrazione dalla lista. LEZIONE 35:

BDE (Borland Database Engine)

Delphi, fin dalle prime versioni, mette a disposizione dello sviluppatore la possibilit di accedere a basi di dati (databases). Per far ci la Borland ha realizzato il BDE (Borland Database Engine); questo non altro che un insieme di funzioni contenute in alcune DLL distribuite con Delphi, attraverso le quali ci si interfaccia con vari tipi di Database. Attraverso il BDE Dlphi in grado di accedere direttamente ai seguenti tipi di database: DBase, Paradox, Access, FoxPro, ASCII. Attraverso gli SQL Links (forniti solamente con la versione Enterprise di Delphi) possibile anche accedere ad alcuni server SQL locali o remoti. Questi sono una serie di drivers sempre realizzati dalla Borland per permettere di accedere direttamente a server SQL come Oracle, SQL Server, Informix, SyBase, DB2. Il BDE permette altres di accedere a qualsiasi tipo di database purch siano presenti nel sistema i relativi drivers ODBC. In questo caso il BDE non acceder direttamente alle strutture del database ma gestir queste strutture attraverso il driver ODBC. Nelle ultime versioni di Delphi Enterprise, vengono forniti anche degli oggetti ad hoc per l'accesso a basi di dati tramite ADO, la tecnologia di Microsoft che tende a centralizzare la gestione di differenti sorgenti di dati. Non strettamente necessario possedere la versione Enterprise di Delphi per lavorare con ADO, anche con la versione Professional ci possibile purch si trovino dei componenti per l'accesso ad ADO, come ADOExpress disponibile nella versione Enterprise ed acquistabile separatamente per la versione Professional, oppure oggetti di terze parti, magari anche free (come TaoADODB), oppure implementando ci che necessari ricorrendo magari all'importazione delle librerie di tipi (Type Library) di ADO attraverso la funzione messa a disposizione dall'IDE di Delphi che esegue questa procedura automaticamente creando le unit necessarie. Ovviamente, in tutti i casi sopra trattati necessario che nel sistema che ospita la nostra applicazione sia presente o il BDE, ADO, o l'MDAC di Microsoft. Da notare che in base al tipo di accesso che

intendete utilizzare per la vostra base di dati, dovrete distribuire anche il BDE o altre librerie necessarie al raggiungimento dello scopo. Oltre a permettere l'accesso a tipi di database differenti, il BDE implementa alcune funzionalit come per esempio le transazioni. Una transazione un gruppo di azioni che devono essere tutte portate a termine con successo su una o pi tabelle di un database prima che vengano rese permanenti. Se una di queste azioni non viene completata con successo, tutte le altre azioni vengono annullate. Le transazioni sono utili per prevenire sia problemi derivanti da guasti hardware nel mezzo dell'esecuzione di un determinato comando, sia a gestire la simultaneit di accesso da parte degli utenti in ambienti multiutente. Normalmente il supporto per le transazioni non disponibile con database di tipo locale ma il BDE fornisce un supporto transazionale limitato per alcuni database di questo tipo. Dalla versione Professional Delphi viene fornito corredato dei componenti per l'accesso diretto all'RDBMS della Borland Interbase. Quest'ultimo viene anche fornito nel CD d'installazione di Delphi. Questo database manager viene ora fornito liberamente ed disponibile nella versione 6 sia per piattaforma Windows che Linux. LEZIONE 36:

Componenti per l'accesso ai databases

In Delphi l'accesso alle basi di dati reso molto semplice grazie ai componenti per l'accesso ai databases che si trovano nella pgina "Data Access" della component palette.

Si tratta di otto componenti non visibili a run-time e sono: TDataSource, TTable, TQuery, TStoredProc, TDatabase, TSession, TBatchMove, TUpdateSql. Per accedere ai dati necessaria una sorgente di dati. Questo non si riferisce direttamente ai dati ma ad un dataset che pu essere una tabella oppure una query, una stored procedure o altri tipi di dataset come per esempio quelli ADO che possono accedere anche a fogli di Excel ed altro. Se necessario, i dataset possono essere collegati ad un database e quest'ultimo ad una sessione.

TDataSource
TDataSource sostanzialmente una interfaccia tra un dataset ed un componente per la manipolazione dei dati (data-aware). Attraverso questo componente i controlli data-aware, possono visualizzare, modificare o navigare tra i dati presenti nel dataset. Se si vogliono manipolare i dati contenuti in un dataset attraverso i controlli data-aware, necessario che ciascun datasource venga ssociato ad un dataset. Allo stesso modo, ciascun controllo data-aware deve essere connesso ad un componente datasource. I componenti datasource sono altres utilizzati per collegare due dataset in relazioni di tipo master-detail. Le principali propriet del componente TDataSource sono: AutoEdit, Dataset, Enabled, State. Queste sono tutte accessibili a design-time tranne State, che read-only e accessibile solamente a run-time. La propriet AutoEdit, se impostata a true, permette di modificare automaticamente il contenuto di un campo del dataset associato al datasource, nel momento in cui viene modificato il valore visualizzato nel controllo data-aware associato al campo. La propriet Dataset permette di specificare il dataset a cui fa riferimento il datasource. A design-time, l'object inspector, presenta una lista dei dataset presenti nel modulo corrente e/o in tutti i moduli collegati al modulo corrente. La propriet Enabled, stabilisce lo stato dei controlli data-aware associati al datasource. Se impostata su false i controlli sono disattivati e non possibile modificare i loro valori. La propriet State riporta lo stato del dataset riferito dal componente datasource ed assume gli stessi valori della propriet State del componente TDataSet collegato. I metodi principali del componente TDataSource sono: Edit, IsLinkedTo. Il metodo Edit permette di attivare la modalit di edit sul dataset associato. Prima di attivare la modalit edit, il metodo effettua un controllo sulla propriet AutoEdit e State del componente Datasource.

Per il componente DataSource gli eventi disponibili sono tre: OnDataChange, OnStateChange, OnUpdateData. Il primo evento, viene attivato quando il record corrente viene modificato e l'applicazione si sposta da un campo ad un altro od a un altro record nel dataset associato. I controlli data-aware segnalano un cambiamento di "data change" nei seguenti casi: spostamento ad un nuovo record, modifica dei dati di un campo. L'evento OnStateChange viene attivato quando lo stato del dataset associato al componente DataSource cambia. Si ha un cambiamento di stato quando si passa per esempio dallo stato dsBrowse (consultazione dei dati) allo stato dsEdit (per la modifica dei dati). Per controllare lo stato in cui si trova il dataset sottostante il componente DataSource, controllare all'attivazione dell'evento OnStateChange il valore della propriet State del componente DataSource. L'ultimo evento viene attivato immediatamente prima che i dati vengano salvati nel dataset collegato.

TTable
Il componente Table incapsula una tabella appartenente ad un database. Il componente Table utilizzato per accedere ad una tabella di un database attraverso il BDE. Questo componente permette di accedere direttamente a ciascun record e campo della tabella associatagli. Attraverso questo componente + anche possibile accedere ad un sottoinsieme dei dati contenuti nella tabella sottostante, facendo uso dei filtri o degli intervalli. E' possibile accedere a tabelle di qualsiasi database supportato dal BDE. Le principali propriet del componente Table sono: Active, BOF, CanModify, DataSource, Database, DatabaseName, DefaultIndex, Exclusive, Exists, EOF, Filter, Filtered, IndexName, ReadOnly, MasterSource, TableName, TableType, SessionName, State. La propriet Active indica se la tabella aperta. Per apripre una tabella necessario, dopo aver assegnato le propriet DatabaseName, TableName, eseguire il metodo Open oppure impostare su true il valore della propriet Active. Questa propriet pu essere sia letta che scritta ed accessibile anche a design-time per attivare la tabella in fase di sviluppo. La propriet BOF (Begin Of File) indica quando il record corrente il primo presente nella tabella. Esattamente il contrario indica la propriet EOF (End Of File). La propriet CanModify indica se i dati contenuti nella tabella riferita possono essere modificati. Questa propriet viene impostata automaticamente quando la tabella viene aperta ed una propriet a sola lettura. Se CanModify false significa che la tabella in modalit read-only oppure altri fattori possono impedire la modifica dei dati, come per esempio un altro utente che sia connesso alla tabella in modalit esclusiva, oppure non si abbiano i privilegi (ad esempio in un server di database come SQL server) per accedere a quella tabella in modifica. La propriet DataSource serve ad indicare quale il datasource che si riferisce alla tabella Master quando si sta utilizzando una relazione di tipo Master-Detail dove la tabella corrente funge da dettaglio. DatabaseName indica il database a cui fa riferimento il dataset. Questa propriet indica il nome di un componente TDatabase, oppure il percorso in cui si trovano le tabelle database, oppure ancora il nome di un alias di database. DefaultIndex identifica l'indice che verr utilizzato per ordinare i record nella tabella quando questa verr aperta. La propriet Exclusive, modificabile solamente a tabella chiusa, permette di aprire la tabella in modalit esclusiva, ovvero qualsiasi altro utente che richiedesse l'accesso a quella stessa tabella sarebbe impedito dal farlo. Exists una propriet che permette di stabilire se esiste effettivamente la tabella fisica indicata nel componente TTable. Questa restituisce true se la tabella esiste, false altrimenti. Filter una propriet che permette di specificare un criterio in base al quale verra limitata la visibilit dei record presenti nella tabella. Accetta valori di tipo stringa del tipo: 'Campo1=valore1 or Campo2=valore2'. Possono anche essere utilizzati caratteri jolly come "*". Attraverso la propriet Filtered possibile attivare o disattivare il filtraggio dei record in base al filtro precedentemente definito nella propriet Filter. IndexName indica un indice secondario da utilizzare per definire l'ordinamento dei records della tabella. Se questo valore non viene impostato, i records saranno ordinati in base all'indice di default oppure, nel caso delle tabelle DBase, in base all'ordine naturale dei records, ovvero la sequenza con cui i record sono stati aggiunti nella tabella. La propriet ReadOnly, permette di definire l'apertura della tabella come di sola lettura, percui qualsiasi operazione di scrittura applicata a quella tabella restituir un messaggio di errore. Per impostare questa propriet necessario impostare la propriet Active su false (o chiudere la tabella), impostare il valore desiderato, quindi reimpostare Active a true (o riaprire la tabella). Impostando ReadOnly a true, automaticamente la propriet CanModify assumer valore False. MasterSource contiene un riferimento ad un oggetto DataSource collegato ad una tabella master. Questo valore va impostato solamente nel caso in cui si lavori con tabelle in relazioni di tipo master-detail. TableName una delle propiet pi importanti dell'oggetto Table; essa specifica il nome della tabella fisica a cui associato l'oggetto Table. TableType una propriet che specifica il tipo fisico di tabella a cui fa riferimento l'oggetto Table stesso. Valori validi per questa propriet sono: ttDefault, ttParadox, ttDBase, ttFoxPro, ttASCII. Se impostato su ttDefault, il tipo di tabella viene determinato automaticamente in base all'estenzione del nome del file della tabella (ex.DB o niente=Paradox, DBF=DBase, TXT=ASCII). SessionName identifica il componente Session all'interno del quale agisce la tabella. La propriet State riporta lo stato della tabella e valori possibili sono: dsInactive,

dsBrowse, dsEdit, dsInsert, dsSetKey, SetRange, dsCalcFields, dsFilter, dsNewValue, dsOldValue, dsCurValue, dsBlockRead, dsInternalCalc, dsOpening. I metodi principali dell'oggetto Table sono: Append, AppendRecord, Cancel, Close, CreateTable, Delete, DeleteTable, DisableControls, Edit, EmptyTable, EnableControls, FieldByName, FindFirst, FindLast, FindNext, FindPrior, FindNearest, First, Insert, InsertRecord, IsEmpty, Last, Locate, LockTable, Lookup, MoveBy, Next, Open, Post, Prior, RenameTable, UnlockTable. Il metodo Append serve ad aggiungere un nuovo record vuoto alla fine della tabella e rende record corrente il nuovo record aggiunto e lascia la tabella in modalit inserimento. AppendRecord serve ad aggiungere alla fine della tabella un nuovo record. Questa funzione permette di passare tutti i valori dei campi che mompongono il record direttamente e di salvarne il contenuto con una unica operazione. Cancel ha lo scopo di annullare lemodifiche apportate al contenuto dei records prima che queste vengano salvate nella tabella ed imposta lo stato della tabella a dsBrowse. Close chiude la tabella impostando la propriet Active a false. Speculrmente, il metodo Open apre la tabella. Quando la tabella chiusa, non possibile leggere o scrive dati in essa. CreateTable il metodo che permette di creare fisicamente la tabella su disco basandosi sulle impostazioni contenute nelle propriet FieldsDef e IndexDefs. Per ulteriori informazioni a riguardo, consultare la guida in linea. Delete permette di eliminare il record corrente. Il metodo DeleteTable ha lo scopo di eleiminare fisicamente dal disco la tabella a cui collegato l'oggetto Table. DisableControls disattiva tutti i controlli (edit box, combobox, labels) collegati all'oggetto Table; al contrario il metodo EnableControls li attiva. Il metodo Edit permette di impostare la modalit modifica per la tabella. In questa modalit possibile modificare il contenuto dei campi dei records della tabella,ovviamente a tabella aperta. EmptyTable si occupa di eliminare tutto il contenuto di una tabella. FieldByName viene utilissato per recuperare un riferimento all'oggetto TField collegato al campo di una tabella. Questo metodo restituisce un riferimento all'oggetto TField corrispondente al campo il cui nome viene passato al metodo. FindFirst effettua una ricerca tra i records della tabella alla ricerca del primo record corrispondente ai criteri di ricerca specificati. FindLast, agisce in maniera simile al metodo FindFirst solamente che restituisce l'ultimo tra i records corrispondenti ai criteri di ricerca. FindNext, FindPrior, agiscono sulla precedente chiamata di uno dei metodi FindFirst, FindLast: il primo recupera il record successivo tra quelli corrispondenti ai criteri di ricerca, mentre il secondo il precedente. Per utilizzare questi metodi occorre prima effettuare una chiamata ad uno dei metodi FindFirst, FindNext. I criteri di ricerca vengono specificati impostati assegnando i un valore alla propriet Filter della tabella. FindNearest permette di spostare la posizione corrente della tabella al record che corrisponde il pi possibile ai criteri di ricerca specificati. Questo metodo riceve come parametri un array di valori rappresentanti il contenuto (che si sta ricercando) dei campi del record. First posizione il cursore della tabella al primo record in essa contenuto, mentre Last lo posiziona sull'ultimo; Next e Prior effettuano uno spostamento rispettivamente al record successivo o al record precedente. Insert predispone la tabella per l'inserimento di un nuovo record nella posizione del cursore della tabella. Funziona come Append solamente che l'aggiunta del nuovo record avviene nella posizione corrente della tabella. Lo stesso dicasi per InsertRecord. IsEmpty permette di stabilire se la tabella contiene records: restituisce true se non contiene alcun record, false altrimenti. Locate un metodo che permette di ricercare dei recortds all'interno di una tabella. Accetta come parametri un array contenente i nomi dei campi in cui effettuare la ricerca, una array di valori da ricercare nei corrispondenti campi dell'array precendente ed un parametro opzione che stabilisce la modalit di ricerca. Se il metodo trova un record corrispondente ai criteri di ricerca specificati, restituisce true e posiziona il cursore della tabella in quella posizione, altrimenti restituisce false e non effettua alcun spostamento. LockTable e UnlockTable sono due metodi che permattono di bloccare la tabella ad altre applicazioni impedendone l'accesso in lettura o scrittura a seconda del valore del specificato nel parametro. Lookup permette di recuperare il contenuto, o parte, di un record corrispondente a determinati criteri di ricerca. Il funzionamento simile al metodo Locate tranne per il fatto che in LookUp va specificato un elenco di campi di cui verr restituito il valore e come risultato della chiamata al metodo si otterr un variant contenente i valori dei campi speificati nell'array dei campi da restituire. In ogni caso, sia che il record venga trovato sia no, la posizine corrente della tabella non cambia. MoveBy un metodo che permette di spostarsi tra i record della tabella relativamente alla posizione corrente. Specificando valori negativi, si avr uno spostamento indietro di tanti record quanti indicati dal valore specificato, specificando valori positivi, lo spostamento avverr in avanti. RenameTable permette di rinominare la tabella fisica connessa all'oggetto Table aggiornando automaticamente anche tutti gli altri files connessi alla tabella (es. file di indice).

TQuery
Il componente Query molto simile al componente Table tranne per il fatto che i dati non vengono recuperati direttamente da una tabella, ma tramite una query SQL. Sia la classe TTable che la classe TQuery discendono entrambe dalla classe TDBDataSet. Il BDE ha integrato un semplice engine SQL (Local SQL) che permette di effettuare semplici query anche su database che non siano dei server SQL, come ad esempio delle tabelle

dBase o Paradox. Molte delle propriet e metodi dell'oggetto Query sono ugualia quelle dell'oggetto Table. In questa sezione vedremo solamente le propriet ed i metodi per cui differiscono i due oggetti. Principalmente le differenze tra gli oggetti Table e Query risiedono nelle seguenti popriet: Constrained, ParamCheck, Params, RequestLive, SQL, UniDirectional. La propriet Constrained riguarda le tabelle Paradox e DBase. Serve ad indicare se permesso o no l'inserimento e la modifica dei dati anche se i nuovi dati non sono conformi con il comando SELECT utilizzato per ottenere i dati. Per maggiori informazioni su questa propriet consultare la guida in linea. La propriet ParamCheck indica se la lista dei parametri, utilizzati nel testo della query, va rigenerata se il testo della query viene modificato a runtime. Ci utile quando vengono create query a runtime utilizzando numero e tipi diversi di parametri. Params la lista dei paramteri utilizzati nel testo SQL definito nella propriet SQL. Per accedere ai singoli parametri si utilizza la sintassi degli array indicando l'indice del paramtero che interessa reperire. Esiste anche una propriet ParamCount, non accessibile a design-time che permette di recuperare il numero di parametri presenti nel testo SQL definito. RequestLive indica se l'oggetto Query restituir un dataset modificabile o no. possibile infatti operare su un oggetto Query come su un oggetto Table, inserendo e modificando i record da esso restituiti. Se RequestLive impostato a false, non sar possibile inserire o modificare, attraverso i metodi dell'oggetto stesso, i dati dei records restituiti dalla query. Da notare che non tutti iresult set restituiti dalle query possono essere trattati in questa maniera; alcuni costrutti infatti resituiscono comunque un result set read-only, percui per modificare i valori dei records bisogna ricorrere all'esecuzione di una nuova query. SQL la propriet pi importante dell'oggetto Query. Esso contiene il testo della query che verr eseguita. Il tipo di questa propriet TStrings; ci significa per aggiungere del codice sql bisogna utilizzare il metodo Add della classe TStrings, oppure assegnare direttamente il testo sql alla propriet Text della classe TStrings. La propriet UniDirectional stabilisce il tipo di cursore che verr utilizzato per spostarsi attraverso i records restituiti dalla query. Impostato su true, lo spostamento sar possibile solamente in avanti. Per impostazione predefinita UniDirectional ha valore false, percui possibile spostarsi tra i records sia in avanti che in dietro. Vediamo ora i metodi dell'oggetto Query: ExecSQL, ParamByName, Prepare, UnPrepare. Il metodo ExecSQL permette di eseguire il testo sql definito in precedenza nella propriet SQL nel caso in cui questo non restituisca un result set, come per esempio potrebbe essere una query di inserimento dati (INSERT) oppure di eliminazione (DELETE), oppure di aggiornamento (UPDATE). Queste istruzioni sql infatti non restituiscono un set di dati, come invece accade per SELECT. ParamByName un metodo che permette di accedere, in lettura o scrittura, ai valori dei parametri definiti nel testo sql, specificando il nome del parametro. Ad esempio, se nel testo sql abbiamo definito un parametro "pippo" (ovviamente le virgolette non vanno indicate), potremmo accedere in scrittura al suo valore con NomeOggettoQuery.ParamByName('pippo').Value = valore oppure in lettura con NomeOggettoQuery.ParamByName('pippo').Value. Per definire un parametro nel testo sql necessario far precedere il suo nome dal segno ":" (due punti). Una volta definito il testo contenente la query da eseguire nella propriet SQL, completo dei parametri che si volgioni definire, si possono modificare o specificare le propriet dei vari parametri agendo sulla propriet Params. Qui possibile impostare il tipo di dato che esso conterr (String, Integer, Boolean, ...) la direzione del parametro (parametro di input o di output). Nella maggior parte dei casi non necessario modificare questi valori. I metodi Prepare e UnPrepare servono a ottimizzare la query prima di essere eseguita. Il primo comunica al BDE o al server remoto il contenuto della query da eseguire cos che il server od il BDE possa predisporre le risorse necessarie all'effettiva esecuzione della query. Questo permette di migliorare le performances dell'applicazione. Delphi richiama automaticamente questo metodo se non lo si fatto esplicitamente prima di esguire la query. Il metodo UnPrepare agisce in maniera opposta, liberando le risorse occupate per l'ottimizzazione dell'esecuzione della query.

TStoredProc
Questo oggetto permette di operare con le Stored Procedures, ovvero quelle procedure che possibile definire in alcuni server database (come SQL Server). Esse risiedono nel server e vengono eseguite nel server. Le propriet principali dell'oggetto StoredProc sono: ParamBindMode, Params, StoredProcName. ParamBindMode determina l'ordine con cui i parametri vengono assegnati ai parametri contenuti nella stored procedure contenuta nel server. Possibili valori per questa propriet sono pbByName o pbByNumber. Nel primo caso viene seguito la nomenclatura dei parametri. Nel secondo caso viene seguita la posizione. La propriet Params simile alla atessa propriet dell'oggetto Query; permette di definire i parametri in ingresso ed in uscita della stored procedure. StoredProcedure simile alla propriet TableName dell'oggetto Table e serve a specificare il nome della stored procedure presente nel server.

I metodi dell'oggetto StoredProc sono: CopyParams, DescriptionsAvailable, ExecProc, GetResults, ParamByName, Prepare, UnPrepare. Il metodo CopyParams fornisce un metodo rapido per copiare i parametri di una stored procedure ad un'altra. Il metodo DescriptionsAvailable serve a determinare se nel server sono disponibili informazioni riguardo i parametri della stored procedure; questo metodo ritorna true se le informazioni sono disponibili o false altrimenti. ExecProc il metodo da chiamare per eseguire la stored procedure nel server. Prima di effettuare la chiamata a questo metodo, occorre specificare i parametri necessari alla stored procedure ed effettuare una chiamata al metodo Prepare. GetResults serve a forzare l'invio dei parametri di output da parte dei server Sybase o MSSql. Questi server necessitano il raggiungimento dell'ultima posizione nel result set prima di inviare il contenuto dei parametri di output al client. ParamByName ha la stessa funzione del metodo ParamByName dell'oggetto Query. Prepare e UnPrepare, lo abbiamo gi visto, hanno la funzione di assegnare i valori a i parametri della stored procedure e all'ottimizzazione prima dell'esecuzione della stessa. UnPrepare rilascia le risorse occupate con il metodo Prepare.

TDatabase
L'oggetto Database permette di avere un controllo totale sulle propriet della connessione al database. Tramite questo oggetto possibile gestire il login ai server di database, controllo sulle transazioni. Delphi crea sempre, quando si utilizzano componenti per l'accesso alle basi di dati un oggetto Database e un oggetto Session, anche se non viene richiesto dall'utente. Le propriet fondamentali di questo oggetto sono: AliasName, Connected, DatabaseName, DataSets, DriverName, Exclusive, KeepConnection, Params, SessionName, ReadOnly, TransIsolation, LoginPrompt. La prima propriet, AliasName, specifica l'alias BDE da utilizzare per accedere al database. Connected indica se la connessione al database attiva. Impostare a false questa propriet determina la disconnessione del database, al contrario impostarla a true ne determina la connessione. DatabaseName il nome del database presente nel server database oppure il percorso fisico dove sonovisualizzate le tabelle dBase o Paradox. Questa propriet pu essere modificata solamente a connessione non attivata, pena il sollevamento di una eccezione. DataSets un array contenente l'elenco dei dataset correntemente aperti. Attraverso questa propriet possibile accedere alle propriet e metodi dei datasets attivi per il database indicato. DriverName, similmente alla stessa propriet dell'oggetto Table, indica il nome del driver utilizzato per l'accesso al database. Questa propriet va utilizzata quando non viene indicato un alias BDE per l'oggetto Database. Exclusive ha la stessa funzione della propriet Exclusive dell'oggetto Table: impedisce l'accesso al database ad altre applicazioni. La propriet KeepConnection serve a far si che la connessione al database sia attiva anche se non vi sono dataset aperti. Se KeepConnection true, la connessione viene mantenuta attiva anche se non si sono dataset aperti, se ha valore false, la connessione viene chiusa con la chiusura dell'ultimo dataset. Ci comporta tempi pi lunghi di accesso poich sar necessario rieffettuare la connessione al database. Params una propriet che permette di specificare dei parametri relativi al database. Ad esempio possibile indicare tra i parametri una UserId e Password per l'accesso al database cos da non doverli digitare tutte le volte che si accede al database. I parametri da specificare variano da driver a driver e i parametri specificati dall'utente vanno a sovrascrivere i parametri di default assegnati dal BDE. SessioneName contiene il nome dell'oggetto Sessione a cui il database associato. Utilizzare le sessioni utile quando necessario avere connessioni distinte dalla stessa applicazione verso lo stesso database. Potrebbe essere il caso di una applicazione server che risponda alle richieste dei client per la fornitura di informazioni da un database. Lasciando questa propriet vuota, far si che Delphi associ questo database alla sessione di default. ReadOnly specifica se il database sar accessibile in sola lettura, similmente alla stessa propriet degli oggetti Table e Query. TransIsolation permette di indicare illivello di isolamento per le transazioni controllate dal BDE. Ogni server di database ha dei caratteristici livelli di isolamento. Per un elenco consultare la documentazione in linea riguardante la propriet TarnsIsolation dell'oggetto Database. La propriet LoginPrompt permette di specificare se prima dell'apertura della connessione al database venga visualizzata o no una finestra di dialogo per la digitazione delle informazioni di login: user name e password. Metodi dell'oggetto Database: CloseDatasets, Commit, RollBack, StartTransaction, Close, Open. Il metodo CloseDatasets permette di chiudere tutti i datasets attivi relativi al database senza disconnettersi dal server. Commit viene utilizzato insieme ai metodi StartTransaction e RollBack. Questi metodi permettono di implementare il controllo delle transazioni. Per avviare una transazione, ovvero un insieme di operazioni su tabelle, query, che devono necessariamente essere portati a termine con successo perch i dati siano validi, utilizzare il metodo StartTransaction. Se tutte le operazioni intermedie sono state completate con successo, si pu utilizzare il metodo Commit per rendere effettive le modifiche oppure, in caso contrario, utilizzare il metodo RollBack per annullarle e ripristinare automaticamente lo stato precedente dei dati. I metodi Open ed Close sono utilizzati per aprire e chiudere la connessione al database. Per connettersi al server di database anche

possibile utilizzare la propriet Connected impostandola su true per la connessione e a false per la disconnessione.

TSession
L'oggetto Session permette all'applicazione di gestire gruppi di connessioni a database. L'oggetto sessione viene utilizzato per accedere a tabelle paradox in rete da pi postazioni contemporaneamente e per gestire pi connessioni simultanee allo stesso database, (ad esempio una applicazione server web che recuperi dati da database su richiesta dei clients deve gestire una sessione per ogni connessione client richiesta). In pratica la sessione ha lo scopo di isolare le attivit tra sessioni differenti. Le propriet principali dell'oggetto Sessione sono: Active, AutoSessionName, ConfigMode, DatabaseCount, Databases, KeepConnections, NetFileDir, PrivateDir, SessionName, SQLHourGlass. La propriet Active, di tipo boolean, indica quando la sessione attiva oppure no. Essa pu anche essere utilizzata per attivare o disattivare la sessione impostandone il valore. AutoSessionName una propriet molto utile, non presente nella classe TSession contenuta nelle versioni precedenti di Delphi. Essa permette di far si che venga generato automaticamente un nome univoco per la sessione. Ci utile quando viene utilizzato l'oggetto Sesisone in applicazioni multithread dove richiesta l'univocit del nome della sessione. La propriet ConfigMode definisce il modo in cui vengono gestiti gli alias all'interno della sessione. DatabaseCount contiene il numero di databases che si riferiscono alla sessione. La propriet Databases un elenco di riferimenti ad oggetti Database (TDatabase) che si riferiscono alla sessione. KeepConnections ha la stessa funzione della stessa propriet nell'oggetto Database. Si pu, tramite questa propriet, decidere se un database che si riferisce alla sessione, possa mantenere la connessione al server anche in assenza di dataset attivi. NetFileDir propriet che viene utilizzata con tabelle di database Paradox. Essa indica il percorso completo del file di condivisione in rete per le tabelle paradox (PDOXUSRS.NET). PrivateDir indica invece il percorso completo, locale, per l'appoggio di file temporanei generati dal BDE. Questa propriet viene utilizzata per settare nella macchina client un percorso (locale) per la creazione di tabelle temporanee per velocizzare l'accesso ai dati nel server. La propriet SQLHourGlass permette di specificare se il cursore cambier forma durante l'attesa di dati dal server SQL. L'impostazione di questa propriet va effettuata prima di attivare la sessione. Molto pi lunga la lista dei metodi dell'oggetto Session. Questi permettono di operare sugli alias di database, sui drivers di database, sui databases. Essi sono: AddAlias, AddDriver, AddPassword, AddStandardAlias, Close, CloseDatabase, DeleteAlias, DeleteDriver, DropConnections, FindDatabase, GetAliasDriverName, GetAliasNames, GetAliasParams, GetConfigParams, GetDatabasesNames, GetDriverNames, GetDriverParams, GetPassword, GetStoredProcNames, GetTablesNames, IsAlias, ModifyAlias, ModifyDriver, Open, OpenDatabase, RemoveAllPasswords, RemovePassword, SaveConfigFile. Attraverso AddAlias, ModifyAlias, DeleteAlias possibile rispettivamente aggiungere, modificare, eliminare un alias di database nella sessione corrente. Il primo di questi metodi accetta come parametri il nome dell'alias, il nome del driver e una lista di parametri di tipo TStrings. Attraverso la propriet ConfigMode dell'oggetto Sessione possibile indicare se gli alias creati all'interno della sessione saranno visibili da altre sessioni. Il secondo accetta il nome dell'alias da modificare e la lista contenente i parametri da moficiare ed i nuovi valori. L'ultimo accetta solamente il nome dell'alias da eliminare. Stesse funzioni hanno i metodi AddDriver, ModifyDriver, DeleteDriver. Il primo permette di aggiungere un driver di database ed accetta come parametri il nome del driver ed una lista di parametri di configurazione. ModifyDriver accetta come parametri il nome del driver da modificare ed una lista (TStrings) dei parametri di configurazione da modificare. DeleteDriver accetta il solo nome del driver da eliminare. AddPassword viene utilizzato per aggiungere una password di accesso a tabelle paradox cifrate. Se si accede ad una tabella paradox cifrata e non stata definita una password a livello di sessione o non viene fornita una password nell'evento OnPassword Delphi visualizza automaticamente una finestra di dialogo che richiede l'inserimento di una password valida. Per eliminare una password aggiunta utilizzare il metodo RemovePassword specificando la password da eliminare. Per eliminare tutte le password specificate utilizzare il metodo RemoveAllPassword. AddStandardAlias simile al metodo AddAlias tranne per il fatto che permette di agiungere solamente alias per tabelle paradox, dbase o ASCII. I metodi Open e Close permettono di attivare o disattivare la sessione. La chiusura di una sessione comporta la disconnessione di tutti i databases attivi. OpenDatabase permette di attivare la connessione ad un database, appartenente alla sessione corrente, specificandone il nome. Se il nome del database non esiste nell'elenco dei databases appartenenti alla sessione viene creato un database temporaneo e restituito un riferimento a questo come risultato. Per disconnettere ad un database appartenente alla sessione corrente, si pu utilizzare il metodo CloseDatabase fornedogli un riferimento al database che si intende disconnettere. Il metodo DropConnections ha lo scopo eliminare tutti i database temporanei creati dalla sessione. Questo metodo

necessario perch se la propriet KeepConnections true, i database temporanei creati durante la sessione, non vengono eliminati automaticamente quando vengono chiusi. FindDatabase ricerca nell'elenco dei database appartenenti alla sessione corrente il database specificato e ne restituisce un riferimento. Il metodo GetAliasDriverName restituisce il nome del driver utilizzato da un particolare alias di database il nome viene passato come parametro al metodo stesso. GetAliasNames fornisce un elenco (TStrings) di nomi di alias di database disponibili nella configurazione del BDE. GetAliasParams, restituisce in una lista di tipo TStrings un elenco dei parametri impostati per l'alias di database specificato. La lista in formato PARAMETRO=VALORE. Il metodo GetConfigParams ha lo scopo di recuperare l'elenco dei parametri con i rispettivi valori dalla configurazione del BDE. Il metodo prende in ingresso un percorso (corrispondente al ramo della configurazione di cui si vogliono i parametri) un nome di sessione ed un riferimento ad una lista di stringhe (TStrings) che verr riempita con i parametri recuperati. GetDatabaseNames restituisce un elenco di tutti gli alias di database definiti nella cinfigurazione del BDE e tutti i database conosciuti dalla sessione corrente. In maniera simile agisce il metodo GetDriverNames che restituisce un elenco di tutti i driver di database disponibili nella sessione. GetDriverParams, in maniera simile al metodo GetAliasParams, restituisce in una lista di stringhe di tipo TStrings, un elenco dei parametri associati al driver di database specificato. Il metodo GetPassword permette di recuperare una password per l'accesso ad un database. Effettuando una chiamata al metodo, viene prima attivato l'evento OnPassword e, nel caso in cui non fosse definito un gestore di evento, visualizzata la finsetra di dialogo standard per la richiesta della password. Il metodo restituisce true se viene recuperata una password, false altrimenti. GetStoredProcNames al pari di altri metodi gi visti, restituisce un elenco delle stored procedure presenti in database appartenente alla sessione corrente. GetTableNames restituisce l'elenco delle tabelle appartenenti ad un database, specificato, appartenete alla sessione. Alcuni parametri passati a questo metodo, permettono di filtrare l'elenco delle tabelle in base al nome, all'estenzione (tabelle paradox, dbase), di indicare se resituiture anche le tabelle di sistema del database (SQL servers databases). Il metodo IsAlias permette di determinare se un determinata stringa passatagli, corrisponde ad un alias di database conosciuto dalla sessione. Il metodo SaveConfigFile importante quando si vogliono rendere persistenti le modifiche apportate alla configurazione del BDE, ovvero agli alias di datbase o ai drivers. Alcune modifiche non verrano salvate se il parametro ConfigMode dell'oggetto Session impostato su cmSession.

TBatchMove
L'oggetto BatchMove ha lo scopo di permettere alle applicazioni di effettuare operazioni batch (spostamenti, eliminazioni, trasformazioni, etc) su gruppi di records o su intere tabelle. Questo oggetto opera su due tabelle, una sorgente ed una di destinazione. Possiede una gestione dei problemi riguardanti violazioni di chiave od altri prolemi durante l'esecuzione della procedura batch. Nel caso vi siano errori, questi vengono riportati in due tabelle. Ecco l'elenco delle sue propriet: AbortOnKeyViol, AbortOnProblem, ChangedCount, ChangedTableName, CommitCount, Destination, KeyViolCount, KeyViolTableName, Mappings, Mode, MovedCount, ProblemCount, ProblemTableName, RecordCount, Source, Transliterate. AbortOnKeyViol e AbortOnProblem sono due propriet che definiscono il comportamento dell'oggetto nel caso si verifichino errori. Impostando una o entrambe queste propriet a true l'operazione batch verr annullata. ChangedCount riporta il numero di record che sono stati modificati. ChangedTableName specifica il nome della tabella paradox che conterr una copia dei records originali che sono stati modificati. CommitCount permette di specificare quanti records devono essere modificati prima che le modifiche vengano applicate effettivamente. Destination e Source specificano i nomi degli oggetti Table che verranno utilizzati rispettivamente come tabella sorgente e come tabella di destinazione. KeyViolCount riporta il numero di violazioni di chiave incontrati durante l'esecuzione della procedura batch. La propriet KeyViolTableName indica il nomefile di una tabella paradox utilizzata per contenere i records che hanno generato una violazione di chiave. Mappings una propriet di tipo TStrings che permette di specificare la corrispondenza dei campi tra la tabella sorgente e quella di destinazione. Il formato da utilizzare del tipo NOMECAMPODESTINAZIONE=NOMECAMPOSORGENTE. Per far si che il contenuto di un campo nella tabella sorgente vada in un campo con lo stesso nome nella tabella di destinazione, indicarne solamente il nome. La propriet Mode determina iltipo di operazione batch da effettuare. Valori validi per questa propriet sono: batAppend, batUpdate, batAppendUpdate, batCopy, batDelete. La modalit di funzionamento predefinita batAppend. Consultare la guida in linea per un approfondimento sulle funzioni dei valori indicati. MovedCount riporta il numero di records che sono stati processati e trasferiti nella tabella di destinazione. ProblemCount indica il numero di records che non sono stati aggiunti alla tabella di destinazione per il verificarsi di problemi. ProblemTableName specifica il nomefile di una tabella parado che conterr i records che non sono stati aggiunti alla tabella di destinazione a causa di errori. RecordCount indica il numero massimo di record che verranno spostati dalla tabella sorgente a quella

di destinazione. La propriet Transliterate specifica se durante l'operazione batch deve essere effettuata una conversione in base al linguaggio del driver utilizzato per la tabella di destinazione. L'unico metodo rilevante per questo oggetto il metodo Execute che avvia la procedura batch.

TUpdateSQL
L'oggetto UpdateSQL permette di specificare degli scripts SQL per la modifica, l'inserimento e l'eliminazione di records in dataset di sola lettura di tipo TQuery o TStoredProc. Le propriet di questo oggetto sono: DataSet, DeleteSQL, InsertSQL, ModifySQL, Query, SQL. La propriet Dataset indica il componente TQuery o TStoredProc che utilizzer l'oggetto UpdateSQL. DeleteSQL, InsertSQL, ModifySQL, sono tre propriet di tipo TStrings che conterranno gli scripts SQL per, rispettivamente, eliminare, inserire, modificare il contenuto del dataset collegato. La propriet Query restituisce, in base all'indice specificato, l'oggetto TQuery utilizzato per eliminare, inserire o modificare i dati del dataset. Valori di indice validi sono: ukDelete, ukInsert, ukModify. In maniera simile alla propriet precedente, SQL resituisce la stringa SQL utilizzata per eliminare, inserire, modificare il dataset. I metodi dell'oggetto UpdateSQL sono: Apply, ExecSQL, SetParams. Il metodo Apply permette di assegnare i parametri alla stringa SQL corrispondente all'indice specificato e di eseguirla per aggiornare il record. ExecSQL in maniera simile ad Apply esegue il codice SQL in base all'ndice specificato. Da notare per che se il codice SQL specificato contiene dei parametri, occorre chiamare prima il metodo SetParams. LEZIONE 36:

Componenti per la manipolazione dei dati (Data-Aware)

Dopo aver trattato i componenti per l'accesso alle sorgenti di dati, passiamo ora a vedere i componenti che ci permettono di interagire in maniera visuale con i dati provenienti da queste sorgenti dati. Come visibile nell'immagine qui sotto, questi componenti sono raccolti nella pagina "Data Controls" della Component Palette.

Questa pagina ci mette a disposizione una griglia, una barra per la navigazione all'interno dei nostri datasets, una etichetta collegata direttamente ad un campo di un dataset, una casella di edit, un memo, un componente immagine da collegare direttamente al campo del nostro dataset per visualizzare l'immagine in esso contenuta, una casella combinata, una lista, una casella spuntabile per valori booleani, un gruppo di componenti per selezioni esclusive, una casella di lookup, una lista di lookup, un componente per formato RTF, una griglia di controllo, una componente per la visualizzazione dei dati in forma grafica (Torte, Istogrammi, ...). Si tratta di componenti Data-Aware, ovvero componenti che si collegano direttamente alla sorgente di dati e che automaticamente gestiscono la visualizzazione, la modifica. Questi componenti vengono collegati al dataset indirettamente, ovvero vengono collegati ad un datasource che fa riferimento al dataset interessato. Come per gli oggetti per l'accesso alle sorgenti di dati, daremo uno sguardo alle propriet e metodi principali e pi utilizzati di questi componenti. Segue un elenco dei nomi dei componenti e le loro rispettive classi: DBGrid (TDBGrid), DBNavigator (TDBNavigator), DBLabel (TDBLabel), DBEdit (TDBEdit), DBMemo (TDBMemo), DBListBox (TDBListBox), DBComboBox (TDBComboBox), DBCheckBox (TDBCheckBox), DBRadioGroup (TDBRadioGroup), DBLookupListBox (TDBLookupListBox), DBLookupComboBox (TDBLookupComboBox), DBRichEdit (TDBRichEdit), DBCtrlGrid (TDBCtrlGrid), DBChart (TDBChart).

TDBGrid
L'oggetto DBGrid permette di visualizzare e manipolare i records di un dataset in una griglia tipo quella utilizzata da programmi come MS Excel. Questo oggetto deriva da TCustomDBGrid che a sua volta deriva da TCustomGrid. Tratteremo solamente le propriet ed i metodi relativi all'oggetto TCustomDBGrid poich la

classe TDBGrid non aggiunge alcun metodo o propriet, ma espone solamente i metodi della classe da cui deriva, appunto TCustomDBGrid. Ecco l'elenco delle propriet dell'oggetto DBGrid: Columns, DataSource, DefaultDrawing, FieldCount, Fields, Options, ReadOnly, SelectedRows, TitleFont, BorderStyle, FixedColor. La propriet Columns, di tipo TDBGridColumns, permette di definire le colonne che verranno visualizzate nella griglia. Questa propriet un elenco di oggetti TColumn che definiscono le propriet delle varie colonne che andranno a formare la tabella, a partire dal campo del dataset a cui si riferisce la colonna, al colore del titolo, l'allineamento del testo etc... Per modificare la propriet Columns, disponibile un editor di propriet, avviabile, come sempre, attraverso il bottone con i tre puntini nel Object Inspector. Se non vengono definite delle colonne per la griglia, verranno automaticamente visualizzate tante colonne quanti sono i campi visibili del dataset. La propriet DataSource quella che permette di impostare il riferimento all'oggetto DataSource che a sua volta si riferisce al dataset (Table, Query). DefaultDrawing indica se le celle della griglia verranno disegnate automaticamente oppure il loro disegno verr personalizzato attraverso i gestori di eventi OnDrawColumnCell o OnDrawDataCell. Se impostato su True l'oggetto griglia si occuper direttamente del disegno dei dati nella cella, dal colore di sfondo, al font, al valore dei dati, se impostato su false, verr gestito dall'oggetto griglia solamente il disegno dello sfondo, il disegno dei valori dei dati demandato ai gestori di eventi OnDrawColumnCell o OnDrawDataCell. FieldCount una propriet che indica il nome dei campi, ovvero delle colonne, visualizzate nella griglia. La propriet Fields permette di accedere in forma indicizzata i campi corrispondenti alle colonne visualizzate, come per la propriet Fields dell'oggetto Table. Attraverso la propriet Options possibile definire alcune caratteristiche funzionali e grafiche della griglia. Alcune di esse sono la possibilit di editare il contenuto della griglia, la visualizzazione di un indicatore di riga, la visualizzazione dei titoli al disopra delle colonne, la possibilit di selezionare un'intera riga della griglia, effettuare una selezione su pi righe, richiedere la conferma prima di eliminare un record dal dataset. I valori disponibili per la propriet sono: dgEditing, dgAlwaysShowEditor, dgAlwaysShowEditor, dgRowSelect, dgTitles, dgIndicator, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgRowSelect, dgAlwaysShowSelection, dgConfirmDelete, dgCancelOnExit, dgMultiSelect. La propriet ReadOnly, autoesplicativa, ovvero permette di impostare in sola lettura il contenuto della griglia cos da impedirne la modifica. SelectedRows, riporta un elenco di Bookmark (Segnalibri che vengono utilizzati per segnare una posizione come selezionata all'interno della griglia) corrispondenti alle posizioni selezionate nella griglia. La propriet TitleFont permette di impostare il font per le celle contenenti i titoli delle colonne. Attraverso la propriet BorderStyle, si pu definire il tipo di bordo disegnato intorno alla griglia. In ultima, la propriet FixedColor definisce il colore delle celle della griglia che rimangono fisse durante lo scorrimento della stessa. Dopo aver visto le propriet passiamo ora ai metodi. Eccone l'elenco: DefaultDrawColumnCell, DefaultDrawDataCell. DefaultDrawColumnCell viene utilizzato all'interno di un gestore di evento OnDrawColumnCell per il disegno del testo. Il risultato della chiamata a DeafulDrawColumnCell lo stesso che impostare la propriet DefaultDrawing a True, tranne per il fatto che che non viene disegnato il rettangolo di fuoco della cella. DefaultDrawDataCell l'equivalente di DefaultDrawColumnCell tranne per il fatto che in questo non vi un riferimento alla colonna a cui appartiene la cella.

TDBNavigator
Il componente DBNavigator, un componente visuale che permette di facilitare lo spostamento all'interno di dataset mettendo a disposizione una barra contenente dei pulsanti corrispondenti alle tipiche funzioni di spostamento. Ci si pu spostare in avanti, in dietro, alla prima posizione, all'ultima, aggiungere una nuova posizione, eliminare quella corrente, annullare le modifiche apportate ai dati. Le propriet di DBNavigator sono accessibili quasi completamente dall'Object Inspector a design-time e sono: ConfirmDelete, DataSource, Flat, Hints, VisibleButtons. ConfirmDelete permette di specificare se verr richiesta la conferma per l'eliminazione del record oppure no. Impostando questa propriet a true, verr presentata una finestra di dialogo attraverso la quale sar possibile decidere se continuare con l'eliminazione oppure annullare l'operazione. DataSource la propriet che serve ad indicare quale il dataset a cui fa riferimento l'oggetto DBNavigator. La propriet Flat ha lo scopo di definire l'aspetto dei pulsanti della barra di navigazione, ovvero se questi avranno uno stile piatto oppure tradizionale. Attraverso la propriet Hints si pu definire il contenuto dei tooltip che vengono visualizzati quando ci si ferma sopra i pulsanti della barra. Questa propriet potrebbe essere utilizzata per esempio per visualizzare in italiano la funzione dei vari pulsanti della barra visto che di default il testo visualizzato in inglese. L'ultima propriet quella che definisce quali pulsanti visualizzare nella barra. Non sempre sono necessari tutti i pulsanti presenti nella barra, ad esempio potrebbero servire

solamente i pulsanti di spostamento e non quelli di inserimento, modifica ed eliminazione. Tramite VisibleButtons, di tipo TButtonSet, possibile specificare l'elenco dei pulsanti da visualizzare. Per quanto riguarda i metodi dell'oggetto DBNavigator, interessante il metodo BtnClick che permette di eseguire la funzione associata al pulsante nella barra di navigazione, come se il pulsante fosse stato premuto dall'utente. Questo metodo accetta come parametro il tipo di pulsante che di cui si vuole simulare la pressione (nbFirst, nbPrior, nbNext, nbLast, nbInsert, nbDelete, nbEdit, nbPost, nbCancel, nbRefresh). L'esecuzione del metodo attiva comunque l'evento OnClick dell'oggetto DBNavigator.

TDBText
L'oggetto DBText un oggetto visuale che permette di visualizzare il contenuto testuale di un campo di un dataset. Attraverso questo oggetto l'utente nonpu modificare il contenuto del campo visualizzato. Le propriet dell'oggetto DBText sono: AutoSize, DataField, DataSource. La propriet AutoSize dell'oggetto DBText indica se la dimensione dell'oggetto viene modificata automaticamente rispecchiando la dimensione del testo da visualizzare. Impostare AutoSize a false a volte utile per impedire che il testo da visualizzare vada a sovrapporsi ad altri controlli nelle schermate del programma. DataField contiene il nome del campo del dataset di cui verra visualizzato il contenuto. DataSource conterr il riferimento all'oggetto DataSource a cui collegato il dataset a cui collegare l'oggetto DBText.

TDBEdit
TDBEdit un oggetto visuale rappresentante una casella di testo dove possibile visualizzare, inserire, modificare del testo contenuto in un campo di un dataset. Questo oggetto deriva da altri oggetti non connessi direttamente a datasets come CustomMaskEdit e CustomEdit. Per le propriet ereditate da questi oggetti si rimanda alla guida in linea. Come per altri oggetti connessi ai dataset, le propriet pi importanti sono DataField, DataSource e ReadOnly. Per le prime due il significato lo stesso degli oggetti visti in precedenza, ReadOnly presente, come nel nostro caso, in quei componenti che permettono la modifica dei dati. Attraverso questa propriet, impostando il suo valore a true, si impedisce la modifica dei dati visualizzati nel solo componente DBEdit interessato.

TDBMemo
Il componente DBMemo simile al componente DBEdit, ha la funzione di visualizzare il testo contenuto in un campo di un dataset con la differenza che il testo viene visualizzato su pi righe. Le propriet fondamentali sono le stesse dell'oggetto DBEdit, mentre per le propriet relative all'oggetto TCustomMemo, da cui DBMemo deriva, consultare la guida in linea.

TDBImage
L'oggetto DBImage viene utilizzato per visualizzare ed inserire immagini in un campo di un dataset. Solitamente questo oggetto viene collegato a campi di tipo blob. Oltre alle propriet comuni a tutti i componenti data-aware, l'oggetto DBImage possiede le seguenti propriet: AutoDisplay, BorderStyle, Center, Picture, QuickDraw, Stretch. La propriet AutoDisplay, indica se l'immagine contenuta nel campo viene automaticamente caricata e visualizzata. Impostando questa propriet a false, per visualizzare l'immagine sar necessario effettuare un doppio click sull'oggetto oppure selezionare l'oggetto e premere enter. Attraverso BorderStyle possibile definire l'aspetto del bordo dell'immagine che pu essere: bsNone, bsSingle, bsSizeable, bsDialog, bsToolWindow, bsSizeToolWin. La propriet Center indica se

l'immagine verr visualizzata al centro dell'oggetto DBImage. Per ovvi motivi, questa propriet non avr alcun effetto se la propriet Stretch sar impostata a true. La propriet Picture contiene un riferimento all'immagine contenuta nel campo del dataset. Questa propriet risulta molto utile per caricare una immagine nel campo blob oppure per salvare su file il contenuto del campo blob stesso. Per maggiori informazioni sui metodi e sulle propriet dell'oggetto Picture consultare la guida in linea riguardo alla classe TPicture. Attraverso QuickDraw, possibile velocizzare la visualizzazione dell'immagine, disabilitando l'utilizzo di una palette di colori, ottenendo per una qualit inferiore dell'immagine. Impostando questa propriet a false, verr utilizzata una palette di colori per permettere una visualizzazione migliore dell'immagine, introducendo dei ritardi di visualizzazione causati dall'elaborazione pi complessa. La propriet Stretch permette di indicare se l'immagine visualizzata verr riadattata alle dimensioni dell'oggetto DBImage effettuando appunto un stretching, con conseguente probabile deformazione o perdita di definizione dell'immagine. I metodi dell'oggetto DBImage sono: CopyToClipBoard, CutToClipBoard, LoadPicture, PasteFromClipBoard. I metodi relativi alla clipboard (gli appunti di Windows) sono abbastanza autoesplicativi e servono appunto a copiare ed a tagliare il contenuto mettendolo nella clipboard o ad incollarlo prelevandolo da essa. LoadPicture ha lo scopo di caricare l'immagine dal campo del dataset. Questo metodo necessario per visualizzare il contenuto dell'immagine se la propriet AutoDisplay stata impostata false.

TDBListBox, TDBComboBox, TDBRichEdit, TDBCheckBox, TDBRadioGroup


Per tutti questi componenti, non esistono particolari propriet o metodi all'infuori di quelli gi visti comuni a tutti i componenti Data-Aware (DataSource, DataField, etc.). Pertanto per le propriet di questi controlli si rimanda alla guida in linea di Delphi. L'unica differenza interessante tra la versione non Data-Aware e la versione standard riguarda il controllo TDBRadioGroup che possiede una propriet Values che ha lo scopo di permettere l'assegnazione di valori personalizzati da salvare nel campo del Dataset. Questa propriet di tipo TStrings e se non vengono definiti dei valori per essa, nel campo dele dataset verr salvato l'indice, in base 0, della voce selezionata.

TDBLookupListBox, TLookupComboBox
Questi due controlli, sono in tutto simili ai rispettivi TDBListBox e TDBComboBox tranne per il fatto che i valori riportati nelle liste di scelta, provengono da un dataset. Questi controlli sono collegati a due dataset: uno il dataset di lavoro (dove vengono inseriti i valori del controllo) e l'altro quello da dove vengono recuperati i valori che appaiono nelle liste di scelta. Per quanto riguarda il collegamento al dataset principale, non c' nulla di nuovo da dire, il collegamento segue le regole gi viste. Per il dataset di origine dei valori delle liste, ci troviamo di fronte a delle nuove propriet che sono comuni a tutti quei componenti di tipo lookup che derivano da TDBLookupControl. Queste propriet sono: KeyField, KeyValue, ListField, ListFieldIndex, ListSource. Tutte queste propriet, all'infuori di KeyValue, sono accessibili a design-time tramite l'Object Inspector. KeyField di tipo string e indica il campo del dataset sorgente per la lista da cui verranno estratti i dati da inserire nel campo del dataset principale indicato da DataField. KeyValue contiene il valore del campo indicato in KeyField. Esso non corrisponde al valore visualizzato dal controllo. Assegnando a questa propriet un valore, il controllo effettuer una ricerca all'interno del dataset sorgente per la lista alla ricerca di un record il cui contenuto del campo riportato in KeyField corrisponde al valore riportato in KeyValue. ListField, di tipo string e contiene il campo del dataset sorgente per la lista il cui valore sar visualizzato dal controllo. Nel caso di TDBLookupComboBox, sara il campo i cui valori appariranno nella lista a discesa visualizzata dal controllo. ListFieldIndex indica l'indice che verr utilizzato per ordinare la lista dei valori visualizzati dal controllo. ListSource contiene il riferimento al datasource che si riferisce al dataset sorgente per la lista di valori.

TDBCtrlGrid
Questo controllo particolare ma molto utile per creare una visualizzazione particolare del contenuto dei records di un dataset. Esso permette di raggruppare in pannelli i contenuti dei records di un dataset,

permettendo di disporre a piacere i controlli che effettivamente visualizzano il contenuto dei campi dei records. A differenza degli altri componenti data-aware che visualizzano o gestiscono direttamente il contenuto dei campi dei records, il controllo DBCtrlGrid non possiede, ovviamente, la propriet FieldName. Questo controllo lavora con i records piuttosto che con i campi dei records. Infatti se viene posto in una form un controllo DbCtrlGrid senza inserirvi degli altri controlli data-aware all'interno che gestiscano il contenuto dei campi dei records non si vedr apparire nulla all'interno di esso. Tra le caratteristiche impostabili di questo controllo vi la possibilit di scegliere il numero di righe e colonne, l'orientamento, la dimensione dei pannelli ed altre caratteristiche. Ecco le propriet principali di questo controllo: AllowDelete, AllowInsert, Canvas, ColCount, DataSource, EditMode, Orientation, PanelBorder, PanelCount, PanelHeight, PanelIndex, PanelWidth, RowCount, SelectedColor, ShowFocus. Le propriet AllowDelete e AllowInsert indicano se il controllo pu permettere all'utente l'inserimento di un nuovo record oppure l'eliminazione di quello corrente. Queste due operazioni possono essere effettuate attraverso la pressione rispettivamente dei tasi Ctrl+Delete e Ctrl+Insert sulla tastiera quando il controllo possiede il fuoco. La propriet Canvas, disponibile solamente a run-time, permette di accedere al controllo Canvas che gestisce la grafica dei pannelli della griglia. ColCount e RowCount, permettono di specificare rispettivamente il numero di colonne e di righe che verranno rappresentati contemporaneamente nel controllo. Queste propriet definiscono altres il numero di pannelli visibili nella griglia. Questo valore reperibile nella propriet PanelCount disponibile a run-time. DataSource definisce il riferimento alla sorgente dati collegata al dataset a cui la griglia sar collegata. EditMode permette di stabilire la modalit di funzionamento della griglia, ovvero se questa pu permettere l'inserimento l'eliminazione e la modifica dei records, un po' come la propriet ReadOnly di altri controlli. PanelBorder definisce lo stile del bordo dei pannelli nella griglia. I valori possibili sono gbNone o gbRaised. Attraverso le propriet PanelHeight e PanelWidth possibile definire le dimensioni dei pannelli, rispettivamente altezza e larghezza. SelectedColor definisce il colore che verr utilizzato per indicare il pannello corrispondente al record corrente nel dataset. In ultimo ShowFocus permette di indicare se il controllo DBCtrlGrid deve disegnare un rettangolo di fuoco attorno al pannello corrispondente al record corrente quando il controllo ha il fuoco. LEZIONE 36:

I Reports

Nelle sue distribuzioni standard, Delphi fornisce dei componenti per creare, organizzare e stampare i resoconti delle eleborazioni su database effettuate dalle nostre applicazioni. Questo insieme di componenti costituiscono la suite QuickReport che permette di creare visualmente lo schema da utilizzare per stampare i dati. Questa suite permette di realizzare a design-time il report, di effettuarne l'anteprima sia in fase di design che di runtime, effettuare calcoli l''interno del report stesso, inserire grafici recuperare automaticamente i dati dai campi dei records, la realizzazione di report a pi livelli o collegati, effettuare un export del risultato dell'elaborazione in formato testuale, CSV o HTML. Su internet ed in particolare sui news group che trattano di Delphi molti non trovano bene con questi componenti, che in effetti, almeno nelle versioni precedenti a quella presente con Delphi 5, avevano qualche difetto. Esistono altre soluzioni per la creazione di reports, sia commerciali che freeware, pi o meno complesse e complete. Una delle suite che ritengo meriti di essere mensionata FuzzyReport (precedentemente FastReport) creata da Fabio Dell'Aria che mette a disposizione un editor di reports a runtime permettedo quindi all'utente finale di personalizzare i reports dell'applicazione. Tutto questo free, e anche con prestazioni migliori rispetto a quelle di QuickReport. Per ulteriori informazioni potete visitare il sito http://www.fuzzyreport.shorturl.com/. Comunque in questo corso tratteremo in maniera molto breve gli strumenti standard forniti con Delphi.

Il componente fondamentale di QuickReport il componente QuickRep, il cuore del sistema di generazione dei reports. Esso si presenta come un foglio bianco da posizionare in una form nel quale andranno posizionati gli altri componenti di QuickReport che andranno a definire l'aspetto grafico e funzionale del report stesso. QuickReport uno di quegli strumenti per la costruzione di reports definiti a "bande". Infatti per definire la struttura del report si utilizzano delle bande, ogniuna con uno specifico compito. Eistono bande titolo, itestazione, pi di pagina, header, dettaglio, sottodettaglio e cos via. All'interno di cisacuna banda andranno inseriti altri componenti visuali, che serviranno a rappresentare i dati del report: possiamo avere delle etichette sia statiche che collegate a campi di database, come il componente Label standard, delle immagini, sia statiche che ricavate da campi blob di database, campi per inserire formule, componenti per le forme, per

grafici statistici o per ricavare informazioni strettamente riguardanti il report come il numero di pagine totali o il numero di pagina corrente, la data di stampa etc. La costruzione di un report pu avvenire in due modi, attraverso i wizards attraverso il menu File->New oppure "a manina" partendo da una normalissima form. Per prima cosa si posiziona un componente QuickRep nella form che assumer l'aspetto di un foglio bianco con una griglia quadrettata e l'indicazione dell'area stampabile nella stampante predefinita. A questo punto gi possibile definire alcune delle caratteristiche del report; attraverso la propriet Bands del componente QuickRep, possibile definire quali bande sono necessarie alla realizzazione del report: Title, PageHeader, PageFooter, etc. La propriet Dataset serve a legare il report ad un dataset per il recupero dei dati. La propriet Description serve a definire una descrizione per il report; questa propriet potrebbe tornare utile quando si da la possibilit all'utente di scegliere un report, poich attraverso questa propriet si pu fornire una descrizione del report. Attraverso la propriet Frame possibile specificare se il report verr riquadrato segnando l'area stampabile dalla stampante. Accedendo alla propriet Page possibile definire le propriet della pagina di stampa, larghezza, altezza, margini e se si vuole, la visualizzazione dei righelli che ovviamente saranno visibili solamente in fase di disegno del report. PrinterSettings permette di impostare le opzioni per la stampante, il numero di copie, il binario della carta, etc. PrinterIfEmpty indica a QuickReport se stampare o no il report se il dataset a cui collegato vuoto. ReportTitle serve ad impostare il titolo del report e pu essere utilizzato nel report stesso attraverso il componente QRSysData e la stessa propriet viene utilizzata nell'elenco dei job di stampa nel "Print Manager" di windows. Vi sono ancora altre propriet interessanti in questo componente come ShowProgress che serve a visualizzare o no l'avanzamento dello stato di costruzione del report, Unit che indica l'unit utilizzata nella rappresentazione della griglia e dei righelli ed infine lo Zoom, che permette di regolare la visualizzazione del report in fase di disegno. Completata la costruzione del report, per permetterne la visualizzazione necessario eseguire all'interno del codice del programma il metodo Preview del componente QuickRep, per visualizzare l'anteprima del report, o il metodo Print per effettuarne la stampa. QuickReport permette di personalizzare l'aspetto della form di anteprima, mettendo a disposizione il componente QRPreview. Per maggiori informazioni a riguardo consultare la guida in linea. Un'altra possibilit messa a disposizione da QuickReport quella di esportare il risultato dell'eleborazione del report in formato testuale formattato o in HTML attraverso i componenti QRTextFilter, QRCVSFilter, QRHTMLFilter. L'esportazione molto semplice: basta collocare un componente di quelli precedentemente mensionati in una form ed automaticamente, attraverso l'opzione salva nella finestra di anteprima del report, verr visualizzata l'opzione di esportazione scelta. Si pu efettuare la stessa operazione da codice attraverso il metodo ExportToFilter del componente QuickRep. LEZIONE 36:

Modifica e creazione di componenti

Fino ad ora abbiamo visto pi o meno come utilizzare i molti componenti che sono disponibili in Delphi. Ma cosa fare tra tutti questi componenti non troviamo quello che fa al caso nostro? Semplice, possiamo crearcelo da soli oppure modificare le caratteristiche di uno gi esistente che assomiglia molto al componente che stiamo cercando. Questa non difficile costruire nuovi componenti o modificare quelli gi esistenti, ma occorre avere una discreta conoscenza dell'Object Pascal poich la realizzazione di un componente non prevede l'impiego di tool di sviluppo visuale (almeno direttamente forniti con Delphi) e quindi necessario scrivere tutto il codice a mano. Occorre altres avere una buona conoscenza dei componenti gi esistenti e delle classi base messe a disposizione da Delphi per evitare di riscrivere del codice o componenti gi esistenti. In questa sezione del corso vedremo prima di tutto come modificare un componente gi esistente e poi come costruire un semplicissimo componente "ex novo". Quando si decide di realizzare un componente, necessario analizzare attentamente i componenti esistenti e tenere presente che non possibile nascondere metodi o propriet di un componente. Per questo la scelta del componente (classe) da cui ereditare le caratteristiche necessarie ha una grande importanza. Delphi come al solito, ci viene incontro fornendoci delle classi base dei quasi tutti i componenti standard della VCL (TEdit, TComboBox, ...). Queste classi hanno nomi del tipo TCustomEdit, TCustomComboBox, etc. Esse implementano tutte le funzionalit e propriet dei componenti veri e propri ma non le rendono visibili all'esterno. Le classi che implementano il componente vero e proprio, non fanno altro che ereditare dalle classi base e rendere visibili all'esterno tutti i metodi e propriet della relativa classe base. Ci permette di rendere visibili solo alcuni dei metodi e propriet che ci interessano. Per creare un nuovo componente, possiamo seguire due strade: attraverso il menu Component->New Component, oppure scrivendo da soli lo scheletro di base per il nostro oggetto. Conviene comunque seguire la prima strada che risparmia, se non altro, il tempo per la scrittura del codice. Cominciamo.

Apriamo la finestra di dialogo New Component come sopra descritto e digitiamo quanto riportato nell'immagine seguente

Fig. 1 - Dialogo per creazione nuovo componente

Ancestor Type: indica la classe di base da cui far discendere il nostro nuovo componente. Class Name: il nome che vogliamo dare alla nostra nuova classe; viene automaticamente compilato quando si sceglie il tipo di classe da cui ereditare ma pu essere modificato a piacere. Il nome scelto per questo esempio TMyEdit, ma nessuno vieta di scegliere un altro nome. Occhio a non usare nomi di classi gi esistenti! Palette Page: la pagina della Componente Palette di Delphi dove verr posizionato il componente quando andremo a registrarlo nell'IDE di Delphi. In questo esempio ho scelto di posizionare il nuovo componente nella pagina Samples della component Palette, ma voi potrete scegliere la pagina che preferite. Unit File Name: il nome, completo di percorso, che vogliamo dare alla unit che conterr il codice del nostro nuovo componente. In questa casella potete inserire il percorso che preferite che sicuramente differir da quello da me scelto. Search Path: l'elenco di percorsi all'interno dei quali Delphi ricercher le unit necessarie per il componente. Completata questa fase, premiamo il tasto OK e vedremo che Delphi creer una unit con il nome da noi fornito contenente l'impalcatura della nostra nuova classe. Ecco il codice che comparir nell'Code Editor al termine dell'operazione

unit MyEdit; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMyEdit = class(TEdit) private { Private declarations } protected { Protected declarations } public { Public declarations } published

{ Published declarations } end; procedure Register; implementation procedure Register; begin RegisterComponents('Samples', [TMyEdit]); end; { TMyEdit } end. Come potete vedere, sono rintracciabili tutte le informazioni da noi fornite all'interno della finestra di dialogo, ma non abbiamo dovuto scrivere una riga di codice! Ora passiamo a modificare le impostazioni di default della classe TEdit. Vogliamo impostare come colore di sfondo di default del componente TEdit il colore giallino utilizzato nei ToolTips e impostare la sua lunghezza di default a 200 pixels. Bene, procediamo con il fare l'override della procedura di creazione del componente. Aggiungiamo nella sezione Public della dichiarazione della classe la ridichiarazione del costruttore Create come segue Constructor Create(AOwner : TComponent); override; una volta scritto il codice sopra riportato, possiamo utilizzare la combinazione di tasti Ctrl+Shift+C per dire a Delphi di completare il codice automaticamente. Questa operazione porter alla creazione del codice di implementazione del costruttore appena definito nella forma seguente

constructor TMyEdit.Create(AOwner: TComponent); begin inherited; end; a questo punto possiamo aggiungere il codice necessario per personalizzare i nostri valori di default; modifichiamo l'implementazione del costruttore come segue

constructor TMyEdit.Create(AOwner: TComponent); begin inherited; Width := 200; Color := clInfoBk; end; Con queste righe di codice impostiamo a 200 pixels la lunghezza del controllo ed il colore di sfondo al colore di sfondo correntemente attivo per i ToolTips. La procedura Register serve a Delphi per registrare il componente in una pagina della Components Palette; questa procedura chiama infattai la procedura RegistreComponents passandogli come parametro il nome della pagina della Components Palette: se la pagina esiste vi registra il componente altrimenti la crea e quindi vi registra il componente.

Questo un semplicissimo esempio di come possibile personalizzare alcune caratteristiche di un componente gi esistente. Si possono anche aggiungere funzionalit ad un componente gi esistente; per esempio, potremmo voler aggiungere al nostro componente MyEdit una propriet che contenga il testo digitato nel controllo con l'ordine dei caratteri invertito. Vediamo come implementare questa caratteristica. Aggiungiamo nella parte Private della nostra classe TMyEdit la funzione Reverse dichiarandola come segue

Function Reverse : String; ed implementandola come segue

function TMyEdit.Reverse: String; Var I, L : Integer; begin L := Length(Text); SetLength(Result, L); For I := 1 to Length(Text) do Result[(L+1) - I] := Text[I]; end; Questa funzione legge la stringa contenuta nella propriet Text del controllo (ereditata dalla classe TEdit) e dispone i caratteri che la compongono in ordine inverso. La propriet Text contiene il testo digitato dall'utente. Ora occorre implementare una propriet che restituisca, in lettura, il testo invertito. Aggiungiamo nella parte di dichiarazione Public della nostra classe TMyEdit la propriet ReversedTxt nel seguente modo:

Property ReversedTxt : String read GetReversedTxt; Premendo Ctrl+C dopo aver digitato questa riga, otterremo il completamento automatico della nostra propriet con la creazione della funzione GetReversedTxt. Ecco come apparir la dichiarazione della nostra classe dopo queste modifiche

TMyEdit = class(TEdit) private { Private declarations } Function Reverse : String; function GetReversedTxt: String; protected { Protected declarations } public { Public declarations } Constructor Create(AOwner : TComponent); override; Property ReversedTxt : String read GetReversedTxt; published { Published declarations } end; Modifichiamo l'implementazione della funzione GetReversedTxt come segue

function TMyEdit.GetReversedTxt: String; begin Result := Reverse; end; Si pu anche eliminare la funzione Reverse implementando il codice direttamente nella funzione GetReversedTxt ma per questioni di chiarezza e modularit del software, preferisco utilizzare questa struttura. Per provare il nostro componente possiamo registrarlo in Delphi scegliendo dal menu Component la voce "Install Component", il componente verr installato nella libreria dei componenti e la sua icona posta nella pagina della Components Palette da noi scelta, oppure aggiungere la unit MyEdit nella clausola uses di una nuova form e creare il componente a runtime come segue

procedure TForm1.FormCreate(Sender: TObject); begin MyEdit := TMyEdit.Create(Self); MyEdit.Parent := Self; // MyEdit.Top := 16; //Posizionamento del componente nella form MyEdit.Left := 10; // " " " " " end; e nel evento OnDestroy della form aggiungiamo il seguente codice

procedure TForm1.FormDestroy(Sender: TObject); begin MyEdit.Free; end; per eliminare il componente da noi creato quando eliminiamo la form. Non dimenticatevi di inserire una variabile MyEdit di tipo TMyEdit nella sezione Private della classe TForm1

... private { Private declarations } MyEdit : TMyEdit; public { Public declarations } end; Potete scaricare il codice completo dell'esempio riportato per vedere come funziona ed avere il codice a disposizione. Per quanto riguarda la creazione di componenti, molto difficile che si creino componenti partendo completamente da zero, salvo in casi molto particolari. Generalmente si eredita dalla classe TComponent per i componenti non visibili e da TControl o da TWinControl per i componenti visibili e da TGraphicControl per i componenti grafici. Possiamo dire che queste sono le classi utili pi "primitive" da cui iniziare a creare nuovi componenti. La scrittura di codice per componenti richiede una discreta conoscenza della programmazione ad oggetti in Object Pascal, delle classi da cui ereditare e delle basi del sistema operativo, in questo caso Windows. Pertanto si rimanda alla lettura dei manuali e alla lettura di testi specifici, come la serie Mastering Delphi di Marco Cant, in particolare "Delphi Developer's Handbook" e "Laboratorio di Delphi" di Nando Dessena e Barbini, nonch frequentare i news group che trattano di Delphi frequentati anche dai personaggi citati.

LEZIONE 36:

DLL: cosa sono, a cosa servono, come crearle e/o utilizzarle

DLL l'abbrevizione di Dynamic Link Library (Libreria a Collegamento Dinamico). Le DLL sono delle raccolte di routines che possono essere chiamate da pi programmi e solitamente contengono codice di utilit generale che viene messo a disposizione di pi applicazioni. Ci permette di non inserire codice duplicato in applicazioni differenti. Si possono assimilare le DLL alle unit di Delphi solamente che le prime contengono codice eseguibile, collegabile in fase di esecuzione. In Delphi esistono diversi modi di richiamare funzioni contenute in una DLL: possibile utilizzare la parola chiave External dopo la dichiarazione del nome della funzione da importare, seguita dal nome del file della DLL che la contiene. Non necessario che la DLL sia presente in fase di compilazione, ma se questa non sar presente durante l'esecuzione dell'applicazione si otterr un errore. Un esempio di procedura importata da una DLL il seguente

Procedure DLLProcedure1; external 'MYDLL.DLL'; Un'altro modo di richiamare una funzione o procedura contenuta in una DLL caricando in fase di esecuzione la libreria con l'API di windows LoadLibrary, di recuperare l'indirizzo della procedura o funzione con l'API GetProcAddress, chiamare la procedura attraverso l'indirizzo ottenuto ed infine, quando non occorre pi, scaricare la libreria con l'API FreeLibrary. In questo secondo caso anche necessario dichiarare una variabile procedura del tipo corrispondente alla procedura o funzione da richiamare. Se nell'esempio precedente la nostra procedura avesse avuto una dichiarazione del tipo

Procedure DLLProcedure1(Param1, Param2 : Integer); la variabile procedura avrebbe dovuto essere del tipo Type TDLLProcedure1 = procedure(Param1, Param2 : Integer); Var DLLProcedure1 : TDLLProcedure1; A questa variabile deve essere assegnato il valore restituito dalla funzione GetProcAddress nella seguente maniera

... @DLLProcedure1 := GetProcAddress(Handle, 'DLLProcedure1'); ... dove Handle l'handle della libreria restituito dalla chiamata a LoadLibrary. Si consiglia di fare riferimento all'SDK di Windows per le API sopracitate. La scrittura di una DLL in Delphi non differisce molto dalla scrittura di una normale applicazione. La differenza sostanziale sta nella parola chiave Library che sostituisce Program nell'intestazione della unit. In una DLL le procedure e funzioni possono essere contenute nel file di progetto oppure contenute in unit esterne e la libreria si riduce ad una clausola uses contente le unit con le funzioni e procedure seguita da una sezione Exports con l'elenco delle funzioni o procedure da esportare. Il blocco principale Begin..End di una libreria rappresenta il blocco di codice che viene eseguito come codice di inizializzazione della libreria e viene eseguito ogni qual volta la libreria viene caricata in memoria. Se si vogliono rendere disponibili le routines contenute in una DLL scritta in Delphi anche ad altri linguaggi di programmazione, occorre specificare la direttiva stdcall poich non tutti i linguaggi supportano la modalit di chiamata ottimizzata "register" utilizzata dall'Object Pascal.

La sezione Exports di una DLL ha la seguente struttura

exports entry1, entry2, ..., entryn; Ogni entry costituita dal nome della routine da esportare (funzione o procedura) seguita da due specificatori opzionali: index e name. Il primo serve a definire un indice numerico per richiamare la routine esportata e pu assumere valori tra 1 e 2.147.483.647. Se per una routine non viene specificato un valore per index gli viene assegnato automaticamente un numero tratto dalla tabella di esportazione della DLL. Lo specificatore name seguito da una costante stringa e specifica il nome con cui verr esportata la routine. Se non viene assegnato ad una routine uno specificatore name, questa verr esportata con il nome con cui stata dichiarata nella DLL, rispettando anche le maiuscole/minuscole. Tornando alla nostra procedura di esempio, potremmo avere una sezione exports di questo genere

exports DLLProcedure1 index 1 name 'DLLProcedure1'; che equivalente a

exports DLLProcedure1; se vogliamo esportare la routine con un altro nome

exports DLLProcedure1 index 1 name 'DLLProcedure'; Delphi ci mette a disposizione alcune variabili globali per semplificare la gestione delle DLL. Abbiamo visto in precedenza che il codice contenuto nel blocco principale della libreria viene utilizzato come codice di inizializzazione della libreria stessa, ma esiste un codice di pulizia da eseguire al rilascio della libreria? Esiste. Si pu fornire l'indirizzo di una procedura che si occuper di eseguire il codice di pulizia tramite la variabile ExitProc. Questa viene solitamente impostata in fase di inizializzazione della DLL. Quando la libreria viene rilasciata, viene eseguito automaticamente il codice contenuto nella procedura all'indirizzo contenuto in ExitProc. Occorre per salvare il vecchio puntatore alla procedura di pulizia prima di impostare una nuova procedura e ripristinarlo prima che la nuova procedura termini l'esecuzione. Nella unit System presente una variabile che permette di determinare se il codice in esecuzione appartiene ad una DLL oppure ad un'applicazione: IsLibrary. Questa variabile assume valore false quando si in presenza di un'applicazione e true altrimenti. Nella scrittura delle DLL bisogna fare attenzione ai parametri passati e restituiti alle/dalle routines. Se si utilizzano stringhe di tipo long o array dinamici, record od oggetti, occorre che tutti i moduli che fanno uso delle routines e le DLLs che esportano le routines in oggetto utilizzino la unit ShareMem, dichiarandola come prima unit nella clausola uses. Questa una interfaccia per il gestore di memoria BORLANDMM.DLL che permette ai moduli di condividere la memoria allocata in modo dinamico. LEZIONE 36:

I Packages: cosa sono, a cosa servono, come crearli e/o

utilizzarli

I Packages sono particolari tipi di librerie a caricamento dinamico che possono essere utilizzate solamente da applicazioni Delphi e dall'IDE di Delphi stesso. Questo nuovo tipo di libreria stato introdotto con Delphi 3 e lo scopo principale quello di creare librerie di componenti riutilizzabili da pi applicazioni. Essi possono essere utilizzati per linkare il codice in essi contenuto

sia a design-time che a run-time. I Packages possono essere di tre differenti tipi: Design-Time, Run-Time, Design-Time and Run-Time contemporaneamente. I primi sono disponibili solamente in fase di progettazione ed installabili nell'IDE di Delphi, i secondi vengono distribuiti con le applicazioni e non possono essere installati nell'IDE, gli ultimi sono un misto dei primi due. L'estensione per il codice sorgente dei packages .DPK, mentre per la versione compilata sar .BPL (Borland Package Library). Esiste anche la possibilit di creare librerie di packages utili per passare o condividere componenti con altri sviluppatori in uno stesso team. In questo caso avremo un file di libreria con estensione .DPL. Questo tipo di libreria pu essere creato attraverso l'apposito editor; il Package collection Editor. Per creare un nuovo package si pu come sempre ricorrere al wizard "New" dal menu File dell'IDE. Come per le librerie DLL, anche per i packages esiste una parola chiave che si trova all'inizio del file sorgente principale e che ne identifica il tipo: Package. Nel file sorgente principale di un package sono presenti due clausole aggiuntive, non presenti nei files sorgente delle DLL; Requires e Contains. Queste due clausole identificano rispettivamente altri packages esterni che sono richiesti dal package corrente per il suo corretto funzionamento e le unit contenute all'interno del package stesso. LEZIONE 36:

Delphi & Web

Il corso sta volgendo al termine. Prima di lasciarci, vorrei, a completamento, mostrare alcune funzionalit di Delphi che permettono la realizzazione di applicazioni indirizzate al web, alla produzione di pagine HTML e che permettono addirittura di creare componenti ASP da inserire nei siti web oppure per creare siti completamente basati su Delphi. Con Delphi possibile realizzare applicazioni CGI oppure ISAPI. Il tutto ovviamente semplicissimo come del resto qualsiasi operazione in Delphi!?! Esistono anche qui dei template da utilizzare per la creazione di CGI, ISAPI e componenti ASP; basta effettuare la scelta giusta nel wizard "New" dal menu "File". Esistono anche dei componenti chiamati "HTML Producer Components" che permettono di creare pagine HTML. Questi componenti utilizzano come schema per le pagine da costruire dei template HTML e ricavano i dati per completarli o direttamente dal codice oppure da tabelle di database o query. Questi componenti si trovano nella pagina Internet della Component Palette e sono: PageProducer, DataSetPageProducer, DataSetTableProducer, QueryTableProducer.

I Componenti per la produzione di pagine HTML


Nella pagina Internet della Component Palette oltre ad altri componenti che riguardano internet (WebDispatcher, WebBrowser) compaiono i componenti per la produzione di pagine Web: PageProducer, DataSetPageProducer, DataSetTableProducer, QueryTableProducer. La tecnica utilizzata da questi componenti per creare pagine HTML quella di utilizzare un normale file HTML, creato con un qualsiasi editor HTML, contenente alcuni tag speciali che hanno lo scopo di fare da segnaposto per l'output dei dati che l'applicazione dovr inserire nel documento. Il formato di questi tags <#NomeTag>. Il componente p semplice, il PageProducer. Questo analizza il documento HTML che gli viene fornito come template e genera un evento OnTag per ogni tag speciale incontrato in esso. Nell'event handler del evento OnTag deve essere inserito il codice che reagir al particolare tag. Per esempio se nel nostro documento HTML abbiamo inserito il tag speciale <#Data>, quando il componente analizzer il documento generer l'evento OnTag. Il tipo dell'event handler per questo evento il seguente

Procedure (Sender : TObject; Tag : TTag; const TagString : String; TagParams : TStrings; var ReplaceText : String); Supponiamo di aver creato il seguente event handler

procedure TForm1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag; const TagString: String; TagParams: TStrings; var ReplaceText: String); begin If TagString = 'Data' then ReplaceText := DateToStr(Now); end; Il codice dell'event handler sostituir il il tag speciale con la data corrente. Supponendo che il nostro documento avesse avuto la seguente struttura

<HTML> <HEAD> <Title>Prova componente Page Producer</Title> </HEAD> <BODY> <H2>Data corrente</H2> La data corrente <#Data> </BODY> </HTML> il risultato sarebbe

Data Corrente
La data corrente 17/08/2001 Il risultato precedente prelevabile dalla propriet Content del componente (di tipo TStrings). Esso pu essere copiato in un componente memo per la visualizzazione oppure salvato su disco con la propriet SaveToFile della classe TStrings. L'esempio riportato semplicissimo, ma possibile realizzare pagine web complesse attingendo da databases o altre fonti.

DataSetTableProducer, QueryTableProducer
A proposito di databases, Delphi dispone gi di un componente specializzato nella composizione di pagine HTML attingendo da fonti dati quali databases. Questo componente DataSetPageProducer. L'utilizzo di questo componente semplicissimo e molto simile al PageProducer che abbiamo visto sopra. In questro caso abbiamo una propriet in pi che quella che definisce il collegamento con il dataset contenente i dati. Basta fornire il solito documento HTML come template e disporre dove necessario i tags speciali corrispondenti ai nomi dei campi del dataset collegato al componente stesso. In questo modo il componente recupera automaticamente il contenuto dei campi e lo inserisce nel documento. Noi non dobbiamo fare nulla se non gestire altri tags speciali, come abbiamo fatto nell'esempio precedente, per inserire valori non provenienti dal dataset attraverso il gestore dell'evento OnTag. Un altro componente per la produzione di codice HTML il DatasetTableProducer che ha lo scopo di creare tabelle in standard HTML prelevando il contenuto da un dataset. Dispone quindi della propriet Dataset per il collegamento alla sorgente di dati. Questo componente dotato anche di un "Property Editor" per la propriet Columns. Attraverso di esso possibile impostare a design-time le caratteristiche delle colonne ed i campi del dataset ad esse associati, producendo anche una anteprima del risultato finale. Il DatasetTableProducer

prevede anche la possibilit di definire un header ed un footer (una intestazione tabella ed un pie' tabella). Tramite le propriet Hader ed Footer del componente possibile inserire, ad esempio, il titolo del documento. Il contenuto della propriet Header e Footer deve essere in formato HTML. Percui, se volessimo definire il titolo del documento, dovremmo scrivere nella propriet Header il seguente testo:

<H1>Titolo</H1> Lo stesso dicasi per la propriet Footer. Nel componente sono previste anche propriet per impostare le caratteristiche della tabella quali il colore di sfondo, il cellpadding (la distanza del contenuto della cella dal bordo cella), il cellspacing (lo spazio tra una cella e l'altra), la presenza o no del bordo e la dimensione, e le caratteristiche delle righe che comporranno la tabella. Tutte questa impostazioni sono raccolte in due propriet, RowsAttributes e TableAttributes. Il componente DatasetTableProducer espone anche degli eventi che permettono un maggiore adattamento alle proprie necessit. In particolare l'evento OnFormatCell che viene attivato al momento della formattazione della singola cella. Definire un gestore per questo evento permette di modificare per ogni singola cella i valori di colore di sfondo, l'allineamento verticale od orizzontale, e perfino il contenuto della cella stessa. In maniera del tutto simile funziona il componente QueryTableProducer tranne per il fatto che questo viene collegato ad un componente Query e che il suo scopo quello di generare pagine HTML basate su query parametrizzate i cui parametri vengono ottenuti da una richiesta HTML. La modalit di richiesta pu essere GET o POST.

Delphi e HTML dinamico


Quanto visto sopra applicabile ad applicazioni che vogliono fornire una versione dei dati in formato HTML (formato multipiattaforma) o per applicazioni che realizzano server web o servizi che rispondono come server web. Ci sono altri sistemi che possono essere utilizzati per fornire informazioni in formato HTML. Chi realizza siti web conosce i CGI (Common Gateway Interface). Questi vengono utilizzati per fornire pagine HTML dinamiche ovvero il cui contenuto vari in corrispondenza del variare dei potenziali dati da visualizzare. Esempi di questo tipo di documenti potrebbero essere pagine che forniscono notizie dell'ultima ora, estrazione di dati data database che vengono aggiornati frequentemente (annunci di compra/vendita). Quello che il browser riceve sempre un documento HTML che altro non che il prodotto di una elaborazione effettuata dal server web al momento della richiesta compilando la pagina con i dati richiesti e disponibili in quel momento. Un CGI altro non che una applicazione eseguibile che risponde alle richieste HTML effettuate dai browser; esso analizza le richieste in arrivo e restituisce dei documenti HTML compilati in base alle richieste ricevute. Esistono due tipi di CGI; i CGI e i WINCGI. I primi ricevono i parametri di richiesta direttamente dalla riga di comando mentre i secondi li estraggono da un file .INI che viene passato loro come parametro nella riga di comando e utilizzano come input ed output speciali files. Questo secondo tipo di CGI stato realizzato per venire incontro agli sviluppatori Visual Basic permettendo di scavalcare alcune limitazioni del linguaggio. Neanche a farlo a posta Delphi ci permette di realizzare questo tipo di applicazioni partendo dalla considerazione che essi non sono altro che applicazioni console che utilizzano lo standard input e lo standard output per acquisire le richieste e fornire i risultati dell'elaborazione. Tutto ci si traduce in una serie di Writeln che restituiscono codice HTML come ad esempio la seguente riga

Writeln('<H1>Titolo</H1>'); Se eseguiamo il CGI da una finestra di terminale vedremo esattamente ci che riportato come parametro nei comandi Writeln. Oltre ai CGI in Delphi possibile creare delle DLL ISAPI (Intrenet Server API) o NSAPI (Netscape Server API). Il primo tipo stato introdotto da Microsoft, mentre il secondo dall Netscape. Questa tecnologia pu essere vista come un miglioramento del CGI. Essa si differenzia dal CGI per le modalit di caricamento nella memoria del server e per le modalit di risposta. Mentre il CGI viene caricato ad ogni richiesta, per questo tipo di DLL le

cose cambiano. La DLL viene carica al momento della richiesta, se essa non gi stata caricata in precedenza, nello stesso spazio di indirizzamento del server web ed capace di rispondere a pi richieste facendo uso di threads. Ci permette di non dover caricare tanti eseguibili in memoria quante sono le richieste fatte al server web come invece accade per i CGI. Ogni thread attivato dal server web gestisce una richiesta e la relativa risposta. C' differenza anche nella velocit di risposta rispetto al CGI poich nel caso delle DLL ISAPI/NSAPI lo scambio dei dati avviene tutto in memoria. Non mi dilungher oltre su questo argomento. Diamo uno sguardo a cosa mette a disposizione Delphi per aiutarci nella realizzazione di questo tipo di software. Delphi 5 include, nelle versioni Professional e Enterprise, la tecnologia WebBroker che integra nella VCL delle classi che semplificano la realizzazione di applicazioni web lato server. Per utilizzare il framework WebBroker, necessario scegliere dal menu file la voce New e nella prima pagina della finestra che compare scegliere "Web Server Application". Ci provocher la visualizzazione di una finestra di scelta del tipo di applicazione che si ha intenzione di realizzare. I tipi di applicazioni tra cui si pu scegliere sono tre: DLL ISAPI/NSAPI, eseguibile CGI , eseguibile WinCGI. Nel caso si scelga si realizzare DLL ISAPI/NSAPI, Delphi produrr un file progetto di libreria del tutto simile ad una normale libreria ma con delle particolarit. Per prima cosa vediamo comparire nella clausola uses del file progetto le unit WebBroker, ISAPIApp ed una nuova unit che contiene un oggetto WebModule. Nello stesso tempo dalla libreria vengono esportate delle procedure standard per il caricamento e scaricamento della libreria nella/dalla memoria del server web. Da notare che in questo tipo di DLL, l'oggetto application che viene creato non di tipo standard, non corrisponde infatti agli altri oggetti application creati nelle normali applicazioni. Nelle applicazioni ISAPI/NSAPI, l'oggetto application deriva da una nuova classe che pu essere TISAPIApplication o TCGIApplication. Queste ultime derivano entrambe da un'altra classe che TWebApplication. Sono delle classi specializzate per la realizzazione di applicazioni per il web. Il tipo dell'oggetto applicazione pu essere TISAPIApplication o TCGIApplication in base alla scelta che abbiamo effettuato nel wizard del Object Repository. La caratteristica pi importante che l'intera applicazione ruota attorno alla nuova unit contenente l'oggetto WebModule. Un WebModule molto simile ad un DataModule standard, con ovviamente delle caratterizzazioni specifiche per le applicazioni web. Il WebModule ha lo scopo di raccogliere tutti gli oggetti non visuali dell'applicazione e di permettere ad essa di rispondere ai messaggi di richiesta HTTP passando gli oggetti Request e Response alle azioni appropriate. Tra le propriet dell'oggetto WebModule compare la propriet Actions. Essa contiene l'elenco delle azioni che devono essere eseguite dall'applicazione in risposta ad un determinato switch che viene passato alla DLL. Lo switch che identifica l'azione contenuto nella propriet PathInfo dell'oggetto WebAction stesso. Un'altra propriet interessante dell'oggetto WebAction Producer che permette di collegare all'azione direttamente un oggetto di tipo TPageProducer, TDataSetPageProducer, TDatasetTableProducer, TQueryTableProducer (che abbiamo gi visto) che verr richiamato all'attivarsi dell'azione stessa. Un'altra tecnologia che permette di realizzare pagine HTML dinamiche, la tecnologia ASP (Active Server Pages) introdotta da Microsoft. Essa si basa su codice script, inserito all'interno di documenti HTML, che viene eseguito direttamente nel server e che produce pagine HTML standard in risposta alle richieste dei clients. Questa tecnologia prevede anche un estensione delle proprie funzioni attraverso la realizzazione di oggetti COM che possono essere richiamati direttamente dal codice script. Anche in questo caso Delphi ci viene incontro fornendoci la struttura base per la realizzazione di questo tipo di componenti. Nel wizard che porta alla realizzazione del codice base per la realizzazione di oggetti ASP possiamo impostare tutti i parametri di funzionamento dell'oggetto COM, come il nome della classe, il tipo di interfaccia ed il tipo di threading dell'oggetto, il tipo di oggetto. Per creare la struttura base di un oggetto ASP, possiamo scegliere dal menu File -> New, nella pagina ActiveX dell'Object Repository la voce Active Server Object. ATTENZIONE! Per poter creare un oggetto ActiveX bisogna disporre di un progetto attivo, nel nostro caso potremmo creare un progetto di tipo "ActiveX Library", sempre dall'Object Repository, e quindi seguire la procedura indicata precedentemente. Il wizard ActiveX Server Object presenter una finestra per l'inserimento delle caratteristiche principali dell'oggetto ActiveX come riportato nell'immagine seguente

Fig. 1

CoClass Name indica il nome dell'oggetto ActiveX che si sta creando a cui Delphi anteporr un T; per cui se inseriamo in questa casella "ProvaASP" il nome della classe che sar "TProvaASP". Instancing definisce la modalit di istanziazione dell'oggetto ovvero Internal, Single Instance, Multiple Instance. Nel primo caso l'oggetto sar creato come interno e nessun'altra applicazione potr creare un'altra istanza dello stesso. Single Instace permette di creare una singola interfaccia per l'oggetto COM per ciascun eseguibile. Multiple Instance permette invece a pi applicazioni di collegarsi all'oggetto. Threading Model specifica invece la modalit di gestione dei thread dell'oggetto COM. L'impostazione Single indica che non vi nessun supporto per i thread e che tutte le richieste dei clients vengono gestite in maniera seriale. Apartment permette di accedere alle propriet ed ai metodi dell'oggetto solamente attraverso il thread in cui stato creato l'oggetto stesso. Free le propriet ed i metodi possono essere chiamati in qualsiasi momento da qualsiasi thread. La modalit Both indica che l'oggetto in questione pu supportare il metodo Apartment e Free. Active Server Type definisce la "compatibilit" con il server web specifico: IIS3 o 4 per l'opzione Page-level event methods e IIS 5 per Object Context. In pratica, nel primo caso vengono generati all'interno dell'oggetto due eventi OnPageStart, OnPageEnd che vengono richiamati dal server web rispettivamente all'inizializzazione della pagina e al termine dell'elaborazione. Nel secondo caso vengono utilizzate le funzionalit di MTS per recuperare i dati di istanza corretti dell'oggetto. Nel riquadro Options, se viene spuntata la voce "Generate a template test script for this object", verr cerata una pagina asp che istanzier e utilizzer l'oggetto creato ed ha solamente scopo di test. Una volta creata la struttura base del nostro oggetto ASP, possibile accedere direttamente agli oggetti Application, Response, Request, Session di ASP. Non resta che implementare il codice specifico. Vediamo un semplicissimo esempio. A questo punto proviamo ad aggiungere un semplice metodo al nostro oggetto. Esso avr solamento lo scopo di restituire nel flusso HTML di risposta del codice HTML. Aggiungiamo alla nostra alla classe appena creata una procedura cos definita

procedure ShowHelloASP; safecall; ed implementata nel seguente modo

procedure TTelpressNewsService.ShowNews; begin Response.Write('<h1>Prova oggetto ASP in Delphi</h1><br>');

Response.Write('<B>Hello ASP!!!</B>'); end; Ora dovremo inserire una chiamata al metodo del nostro oggetto dalla pagina asp di test (magari creata automaticamente da Delphi) ottenendo quanto segue

<HTML> <BODY> <TITLE> Testing Delphi ASP </TITLE> <CENTER> <H3> You should see the results of your Delphi Active Server method below </H3> </CENTER> <HR> <% Set DelphiASPObj = Server.CreateObject("TNSW.TelpressNewsService") DelphiASPObj.ShowHelloASP %> <HR> </BODY> </HTML>

Questo solo un esempio, ma in programmazione (e soprattutto in Delphi) i limiti sono quelli posti dalla nostra immaginazione!!!!!!!!!!! P.S. Ovviamente per approfondimenti sull'argomento trattato, consultate il manuale del linguaggio, la guida in linea e se ne avete la possibilit "Mastering Delphi 5" di Marco Cant. LEZIONE 36:

Risorse utili e bibliografia

Risorse Utili Borland - sito ufficiale Marco Cant, guru italiano del Delphi - articoli, news groups, link, corsi Le pagine di Torry - Articoli, tips, componenti Project Jedi - The Delphi API library Delphi Power Page - Componenti, Informazioni Delphi Pages - Componenti, tips, articoli Delphi Super Page - Componenti per Delphi DelphiSeek - Motore di ricerca per risorse Delphi Dr. Bob's Delphi Clinic FreeVCS - Free Version Control System GExperts - Utility aggiuntive per l'IDE di Delphi FuzzyReport - Tool per la creazione di reports FREE!!! Bibliografia Guida al linguaggio Object Pascal, manuali Delphi 5 (Borland) Guida alla programmazione, manuali Delphi 5 (Borland) Programmare con Delphi 4 (1999 Apogeo, Marco Cant) Programmare con Delphi 5 (2000 Apogeo, Marco Cant) Delphi Developer's Handbook (1998 Sybex, Marco Cant, Tim Gooch, John F. Lam) LEZIONE 37

Componenti per la manipolazione dei dati (Data-Aware)

Dopo aver trattato i componenti per l'accesso alle sorgenti di dati, passiamo ora a vedere i componenti che ci permettono di interagire in maniera visuale con i dati provenienti da queste sorgenti dati. Come visibile nell'immagine qui sotto, questi componenti sono raccolti nella pagina "Data Controls" della Component Palette.

Questa pagina ci mette a disposizione una griglia, una barra per la navigazione all'interno dei nostri datasets, una etichetta collegata direttamente ad un campo di un dataset, una casella di edit, un memo, un componente immagine da collegare direttamente al campo del nostro dataset per visualizzare l'immagine in esso contenuta, una casella combinata, una lista, una casella spuntabile per valori booleani, un gruppo di componenti per selezioni esclusive, una casella di lookup, una lista di lookup, un componente per formato RTF, una griglia di controllo, una componente per la visualizzazione dei dati in forma grafica (Torte, Istogrammi, ...). Si tratta di componenti Data-Aware, ovvero componenti che si collegano direttamente alla sorgente di dati e che automaticamente gestiscono la visualizzazione, la modifica. Questi componenti vengono collegati al dataset indirettamente, ovvero vengono collegati ad un datasource che fa riferimento al dataset interessato. Come per gli oggetti per l'accesso alle sorgenti di dati, daremo uno sguardo alle propriet e metodi principali e pi utilizzati di questi componenti. Segue un elenco dei nomi dei componenti e le loro rispettive classi: DBGrid (TDBGrid), DBNavigator (TDBNavigator), DBLabel (TDBLabel), DBEdit (TDBEdit), DBMemo (TDBMemo), DBListBox (TDBListBox), DBComboBox (TDBComboBox), DBCheckBox (TDBCheckBox), DBRadioGroup (TDBRadioGroup), DBLookupListBox (TDBLookupListBox), DBLookupComboBox (TDBLookupComboBox), DBRichEdit (TDBRichEdit), DBCtrlGrid (TDBCtrlGrid), DBChart (TDBChart).

TDBGrid
L'oggetto DBGrid permette di visualizzare e manipolare i records di un dataset in una griglia tipo quella utilizzata da programmi come MS Excel. Questo oggetto deriva da TCustomDBGrid che a sua volta deriva da TCustomGrid. Tratteremo solamente le propriet ed i metodi relativi all'oggetto TCustomDBGrid poich la classe TDBGrid non aggiunge alcun metodo o propriet, ma espone solamente i metodi della classe da cui deriva, appunto TCustomDBGrid. Ecco l'elenco delle propriet dell'oggetto DBGrid: Columns, DataSource, DefaultDrawing, FieldCount, Fields, Options, ReadOnly, SelectedRows, TitleFont, BorderStyle, FixedColor. La propriet Columns, di tipo TDBGridColumns, permette di definire le colonne che verranno visualizzate nella griglia. Questa propriet un elenco di oggetti TColumn che definiscono le propriet delle varie colonne che andranno a formare la tabella, a partire dal campo del dataset a cui si riferisce la colonna, al colore del titolo, l'allineamento del testo etc... Per modificare la propriet Columns, disponibile un editor di propriet, avviabile, come sempre, attraverso il bottone con i tre puntini nel Object Inspector. Se non vengono definite delle colonne per la griglia, verranno automaticamente visualizzate tante colonne quanti sono i campi visibili del dataset. La propriet DataSource quella che permette di impostare il riferimento all'oggetto DataSource che a sua volta si riferisce al dataset (Table, Query). DefaultDrawing indica se le celle della griglia verranno disegnate automaticamente oppure il loro disegno verr personalizzato attraverso i gestori di eventi OnDrawColumnCell o OnDrawDataCell. Se impostato su True l'oggetto griglia si occuper direttamente del disegno dei dati nella cella, dal colore di sfondo, al font, al valore dei dati, se impostato su false, verr gestito dall'oggetto griglia solamente il disegno dello sfondo, il disegno dei valori dei dati demandato ai gestori di eventi OnDrawColumnCell o OnDrawDataCell. FieldCount una propriet che indica il nome dei campi, ovvero delle colonne, visualizzate nella griglia. La propriet Fields permette di accedere in forma indicizzata i campi corrispondenti alle colonne visualizzate, come per la propriet Fields dell'oggetto Table. Attraverso la propriet Options possibile definire alcune caratteristiche funzionali e grafiche della griglia. Alcune di esse sono la possibilit di editare il contenuto della griglia, la visualizzazione di un indicatore di riga, la visualizzazione dei titoli al disopra delle colonne, la possibilit di selezionare un'intera riga della griglia, effettuare una selezione su pi righe, richiedere la conferma prima di eliminare un record dal dataset. I valori disponibili per la propriet sono: dgEditing, dgAlwaysShowEditor, dgAlwaysShowEditor, dgRowSelect, dgTitles, dgIndicator, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgRowSelect, dgAlwaysShowSelection, dgConfirmDelete, dgCancelOnExit, dgMultiSelect. La propriet ReadOnly, autoesplicativa, ovvero permette di impostare in sola lettura il contenuto della griglia cos da impedirne la modifica. SelectedRows, riporta un elenco di Bookmark (Segnalibri che vengono utilizzati per segnare una posizione come selezionata all'interno della griglia) corrispondenti alle posizioni selezionate nella griglia. La propriet TitleFont permette di impostare il font per le celle contenenti i titoli delle colonne. Attraverso la propriet BorderStyle, si pu definire il tipo di bordo disegnato intorno alla griglia. In ultima, la propriet FixedColor definisce il colore delle celle della griglia che rimangono fisse durante lo scorrimento della stessa.

Dopo aver visto le propriet passiamo ora ai metodi. Eccone l'elenco: DefaultDrawColumnCell, DefaultDrawDataCell. DefaultDrawColumnCell viene utilizzato all'interno di un gestore di evento OnDrawColumnCell per il disegno del testo. Il risultato della chiamata a DeafulDrawColumnCell lo stesso che impostare la propriet DefaultDrawing a True, tranne per il fatto che che non viene disegnato il rettangolo di fuoco della cella. DefaultDrawDataCell l'equivalente di DefaultDrawColumnCell tranne per il fatto che in questo non vi un riferimento alla colonna a cui appartiene la cella.

TDBNavigator
Il componente DBNavigator, un componente visuale che permette di facilitare lo spostamento all'interno di dataset mettendo a disposizione una barra contenente dei pulsanti corrispondenti alle tipiche funzioni di spostamento. Ci si pu spostare in avanti, in dietro, alla prima posizione, all'ultima, aggiungere una nuova posizione, eliminare quella corrente, annullare le modifiche apportate ai dati. Le propriet di DBNavigator sono accessibili quasi completamente dall'Object Inspector a design-time e sono: ConfirmDelete, DataSource, Flat, Hints, VisibleButtons. ConfirmDelete permette di specificare se verr richiesta la conferma per l'eliminazione del record oppure no. Impostando questa propriet a true, verr presentata una finestra di dialogo attraverso la quale sar possibile decidere se continuare con l'eliminazione oppure annullare l'operazione. DataSource la propriet che serve ad indicare quale il dataset a cui fa riferimento l'oggetto DBNavigator. La propriet Flat ha lo scopo di definire l'aspetto dei pulsanti della barra di navigazione, ovvero se questi avranno uno stile piatto oppure tradizionale. Attraverso la propriet Hints si pu definire il contenuto dei tooltip che vengono visualizzati quando ci si ferma sopra i pulsanti della barra. Questa propriet potrebbe essere utilizzata per esempio per visualizzare in italiano la funzione dei vari pulsanti della barra visto che di default il testo visualizzato in inglese. L'ultima propriet quella che definisce quali pulsanti visualizzare nella barra. Non sempre sono necessari tutti i pulsanti presenti nella barra, ad esempio potrebbero servire solamente i pulsanti di spostamento e non quelli di inserimento, modifica ed eliminazione. Tramite VisibleButtons, di tipo TButtonSet, possibile specificare l'elenco dei pulsanti da visualizzare. Per quanto riguarda i metodi dell'oggetto DBNavigator, interessante il metodo BtnClick che permette di eseguire la funzione associata al pulsante nella barra di navigazione, come se il pulsante fosse stato premuto dall'utente. Questo metodo accetta come parametro il tipo di pulsante che di cui si vuole simulare la pressione (nbFirst, nbPrior, nbNext, nbLast, nbInsert, nbDelete, nbEdit, nbPost, nbCancel, nbRefresh). L'esecuzione del metodo attiva comunque l'evento OnClick dell'oggetto DBNavigator.

TDBText
L'oggetto DBText un oggetto visuale che permette di visualizzare il contenuto testuale di un campo di un dataset. Attraverso questo oggetto l'utente nonpu modificare il contenuto del campo visualizzato. Le propriet dell'oggetto DBText sono: AutoSize, DataField, DataSource. La propriet AutoSize dell'oggetto DBText indica se la dimensione dell'oggetto viene modificata automaticamente rispecchiando la dimensione del testo da visualizzare. Impostare AutoSize a false a volte utile per impedire che il testo da visualizzare vada a sovrapporsi ad altri controlli nelle schermate del programma. DataField contiene il nome del campo del dataset di cui verra visualizzato il contenuto. DataSource conterr il riferimento all'oggetto DataSource a cui collegato il dataset a cui collegare l'oggetto DBText.

TDBEdit
TDBEdit un oggetto visuale rappresentante una casella di testo dove possibile visualizzare, inserire, modificare del testo contenuto in un campo di un dataset. Questo oggetto deriva da altri oggetti non connessi direttamente a datasets come CustomMaskEdit e CustomEdit. Per le propriet ereditate da questi oggetti si rimanda alla guida in linea.

Come per altri oggetti connessi ai dataset, le propriet pi importanti sono DataField, DataSource e ReadOnly. Per le prime due il significato lo stesso degli oggetti visti in precedenza, ReadOnly presente, come nel nostro caso, in quei componenti che permettono la modifica dei dati. Attraverso questa propriet, impostando il suo valore a true, si impedisce la modifica dei dati visualizzati nel solo componente DBEdit interessato.

TDBMemo
Il componente DBMemo simile al componente DBEdit, ha la funzione di visualizzare il testo contenuto in un campo di un dataset con la differenza che il testo viene visualizzato su pi righe. Le propriet fondamentali sono le stesse dell'oggetto DBEdit, mentre per le propriet relative all'oggetto TCustomMemo, da cui DBMemo deriva, consultare la guida in linea.

TDBImage
L'oggetto DBImage viene utilizzato per visualizzare ed inserire immagini in un campo di un dataset. Solitamente questo oggetto viene collegato a campi di tipo blob. Oltre alle propriet comuni a tutti i componenti data-aware, l'oggetto DBImage possiede le seguenti propriet: AutoDisplay, BorderStyle, Center, Picture, QuickDraw, Stretch. La propriet AutoDisplay, indica se l'immagine contenuta nel campo viene automaticamente caricata e visualizzata. Impostando questa propriet a false, per visualizzare l'immagine sar necessario effettuare un doppio click sull'oggetto oppure selezionare l'oggetto e premere enter. Attraverso BorderStyle possibile definire l'aspetto del bordo dell'immagine che pu essere: bsNone, bsSingle, bsSizeable, bsDialog, bsToolWindow, bsSizeToolWin. La propriet Center indica se l'immagine verr visualizzata al centro dell'oggetto DBImage. Per ovvi motivi, questa propriet non avr alcun effetto se la propriet Stretch sar impostata a true. La propriet Picture contiene un riferimento all'immagine contenuta nel campo del dataset. Questa propriet risulta molto utile per caricare una immagine nel campo blob oppure per salvare su file il contenuto del campo blob stesso. Per maggiori informazioni sui metodi e sulle propriet dell'oggetto Picture consultare la guida in linea riguardo alla classe TPicture. Attraverso QuickDraw, possibile velocizzare la visualizzazione dell'immagine, disabilitando l'utilizzo di una palette di colori, ottenendo per una qualit inferiore dell'immagine. Impostando questa propriet a false, verr utilizzata una palette di colori per permettere una visualizzazione migliore dell'immagine, introducendo dei ritardi di visualizzazione causati dall'elaborazione pi complessa. La propriet Stretch permette di indicare se l'immagine visualizzata verr riadattata alle dimensioni dell'oggetto DBImage effettuando appunto un stretching, con conseguente probabile deformazione o perdita di definizione dell'immagine. I metodi dell'oggetto DBImage sono: CopyToClipBoard, CutToClipBoard, LoadPicture, PasteFromClipBoard. I metodi relativi alla clipboard (gli appunti di Windows) sono abbastanza autoesplicativi e servono appunto a copiare ed a tagliare il contenuto mettendolo nella clipboard o ad incollarlo prelevandolo da essa. LoadPicture ha lo scopo di caricare l'immagine dal campo del dataset. Questo metodo necessario per visualizzare il contenuto dell'immagine se la propriet AutoDisplay stata impostata false.

TDBListBox, TDBComboBox, TDBRichEdit, TDBCheckBox, TDBRadioGroup


Per tutti questi componenti, non esistono particolari propriet o metodi all'infuori di quelli gi visti comuni a tutti i componenti Data-Aware (DataSource, DataField, etc.). Pertanto per le propriet di questi controlli si rimanda alla guida in linea di Delphi. L'unica differenza interessante tra la versione non Data-Aware e la versione standard riguarda il controllo TDBRadioGroup che possiede una propriet Values che ha lo scopo di permettere l'assegnazione di valori personalizzati da salvare nel campo del Dataset. Questa propriet di tipo TStrings e se non vengono definiti dei valori per essa, nel campo dele dataset verr salvato l'indice, in base 0, della voce selezionata.

TDBLookupListBox, TLookupComboBox
Questi due controlli, sono in tutto simili ai rispettivi TDBListBox e TDBComboBox tranne per il fatto che i valori riportati nelle liste di scelta, provengono da un dataset. Questi controlli sono collegati a due dataset: uno il dataset di lavoro (dove vengono inseriti i valori del controllo) e l'altro quello da dove vengono recuperati i valori che appaiono nelle liste di scelta. Per quanto riguarda il collegamento al dataset principale, non c' nulla di nuovo da dire, il collegamento segue le regole gi viste. Per il dataset di origine dei valori delle liste, ci troviamo di fronte a delle nuove propriet che sono comuni a tutti quei componenti di tipo lookup che derivano da TDBLookupControl. Queste propriet sono: KeyField, KeyValue, ListField, ListFieldIndex, ListSource. Tutte queste propriet, all'infuori di KeyValue, sono accessibili a design-time tramite l'Object Inspector. KeyField di tipo string e indica il campo del dataset sorgente per la lista da cui verranno estratti i dati da inserire nel campo del dataset principale indicato da DataField. KeyValue contiene il valore del campo indicato in KeyField. Esso non corrisponde al valore visualizzato dal controllo. Assegnando a questa propriet un valore, il controllo effettuer una ricerca all'interno del dataset sorgente per la lista alla ricerca di un record il cui contenuto del campo riportato in KeyField corrisponde al valore riportato in KeyValue. ListField, di tipo string e contiene il campo del dataset sorgente per la lista il cui valore sar visualizzato dal controllo. Nel caso di TDBLookupComboBox, sara il campo i cui valori appariranno nella lista a discesa visualizzata dal controllo. ListFieldIndex indica l'indice che verr utilizzato per ordinare la lista dei valori visualizzati dal controllo. ListSource contiene il riferimento al datasource che si riferisce al dataset sorgente per la lista di valori.

TDBCtrlGrid
Questo controllo particolare ma molto utile per creare una visualizzazione particolare del contenuto dei records di un dataset. Esso permette di raggruppare in pannelli i contenuti dei records di un dataset, permettendo di disporre a piacere i controlli che effettivamente visualizzano il contenuto dei campi dei records. A differenza degli altri componenti data-aware che visualizzano o gestiscono direttamente il contenuto dei campi dei records, il controllo DBCtrlGrid non possiede, ovviamente, la propriet FieldName. Questo controllo lavora con i records piuttosto che con i campi dei records. Infatti se viene posto in una form un controllo DbCtrlGrid senza inserirvi degli altri controlli data-aware all'interno che gestiscano il contenuto dei campi dei records non si vedr apparire nulla all'interno di esso. Tra le caratteristiche impostabili di questo controllo vi la possibilit di scegliere il numero di righe e colonne, l'orientamento, la dimensione dei pannelli ed altre caratteristiche. Ecco le propriet principali di questo controllo: AllowDelete, AllowInsert, Canvas, ColCount, DataSource, EditMode, Orientation, PanelBorder, PanelCount, PanelHeight, PanelIndex, PanelWidth, RowCount, SelectedColor, ShowFocus. Le propriet AllowDelete e AllowInsert indicano se il controllo pu permettere all'utente l'inserimento di un nuovo record oppure l'eliminazione di quello corrente. Queste due operazioni possono essere effettuate attraverso la pressione rispettivamente dei tasi Ctrl+Delete e Ctrl+Insert sulla tastiera quando il controllo possiede il fuoco. La propriet Canvas, disponibile solamente a run-time, permette di accedere al controllo Canvas che gestisce la grafica dei pannelli della griglia. ColCount e RowCount, permettono di specificare rispettivamente il numero di colonne e di righe che verranno rappresentati contemporaneamente nel controllo. Queste propriet definiscono altres il numero di pannelli visibili nella griglia. Questo valore reperibile nella propriet PanelCount disponibile a run-time. DataSource definisce il riferimento alla sorgente dati collegata al dataset a cui la griglia sar collegata. EditMode permette di stabilire la modalit di funzionamento della griglia, ovvero se questa pu permettere l'inserimento l'eliminazione e la modifica dei records, un po' come la propriet ReadOnly di altri controlli. PanelBorder definisce lo stile del bordo dei pannelli nella griglia. I valori possibili sono gbNone o gbRaised. Attraverso le propriet PanelHeight e PanelWidth possibile definire le dimensioni dei pannelli, rispettivamente altezza e larghezza. SelectedColor definisce il colore che verr utilizzato per indicare il pannello corrispondente al record corrente nel dataset. In ultimo ShowFocus permette di indicare se il controllo DBCtrlGrid deve disegnare un rettangolo di fuoco attorno al pannello corrispondente al record corrente quando il controllo ha il fuoco. LEZIONE 37 :

Reports

Nelle sue distribuzioni standard, Delphi fornisce dei componenti per creare, organizzare e stampare i resoconti delle eleborazioni su database effettuate dalle nostre applicazioni. Questo insieme di componenti costituiscono

la suite QuickReport che permette di creare visualmente lo schema da utilizzare per stampare i dati. Questa suite permette di realizzare a design-time il report, di effettuarne l'anteprima sia in fase di design che di runtime, effettuare calcoli l''interno del report stesso, inserire grafici recuperare automaticamente i dati dai campi dei records, la realizzazione di report a pi livelli o collegati, effettuare un export del risultato dell'elaborazione in formato testuale, CSV o HTML. Su internet ed in particolare sui news group che trattano di Delphi molti non trovano bene con questi componenti, che in effetti, almeno nelle versioni precedenti a quella presente con Delphi 5, avevano qualche difetto. Esistono altre soluzioni per la creazione di reports, sia commerciali che freeware, pi o meno complesse e complete. Una delle suite che ritengo meriti di essere mensionata FuzzyReport (precedentemente FastReport) creata da Fabio Dell'Aria che mette a disposizione un editor di reports a runtime permettedo quindi all'utente finale di personalizzare i reports dell'applicazione. Tutto questo free, e anche con prestazioni migliori rispetto a quelle di QuickReport. Per ulteriori informazioni potete visitare il sito http://www.fuzzyreport.shorturl.com/. Comunque in questo corso tratteremo in maniera molto breve gli strumenti standard forniti con Delphi.

Il componente fondamentale di QuickReport il componente QuickRep, il cuore del sistema di generazione dei reports. Esso si presenta come un foglio bianco da posizionare in una form nel quale andranno posizionati gli altri componenti di QuickReport che andranno a definire l'aspetto grafico e funzionale del report stesso. QuickReport uno di quegli strumenti per la costruzione di reports definiti a "bande". Infatti per definire la struttura del report si utilizzano delle bande, ogniuna con uno specifico compito. Eistono bande titolo, itestazione, pi di pagina, header, dettaglio, sottodettaglio e cos via. All'interno di cisacuna banda andranno inseriti altri componenti visuali, che serviranno a rappresentare i dati del report: possiamo avere delle etichette sia statiche che collegate a campi di database, come il componente Label standard, delle immagini, sia statiche che ricavate da campi blob di database, campi per inserire formule, componenti per le forme, per grafici statistici o per ricavare informazioni strettamente riguardanti il report come il numero di pagine totali o il numero di pagina corrente, la data di stampa etc. La costruzione di un report pu avvenire in due modi, attraverso i wizards attraverso il menu File->New oppure "a manina" partendo da una normalissima form. Per prima cosa si posiziona un componente QuickRep nella form che assumer l'aspetto di un foglio bianco con una griglia quadrettata e l'indicazione dell'area stampabile nella stampante predefinita. A questo punto gi possibile definire alcune delle caratteristiche del report; attraverso la propriet Bands del componente QuickRep, possibile definire quali bande sono necessarie alla realizzazione del report: Title, PageHeader, PageFooter, etc. La propriet Dataset serve a legare il report ad un dataset per il recupero dei dati. La propriet Description serve a definire una descrizione per il report; questa propriet potrebbe tornare utile quando si da la possibilit all'utente di scegliere un report, poich attraverso questa propriet si pu fornire una descrizione del report. Attraverso la propriet Frame possibile specificare se il report verr riquadrato segnando l'area stampabile dalla stampante. Accedendo alla propriet Page possibile definire le propriet della pagina di stampa, larghezza, altezza, margini e se si vuole, la visualizzazione dei righelli che ovviamente saranno visibili solamente in fase di disegno del report. PrinterSettings permette di impostare le opzioni per la stampante, il numero di copie, il binario della carta, etc. PrinterIfEmpty indica a QuickReport se stampare o no il report se il dataset a cui collegato vuoto. ReportTitle serve ad impostare il titolo del report e pu essere utilizzato nel report stesso attraverso il componente QRSysData e la stessa propriet viene utilizzata nell'elenco dei job di stampa nel "Print Manager" di windows. Vi sono ancora altre propriet interessanti in questo componente come ShowProgress che serve a visualizzare o no l'avanzamento dello stato di costruzione del report, Unit che indica l'unit utilizzata nella rappresentazione della griglia e dei righelli ed infine lo Zoom, che permette di regolare la visualizzazione del report in fase di disegno. Completata la costruzione del report, per permetterne la visualizzazione necessario eseguire all'interno del codice del programma il metodo Preview del componente QuickRep, per visualizzare l'anteprima del report, o il metodo Print per effettuarne la stampa. QuickReport permette di personalizzare l'aspetto della form di anteprima, mettendo a disposizione il componente QRPreview. Per maggiori informazioni a riguardo consultare la guida in linea. Un'altra possibilit messa a disposizione da QuickReport quella di esportare il risultato dell'eleborazione del report in formato testuale formattato o in HTML attraverso i componenti QRTextFilter, QRCVSFilter, QRHTMLFilter. L'esportazione molto semplice: basta collocare un componente di quelli precedentemente mensionati in una form ed automaticamente, attraverso l'opzione salva nella finestra di anteprima del report, verr visualizzata l'opzione di esportazione scelta. Si pu efettuare la stessa operazione da codice attraverso il metodo ExportToFilter del componente QuickRep. LEZIONE 36:

Modifica e creazione di componenti

Fino ad ora abbiamo visto pi o meno come utilizzare i molti componenti che sono disponibili in Delphi. Ma cosa fare tra tutti questi componenti non troviamo quello che fa al caso nostro? Semplice, possiamo crearcelo da soli oppure modificare le caratteristiche di uno gi esistente che assomiglia molto al componente che stiamo cercando. Questa non difficile costruire nuovi componenti o modificare quelli gi esistenti, ma occorre avere una discreta conoscenza dell'Object Pascal poich la realizzazione di un componente non prevede l'impiego di tool di sviluppo visuale (almeno direttamente forniti con Delphi) e quindi necessario scrivere tutto il codice a mano. Occorre altres avere una buona conoscenza dei componenti gi esistenti e delle classi base messe a disposizione da Delphi per evitare di riscrivere del codice o componenti gi esistenti. In questa sezione del corso vedremo prima di tutto come modificare un componente gi esistente e poi come costruire un semplicissimo componente "ex novo". Quando si decide di realizzare un componente, necessario analizzare attentamente i componenti esistenti e tenere presente che non possibile nascondere metodi o propriet di un componente. Per questo la scelta del componente (classe) da cui ereditare le caratteristiche necessarie ha una grande importanza. Delphi come al solito, ci viene incontro fornendoci delle classi base dei quasi tutti i componenti standard della VCL (TEdit, TComboBox, ...). Queste classi hanno nomi del tipo TCustomEdit, TCustomComboBox, etc. Esse implementano tutte le funzionalit e propriet dei componenti veri e propri ma non le rendono visibili all'esterno. Le classi che implementano il componente vero e proprio, non fanno altro che ereditare dalle classi base e rendere visibili all'esterno tutti i metodi e propriet della relativa classe base. Ci permette di rendere visibili solo alcuni dei metodi e propriet che ci interessano. Per creare un nuovo componente, possiamo seguire due strade: attraverso il menu Component->New Component, oppure scrivendo da soli lo scheletro di base per il nostro oggetto. Conviene comunque seguire la prima strada che risparmia, se non altro, il tempo per la scrittura del codice. Cominciamo. Apriamo la finestra di dialogo New Component come sopra descritto e digitiamo quanto riportato nell'immagine seguente

Fig. 1 - Dialogo per creazione nuovo componente

Ancestor Type: indica la classe di base da cui far discendere il nostro nuovo componente. Class Name: il nome che vogliamo dare alla nostra nuova classe; viene automaticamente compilato quando si sceglie il tipo di classe da cui ereditare ma pu essere modificato a piacere. Il nome scelto per questo esempio TMyEdit, ma nessuno vieta di scegliere un altro nome. Occhio a non usare nomi di classi gi esistenti! Palette Page: la pagina della Componente Palette di Delphi dove verr posizionato il componente quando andremo a registrarlo nell'IDE di Delphi. In questo esempio ho scelto di posizionare il nuovo componente nella pagina Samples della component Palette, ma voi potrete scegliere la pagina che preferite.

Unit File Name: il nome, completo di percorso, che vogliamo dare alla unit che conterr il codice del nostro nuovo componente. In questa casella potete inserire il percorso che preferite che sicuramente differir da quello da me scelto. Search Path: l'elenco di percorsi all'interno dei quali Delphi ricercher le unit necessarie per il componente. Completata questa fase, premiamo il tasto OK e vedremo che Delphi creer una unit con il nome da noi fornito contenente l'impalcatura della nostra nuova classe. Ecco il codice che comparir nell'Code Editor al termine dell'operazione

unit MyEdit; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMyEdit = class(TEdit) private { Private declarations } protected { Protected declarations } public { Public declarations } published { Published declarations } end; procedure Register; implementation procedure Register; begin RegisterComponents('Samples', [TMyEdit]); end; { TMyEdit } end. Come potete vedere, sono rintracciabili tutte le informazioni da noi fornite all'interno della finestra di dialogo, ma non abbiamo dovuto scrivere una riga di codice! Ora passiamo a modificare le impostazioni di default della classe TEdit. Vogliamo impostare come colore di sfondo di default del componente TEdit il colore giallino utilizzato nei ToolTips e impostare la sua lunghezza di default a 200 pixels. Bene, procediamo con il fare l'override della procedura di creazione del componente. Aggiungiamo nella sezione Public della dichiarazione della classe la ridichiarazione del costruttore Create come segue Constructor Create(AOwner : TComponent); override; una volta scritto il codice sopra riportato, possiamo utilizzare la combinazione di tasti Ctrl+Shift+C per dire a Delphi di completare il codice automaticamente. Questa operazione porter alla creazione del codice di implementazione del costruttore appena definito nella forma seguente

constructor TMyEdit.Create(AOwner: TComponent); begin inherited; end; a questo punto possiamo aggiungere il codice necessario per personalizzare i nostri valori di default; modifichiamo l'implementazione del costruttore come segue

constructor TMyEdit.Create(AOwner: TComponent); begin inherited; Width := 200; Color := clInfoBk; end; Con queste righe di codice impostiamo a 200 pixels la lunghezza del controllo ed il colore di sfondo al colore di sfondo correntemente attivo per i ToolTips. La procedura Register serve a Delphi per registrare il componente in una pagina della Components Palette; questa procedura chiama infattai la procedura RegistreComponents passandogli come parametro il nome della pagina della Components Palette: se la pagina esiste vi registra il componente altrimenti la crea e quindi vi registra il componente. Questo un semplicissimo esempio di come possibile personalizzare alcune caratteristiche di un componente gi esistente. Si possono anche aggiungere funzionalit ad un componente gi esistente; per esempio, potremmo voler aggiungere al nostro componente MyEdit una propriet che contenga il testo digitato nel controllo con l'ordine dei caratteri invertito. Vediamo come implementare questa caratteristica. Aggiungiamo nella parte Private della nostra classe TMyEdit la funzione Reverse dichiarandola come segue

Function Reverse : String; ed implementandola come segue

function TMyEdit.Reverse: String; Var I, L : Integer; begin L := Length(Text); SetLength(Result, L); For I := 1 to Length(Text) do Result[(L+1) - I] := Text[I]; end; Questa funzione legge la stringa contenuta nella propriet Text del controllo (ereditata dalla classe TEdit) e dispone i caratteri che la compongono in ordine inverso. La propriet Text contiene il testo digitato dall'utente. Ora occorre implementare una propriet che restituisca, in lettura, il testo invertito. Aggiungiamo nella parte di dichiarazione Public della nostra classe TMyEdit la propriet ReversedTxt nel seguente modo:

Property ReversedTxt : String read GetReversedTxt; Premendo Ctrl+C dopo aver digitato questa riga, otterremo il completamento automatico della nostra propriet con la creazione della funzione GetReversedTxt. Ecco come apparir la dichiarazione della nostra classe dopo queste modifiche

TMyEdit = class(TEdit) private { Private declarations } Function Reverse : String; function GetReversedTxt: String; protected { Protected declarations } public { Public declarations } Constructor Create(AOwner : TComponent); override; Property ReversedTxt : String read GetReversedTxt; published { Published declarations } end; Modifichiamo l'implementazione della funzione GetReversedTxt come segue

function TMyEdit.GetReversedTxt: String; begin Result := Reverse; end; Si pu anche eliminare la funzione Reverse implementando il codice direttamente nella funzione GetReversedTxt ma per questioni di chiarezza e modularit del software, preferisco utilizzare questa struttura. Per provare il nostro componente possiamo registrarlo in Delphi scegliendo dal menu Component la voce "Install Component", il componente verr installato nella libreria dei componenti e la sua icona posta nella pagina della Components Palette da noi scelta, oppure aggiungere la unit MyEdit nella clausola uses di una nuova form e creare il componente a runtime come segue

procedure TForm1.FormCreate(Sender: TObject); begin MyEdit := TMyEdit.Create(Self); MyEdit.Parent := Self; // MyEdit.Top := 16; //Posizionamento del componente nella form MyEdit.Left := 10; // " " " " " end; e nel evento OnDestroy della form aggiungiamo il seguente codice

procedure TForm1.FormDestroy(Sender: TObject); begin MyEdit.Free; end;

per eliminare il componente da noi creato quando eliminiamo la form. Non dimenticatevi di inserire una variabile MyEdit di tipo TMyEdit nella sezione Private della classe TForm1

... private { Private declarations } MyEdit : TMyEdit; public { Public declarations } end; Potete scaricare il codice completo dell'esempio riportato per vedere come funziona ed avere il codice a disposizione. Per quanto riguarda la creazione di componenti, molto difficile che si creino componenti partendo completamente da zero, salvo in casi molto particolari. Generalmente si eredita dalla classe TComponent per i componenti non visibili e da TControl o da TWinControl per i componenti visibili e da TGraphicControl per i componenti grafici. Possiamo dire che queste sono le classi utili pi "primitive" da cui iniziare a creare nuovi componenti. La scrittura di codice per componenti richiede una discreta conoscenza della programmazione ad oggetti in Object Pascal, delle classi da cui ereditare e delle basi del sistema operativo, in questo caso Windows. Pertanto si rimanda alla lettura dei manuali e alla lettura di testi specifici, come la serie Mastering Delphi di Marco Cant, in particolare "Delphi Developer's Handbook" e "Laboratorio di Delphi" di Nando Dessena e Barbini, nonch frequentare i news group che trattano di Delphi frequentati anche dai personaggi citati. LEZIONE 38

DLL: cosa sono, a cosa servono, come crearle e/o utilizzarle

DLL l'abbrevizione di Dynamic Link Library (Libreria a Collegamento Dinamico). Le DLL sono delle raccolte di routines che possono essere chiamate da pi programmi e solitamente contengono codice di utilit generale che viene messo a disposizione di pi applicazioni. Ci permette di non inserire codice duplicato in applicazioni differenti. Si possono assimilare le DLL alle unit di Delphi solamente che le prime contengono codice eseguibile, collegabile in fase di esecuzione. In Delphi esistono diversi modi di richiamare funzioni contenute in una DLL: possibile utilizzare la parola chiave External dopo la dichiarazione del nome della funzione da importare, seguita dal nome del file della DLL che la contiene. Non necessario che la DLL sia presente in fase di compilazione, ma se questa non sar presente durante l'esecuzione dell'applicazione si otterr un errore. Un esempio di procedura importata da una DLL il seguente

Procedure DLLProcedure1; external 'MYDLL.DLL'; Un'altro modo di richiamare una funzione o procedura contenuta in una DLL caricando in fase di esecuzione la libreria con l'API di windows LoadLibrary, di recuperare l'indirizzo della procedura o funzione con l'API GetProcAddress, chiamare la procedura attraverso l'indirizzo ottenuto ed infine, quando non occorre pi, scaricare la libreria con l'API FreeLibrary. In questo secondo caso anche necessario dichiarare una variabile procedura del tipo corrispondente alla procedura o funzione da richiamare. Se nell'esempio precedente la nostra procedura avesse avuto una dichiarazione del tipo

Procedure DLLProcedure1(Param1, Param2 : Integer); la variabile procedura avrebbe dovuto essere del tipo Type TDLLProcedure1 = procedure(Param1, Param2 : Integer);

Var DLLProcedure1 : TDLLProcedure1; A questa variabile deve essere assegnato il valore restituito dalla funzione GetProcAddress nella seguente maniera

... @DLLProcedure1 := GetProcAddress(Handle, 'DLLProcedure1'); ... dove Handle l'handle della libreria restituito dalla chiamata a LoadLibrary. Si consiglia di fare riferimento all'SDK di Windows per le API sopracitate. La scrittura di una DLL in Delphi non differisce molto dalla scrittura di una normale applicazione. La differenza sostanziale sta nella parola chiave Library che sostituisce Program nell'intestazione della unit. In una DLL le procedure e funzioni possono essere contenute nel file di progetto oppure contenute in unit esterne e la libreria si riduce ad una clausola uses contente le unit con le funzioni e procedure seguita da una sezione Exports con l'elenco delle funzioni o procedure da esportare. Il blocco principale Begin..End di una libreria rappresenta il blocco di codice che viene eseguito come codice di inizializzazione della libreria e viene eseguito ogni qual volta la libreria viene caricata in memoria. Se si vogliono rendere disponibili le routines contenute in una DLL scritta in Delphi anche ad altri linguaggi di programmazione, occorre specificare la direttiva stdcall poich non tutti i linguaggi supportano la modalit di chiamata ottimizzata "register" utilizzata dall'Object Pascal. La sezione Exports di una DLL ha la seguente struttura

exports entry1, entry2, ..., entryn; Ogni entry costituita dal nome della routine da esportare (funzione o procedura) seguita da due specificatori opzionali: index e name. Il primo serve a definire un indice numerico per richiamare la routine esportata e pu assumere valori tra 1 e 2.147.483.647. Se per una routine non viene specificato un valore per index gli viene assegnato automaticamente un numero tratto dalla tabella di esportazione della DLL. Lo specificatore name seguito da una costante stringa e specifica il nome con cui verr esportata la routine. Se non viene assegnato ad una routine uno specificatore name, questa verr esportata con il nome con cui stata dichiarata nella DLL, rispettando anche le maiuscole/minuscole. Tornando alla nostra procedura di esempio, potremmo avere una sezione exports di questo genere

exports DLLProcedure1 index 1 name 'DLLProcedure1'; che equivalente a

exports DLLProcedure1; se vogliamo esportare la routine con un altro nome

exports DLLProcedure1 index 1 name 'DLLProcedure';

Delphi ci mette a disposizione alcune variabili globali per semplificare la gestione delle DLL. Abbiamo visto in precedenza che il codice contenuto nel blocco principale della libreria viene utilizzato come codice di inizializzazione della libreria stessa, ma esiste un codice di pulizia da eseguire al rilascio della libreria? Esiste. Si pu fornire l'indirizzo di una procedura che si occuper di eseguire il codice di pulizia tramite la variabile ExitProc. Questa viene solitamente impostata in fase di inizializzazione della DLL. Quando la libreria viene rilasciata, viene eseguito automaticamente il codice contenuto nella procedura all'indirizzo contenuto in ExitProc. Occorre per salvare il vecchio puntatore alla procedura di pulizia prima di impostare una nuova procedura e ripristinarlo prima che la nuova procedura termini l'esecuzione. Nella unit System presente una variabile che permette di determinare se il codice in esecuzione appartiene ad una DLL oppure ad un'applicazione: IsLibrary. Questa variabile assume valore false quando si in presenza di un'applicazione e true altrimenti. Nella scrittura delle DLL bisogna fare attenzione ai parametri passati e restituiti alle/dalle routines. Se si utilizzano stringhe di tipo long o array dinamici, record od oggetti, occorre che tutti i moduli che fanno uso delle routines e le DLLs che esportano le routines in oggetto utilizzino la unit ShareMem, dichiarandola come prima unit nella clausola uses. Questa una interfaccia per il gestore di memoria BORLANDMM.DLL che permette ai moduli di condividere la memoria allocata in modo dinamico. LEZIONE 36:

I Packages: cosa sono, a cosa servono, come crearli e/o

utilizzarli
I Packages sono particolari tipi di librerie a caricamento dinamico che possono essere utilizzate solamente da applicazioni Delphi e dall'IDE di Delphi stesso. Questo nuovo tipo di libreria stato introdotto con Delphi 3 e lo scopo principale quello di creare librerie di componenti riutilizzabili da pi applicazioni. Essi possono essere utilizzati per linkare il codice in essi contenuto sia a design-time che a run-time. I Packages possono essere di tre differenti tipi: Design-Time, Run-Time, Design-Time and Run-Time contemporaneamente. I primi sono disponibili solamente in fase di progettazione ed installabili nell'IDE di Delphi, i secondi vengono distribuiti con le applicazioni e non possono essere installati nell'IDE, gli ultimi sono un misto dei primi due. L'estensione per il codice sorgente dei packages .DPK, mentre per la versione compilata sar .BPL (Borland Package Library). Esiste anche la possibilit di creare librerie di packages utili per passare o condividere componenti con altri sviluppatori in uno stesso team. In questo caso avremo un file di libreria con estensione .DPL. Questo tipo di libreria pu essere creato attraverso l'apposito editor; il Package collection Editor. Per creare un nuovo package si pu come sempre ricorrere al wizard "New" dal menu File dell'IDE. Come per le librerie DLL, anche per i packages esiste una parola chiave che si trova all'inizio del file sorgente principale e che ne identifica il tipo: Package. Nel file sorgente principale di un package sono presenti due clausole aggiuntive, non presenti nei files sorgente delle DLL; Requires e Contains. Queste due clausole identificano rispettivamente altri packages esterni che sono richiesti dal package corrente per il suo corretto funzionamento e le unit contenute all'interno del package stesso. LEZIONE 36:

Delphi & Web

Il corso sta volgendo al termine. Prima di lasciarci, vorrei, a completamento, mostrare alcune funzionalit di Delphi che permettono la realizzazione di applicazioni indirizzate al web, alla produzione di pagine HTML e che permettono addirittura di creare componenti ASP da inserire nei siti web oppure per creare siti completamente basati su Delphi. Con Delphi possibile realizzare applicazioni CGI oppure ISAPI. Il tutto ovviamente semplicissimo come del resto qualsiasi operazione in Delphi!?! Esistono anche qui dei template da utilizzare per la creazione di CGI, ISAPI e componenti ASP; basta effettuare la scelta giusta nel wizard "New" dal menu "File". Esistono anche dei componenti chiamati "HTML Producer Components" che permettono di creare pagine HTML. Questi componenti utilizzano come schema per le pagine da costruire dei template HTML e ricavano i dati per completarli o direttamente dal codice oppure da tabelle di database o query. Questi componenti si trovano nella pagina Internet della Component Palette e sono: PageProducer, DataSetPageProducer, DataSetTableProducer, QueryTableProducer.

I Componenti per la produzione di pagine HTML


Nella pagina Internet della Component Palette oltre ad altri componenti che riguardano internet (WebDispatcher, WebBrowser) compaiono i componenti per la produzione di pagine Web: PageProducer, DataSetPageProducer, DataSetTableProducer, QueryTableProducer. La tecnica utilizzata da questi componenti per creare pagine HTML quella di utilizzare un normale file HTML, creato con un qualsiasi editor HTML, contenente alcuni tag speciali che hanno lo scopo di fare da segnaposto per l'output dei dati che l'applicazione dovr inserire nel documento. Il formato di questi tags <#NomeTag>. Il componente p semplice, il PageProducer. Questo analizza il documento HTML che gli viene fornito come template e genera un evento OnTag per ogni tag speciale incontrato in esso. Nell'event handler del evento OnTag deve essere inserito il codice che reagir al particolare tag. Per esempio se nel nostro documento HTML abbiamo inserito il tag speciale <#Data>, quando il componente analizzer il documento generer l'evento OnTag. Il tipo dell'event handler per questo evento il seguente

Procedure (Sender : TObject; Tag : TTag; const TagString : String; TagParams : TStrings; var ReplaceText : String); Supponiamo di aver creato il seguente event handler

procedure TForm1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag; const TagString: String; TagParams: TStrings; var ReplaceText: String); begin If TagString = 'Data' then ReplaceText := DateToStr(Now); end; Il codice dell'event handler sostituir il il tag speciale con la data corrente. Supponendo che il nostro documento avesse avuto la seguente struttura

<HTML> <HEAD> <Title>Prova componente Page Producer</Title> </HEAD> <BODY> <H2>Data corrente</H2> La data corrente <#Data> </BODY> </HTML> il risultato sarebbe

Data Corrente

La data corrente 17/08/2001 Il risultato precedente prelevabile dalla propriet Content del componente (di tipo TStrings). Esso pu essere copiato in un componente memo per la visualizzazione oppure salvato su disco con la propriet SaveToFile della classe TStrings. L'esempio riportato semplicissimo, ma possibile realizzare pagine web complesse attingendo da databases o altre fonti.

DataSetTableProducer, QueryTableProducer
A proposito di databases, Delphi dispone gi di un componente specializzato nella composizione di pagine HTML attingendo da fonti dati quali databases. Questo componente DataSetPageProducer. L'utilizzo di questo componente semplicissimo e molto simile al PageProducer che abbiamo visto sopra. In questro caso abbiamo una propriet in pi che quella che definisce il collegamento con il dataset contenente i dati. Basta fornire il solito documento HTML come template e disporre dove necessario i tags speciali corrispondenti ai nomi dei campi del dataset collegato al componente stesso. In questo modo il componente recupera automaticamente il contenuto dei campi e lo inserisce nel documento. Noi non dobbiamo fare nulla se non gestire altri tags speciali, come abbiamo fatto nell'esempio precedente, per inserire valori non provenienti dal dataset attraverso il gestore dell'evento OnTag. Un altro componente per la produzione di codice HTML il DatasetTableProducer che ha lo scopo di creare tabelle in standard HTML prelevando il contenuto da un dataset. Dispone quindi della propriet Dataset per il collegamento alla sorgente di dati. Questo componente dotato anche di un "Property Editor" per la propriet Columns. Attraverso di esso possibile impostare a design-time le caratteristiche delle colonne ed i campi del dataset ad esse associati, producendo anche una anteprima del risultato finale. Il DatasetTableProducer prevede anche la possibilit di definire un header ed un footer (una intestazione tabella ed un pie' tabella). Tramite le propriet Hader ed Footer del componente possibile inserire, ad esempio, il titolo del documento. Il contenuto della propriet Header e Footer deve essere in formato HTML. Percui, se volessimo definire il titolo del documento, dovremmo scrivere nella propriet Header il seguente testo:

<H1>Titolo</H1> Lo stesso dicasi per la propriet Footer. Nel componente sono previste anche propriet per impostare le caratteristiche della tabella quali il colore di sfondo, il cellpadding (la distanza del contenuto della cella dal bordo cella), il cellspacing (lo spazio tra una cella e l'altra), la presenza o no del bordo e la dimensione, e le caratteristiche delle righe che comporranno la tabella. Tutte questa impostazioni sono raccolte in due propriet, RowsAttributes e TableAttributes. Il componente DatasetTableProducer espone anche degli eventi che permettono un maggiore adattamento alle proprie necessit. In particolare l'evento OnFormatCell che viene attivato al momento della formattazione della singola cella. Definire un gestore per questo evento permette di modificare per ogni singola cella i valori di colore di sfondo, l'allineamento verticale od orizzontale, e perfino il contenuto della cella stessa. In maniera del tutto simile funziona il componente QueryTableProducer tranne per il fatto che questo viene collegato ad un componente Query e che il suo scopo quello di generare pagine HTML basate su query parametrizzate i cui parametri vengono ottenuti da una richiesta HTML. La modalit di richiesta pu essere GET o POST.

Delphi e HTML dinamico


Quanto visto sopra applicabile ad applicazioni che vogliono fornire una versione dei dati in formato HTML (formato multipiattaforma) o per applicazioni che realizzano server web o servizi che rispondono come server web. Ci sono altri sistemi che possono essere utilizzati per fornire informazioni in formato HTML. Chi realizza siti web conosce i CGI (Common Gateway Interface). Questi vengono utilizzati per fornire pagine HTML

dinamiche ovvero il cui contenuto vari in corrispondenza del variare dei potenziali dati da visualizzare. Esempi di questo tipo di documenti potrebbero essere pagine che forniscono notizie dell'ultima ora, estrazione di dati data database che vengono aggiornati frequentemente (annunci di compra/vendita). Quello che il browser riceve sempre un documento HTML che altro non che il prodotto di una elaborazione effettuata dal server web al momento della richiesta compilando la pagina con i dati richiesti e disponibili in quel momento. Un CGI altro non che una applicazione eseguibile che risponde alle richieste HTML effettuate dai browser; esso analizza le richieste in arrivo e restituisce dei documenti HTML compilati in base alle richieste ricevute. Esistono due tipi di CGI; i CGI e i WINCGI. I primi ricevono i parametri di richiesta direttamente dalla riga di comando mentre i secondi li estraggono da un file .INI che viene passato loro come parametro nella riga di comando e utilizzano come input ed output speciali files. Questo secondo tipo di CGI stato realizzato per venire incontro agli sviluppatori Visual Basic permettendo di scavalcare alcune limitazioni del linguaggio. Neanche a farlo a posta Delphi ci permette di realizzare questo tipo di applicazioni partendo dalla considerazione che essi non sono altro che applicazioni console che utilizzano lo standard input e lo standard output per acquisire le richieste e fornire i risultati dell'elaborazione. Tutto ci si traduce in una serie di Writeln che restituiscono codice HTML come ad esempio la seguente riga

Writeln('<H1>Titolo</H1>'); Se eseguiamo il CGI da una finestra di terminale vedremo esattamente ci che riportato come parametro nei comandi Writeln. Oltre ai CGI in Delphi possibile creare delle DLL ISAPI (Intrenet Server API) o NSAPI (Netscape Server API). Il primo tipo stato introdotto da Microsoft, mentre il secondo dall Netscape. Questa tecnologia pu essere vista come un miglioramento del CGI. Essa si differenzia dal CGI per le modalit di caricamento nella memoria del server e per le modalit di risposta. Mentre il CGI viene caricato ad ogni richiesta, per questo tipo di DLL le cose cambiano. La DLL viene carica al momento della richiesta, se essa non gi stata caricata in precedenza, nello stesso spazio di indirizzamento del server web ed capace di rispondere a pi richieste facendo uso di threads. Ci permette di non dover caricare tanti eseguibili in memoria quante sono le richieste fatte al server web come invece accade per i CGI. Ogni thread attivato dal server web gestisce una richiesta e la relativa risposta. C' differenza anche nella velocit di risposta rispetto al CGI poich nel caso delle DLL ISAPI/NSAPI lo scambio dei dati avviene tutto in memoria. Non mi dilungher oltre su questo argomento. Diamo uno sguardo a cosa mette a disposizione Delphi per aiutarci nella realizzazione di questo tipo di software. Delphi 5 include, nelle versioni Professional e Enterprise, la tecnologia WebBroker che integra nella VCL delle classi che semplificano la realizzazione di applicazioni web lato server. Per utilizzare il framework WebBroker, necessario scegliere dal menu file la voce New e nella prima pagina della finestra che compare scegliere "Web Server Application". Ci provocher la visualizzazione di una finestra di scelta del tipo di applicazione che si ha intenzione di realizzare. I tipi di applicazioni tra cui si pu scegliere sono tre: DLL ISAPI/NSAPI, eseguibile CGI , eseguibile WinCGI. Nel caso si scelga si realizzare DLL ISAPI/NSAPI, Delphi produrr un file progetto di libreria del tutto simile ad una normale libreria ma con delle particolarit. Per prima cosa vediamo comparire nella clausola uses del file progetto le unit WebBroker, ISAPIApp ed una nuova unit che contiene un oggetto WebModule. Nello stesso tempo dalla libreria vengono esportate delle procedure standard per il caricamento e scaricamento della libreria nella/dalla memoria del server web. Da notare che in questo tipo di DLL, l'oggetto application che viene creato non di tipo standard, non corrisponde infatti agli altri oggetti application creati nelle normali applicazioni. Nelle applicazioni ISAPI/NSAPI, l'oggetto application deriva da una nuova classe che pu essere TISAPIApplication o TCGIApplication. Queste ultime derivano entrambe da un'altra classe che TWebApplication. Sono delle classi specializzate per la realizzazione di applicazioni per il web. Il tipo dell'oggetto applicazione pu essere TISAPIApplication o TCGIApplication in base alla scelta che abbiamo effettuato nel wizard del Object Repository. La caratteristica pi importante che l'intera applicazione ruota attorno alla nuova unit contenente l'oggetto WebModule. Un WebModule molto simile ad un DataModule standard, con ovviamente delle caratterizzazioni specifiche per le applicazioni web. Il WebModule ha lo scopo di raccogliere tutti gli oggetti non visuali dell'applicazione e di permettere ad essa di rispondere ai messaggi di richiesta HTTP passando gli oggetti Request e Response alle azioni appropriate. Tra le propriet dell'oggetto WebModule compare la propriet Actions. Essa contiene l'elenco delle azioni che devono essere eseguite dall'applicazione in risposta ad un determinato switch che viene passato alla DLL. Lo switch che identifica l'azione contenuto nella propriet PathInfo dell'oggetto WebAction stesso. Un'altra propriet interessante dell'oggetto WebAction Producer che permette di collegare all'azione direttamente un oggetto di tipo TPageProducer,

TDataSetPageProducer, TDatasetTableProducer, TQueryTableProducer (che abbiamo gi visto) che verr richiamato all'attivarsi dell'azione stessa. Un'altra tecnologia che permette di realizzare pagine HTML dinamiche, la tecnologia ASP (Active Server Pages) introdotta da Microsoft. Essa si basa su codice script, inserito all'interno di documenti HTML, che viene eseguito direttamente nel server e che produce pagine HTML standard in risposta alle richieste dei clients. Questa tecnologia prevede anche un estensione delle proprie funzioni attraverso la realizzazione di oggetti COM che possono essere richiamati direttamente dal codice script. Anche in questo caso Delphi ci viene incontro fornendoci la struttura base per la realizzazione di questo tipo di componenti. Nel wizard che porta alla realizzazione del codice base per la realizzazione di oggetti ASP possiamo impostare tutti i parametri di funzionamento dell'oggetto COM, come il nome della classe, il tipo di interfaccia ed il tipo di threading dell'oggetto, il tipo di oggetto. Per creare la struttura base di un oggetto ASP, possiamo scegliere dal menu File -> New, nella pagina ActiveX dell'Object Repository la voce Active Server Object. ATTENZIONE! Per poter creare un oggetto ActiveX bisogna disporre di un progetto attivo, nel nostro caso potremmo creare un progetto di tipo "ActiveX Library", sempre dall'Object Repository, e quindi seguire la procedura indicata precedentemente. Il wizard ActiveX Server Object presenter una finestra per l'inserimento delle caratteristiche principali dell'oggetto ActiveX come riportato nell'immagine seguente

Fig. 1

CoClass Name indica il nome dell'oggetto ActiveX che si sta creando a cui Delphi anteporr un T; per cui se inseriamo in questa casella "ProvaASP" il nome della classe che sar "TProvaASP". Instancing definisce la modalit di istanziazione dell'oggetto ovvero Internal, Single Instance, Multiple Instance. Nel primo caso l'oggetto sar creato come interno e nessun'altra applicazione potr creare un'altra istanza dello stesso. Single Instace permette di creare una singola interfaccia per l'oggetto COM per ciascun eseguibile. Multiple Instance permette invece a pi applicazioni di collegarsi all'oggetto. Threading Model specifica invece la modalit di gestione dei thread dell'oggetto COM. L'impostazione Single indica che non vi nessun supporto per i thread e che tutte le richieste dei clients vengono gestite in maniera seriale. Apartment permette di accedere alle propriet ed ai metodi dell'oggetto solamente attraverso il thread in cui stato creato l'oggetto stesso. Free le propriet ed i metodi possono essere chiamati in qualsiasi momento da qualsiasi thread. La modalit Both indica che l'oggetto in questione pu supportare il metodo Apartment e Free. Active Server Type definisce la "compatibilit" con il server web specifico: IIS3 o 4 per l'opzione Page-level event methods e IIS 5 per Object Context. In pratica, nel primo caso vengono generati all'interno dell'oggetto due eventi OnPageStart, OnPageEnd che vengono richiamati dal server web rispettivamente all'inizializzazione della pagina e al termine dell'elaborazione. Nel secondo caso vengono utilizzate le funzionalit di MTS per recuperare i dati di istanza corretti dell'oggetto. Nel riquadro Options, se viene spuntata la voce "Generate a template test script for this object", verr cerata una pagina asp che istanzier e utilizzer l'oggetto creato ed ha solamente scopo di test.

Una volta creata la struttura base del nostro oggetto ASP, possibile accedere direttamente agli oggetti Application, Response, Request, Session di ASP. Non resta che implementare il codice specifico. Vediamo un semplicissimo esempio. A questo punto proviamo ad aggiungere un semplice metodo al nostro oggetto. Esso avr solamento lo scopo di restituire nel flusso HTML di risposta del codice HTML. Aggiungiamo alla nostra alla classe appena creata una procedura cos definita

procedure ShowHelloASP; safecall; ed implementata nel seguente modo

procedure TTelpressNewsService.ShowNews; begin Response.Write('<h1>Prova oggetto ASP in Delphi</h1><br>'); Response.Write('<B>Hello ASP!!!</B>'); end; Ora dovremo inserire una chiamata al metodo del nostro oggetto dalla pagina asp di test (magari creata automaticamente da Delphi) ottenendo quanto segue

<HTML> <BODY> <TITLE> Testing Delphi ASP </TITLE> <CENTER> <H3> You should see the results of your Delphi Active Server method below </H3> </CENTER> <HR> <% Set DelphiASPObj = Server.CreateObject("TNSW.TelpressNewsService") DelphiASPObj.ShowHelloASP %> <HR> </BODY> </HTML>

Questo solo un esempio, ma in programmazione (e soprattutto in Delphi) i limiti sono quelli posti dalla nostra immaginazione!!!!!!!!!!!

P.S. Ovviamente per approfondimenti sull'argomento trattato, consultate il manuale del linguaggio, la guida in linea e se ne avete la possibilit "Mastering Delphi 5" di Marco Cant.