Introduzione
Delphi è un ambiente di sviluppo decisamente stimolante e capace di procurare grandi
soddisfazioni a chiunque vi si dedichi.
Questo è solo un piccolo esempio di come realizzare un programma in Delphi, dedicato a
chi lo voglia imparare e ne conosca solo la schermata iniziale, quella che appare dopo
aver fatto doppio-click sulla sua icona.
Delphi è, come già detto, un valido prodotto orientato alla progettazione (oserei dire) di qualunque genere di programma si possa immaginare in ambiente Windows. Ecco le sue caratteristiche principali.
Usa il linguaggio Object Pascal: per poter programmare
in Delphi bisogna usare la sintassi del linguaggio Pascal per scrivere il codice. Qui si
darà per scontato che il lettore conosca almeno le basi della programmazione in Pascal.
Ci sono una marea di libri disponibili a chiunque volesse imprarare questo linguaggio che
è uno dei più semplici da apprendere, tanto da essere adottato tuttora a scopo didattico
in scuole medie, superiori e perfino nelle università.
Conoscere la programmazione orientata agli oggetti, non è un requisito indispensabile per
poter creare un semplice programma in Delphi.
Una precisazione però mi sembra d'obbligo.
Chiunque può sedersi dinanzi ad un pianoforte e cominciare a suonare, a premere i tasti
con o senza cognizione, o a riprodurre una melodia a cui è affezionato, in più o meno
tempo. Perché però il pianoforte emetta le note di una splendida sonata o di una
struggente romanza ed arrivi a raggiungere i suoi massimi livelli si presuppone che chi
suoni abbia dedicato tanto tempo all'apprendimento ed al perfezionamento di tutte le
tecniche che questa attività comporta.
Così è la programmazione (oltre che molte altre cose).
La programmazione orientata agli oggetti (Object Oriented Programming - OOP) è in
particolare una filosofia, più che una tecnica di programmazione propria di questo o quel
linguaggio. I suoi canoni (una serie di regole per dirla semplice) prescindono totalmente
dal particolare linguaggio usato. E' piuttosto il linguaggio che può implementare meglio
o peggio l'OOP e permettere il rispetto delle sue regole senza troppi sforzi ma
addirittura con naturalezza. Delphi offre molti strumenti per implementare un buon grado
di OOP e conoscerne i canoni è fondamentale per la progettazione, la
manutenzione, l'aggiornamento ed il testing di un programma un po' più grande o
complesso. Inoltre il codice OO sarà facilmente riusabile ed adattabile ai programmi che
scriverete in seguito.
Concludo a malavoglia questa interessante divagazione (forse anche già troppo lunga)
ma altre informazioni, anche più dettagliate e tecniche, possono essere reperite sui
numerosi testi che ultimamente sono stati realizzati proprio sul Delphi. Basta guardarsi
intorno...
Passiamo invece alla realizzazione del nostro primo programma in Delphi.
Farò l'ipotesi che abbiate la versione 3.0 di Delphi, che non è l'ultima uscita ma che
non differisce molto, ai nostri livelli, dalla precedente (2.0) e dalla seguente (4.0); un
po' di più dalla prima (1.0). Sarà facile adattare eventualmente il programma alle altre
versioni di Delphi in vostro possesso.
Cominceremo con il dettare le specifiche del programma, decidiamo cioè cosa debba fare il programma, quale sia il suo scopo, a cosa serva. Adottare questo approccio è una buona regola; aprire il Delphi con le idee già chiare su quello che si vuole (anche se non sul come lo si può ottenere) impedisce di commettere grossolani errori di impostazione di cui delle volte ci si può perfino non accorgere (ma si finisce per provare solo uno strano ed inspiegabile senso di fastidio).
Specifiche del programma.
Il programma che realizzeremo è una piccola utility. Il suo scopo è l'operazione di
rinomina di un gruppo di files scelti a piacere e che debbano essere rinominati
progressivamente. Facciamo un esempio pratico per assicurarci di essere d'accordo sul
risultato da ottenere.
Abbiamo 5 files (non necessariamente in una stessa directory). Vogliamo rinominarli in
gruppo con un prefisso ed un suffisso comune ed un numero al centro che vari
progressivamente, mantenendone le rispettive estensioni (come è presumibilmente giusto
che accada). E' meno chiaro di prima? Forse sì...
La tabella esemplificativa non può fallire, ed eccola qui.
| PRIMA DELL'ESECUZIONE DEL PROGRAMMA | DOPO L'ESECUZIONE DEL PROGRAMMA |
| Pippo.txt Pluto.bmp Topolino.log Paperino.htm QuiQuoQua.bmp |
File1New.txt (ex Pippo.txt) File2New.bmp (ex Pluto.bmp) File3New.log (ex Topolino.log) File4New.htm (ex Paperino.htm) File5New.bmp (ex QuiQuoQua.bmp) |
Abbiamo fatto l'ipotesi di usare come maschera per i files la stringa 'File#New', dove
al posto del carattere '#' deve essere inserito il numero progressivo.
Ricapitolando: selezione files da rinominare > introduzione della maschera >
esecuzione dell'algoritmo di rinomina.
Assicuriamoci che quello che abbiamo detto specifichi completamente il comportamento del
programma in ogni situazione e su ogni calcolatore.
Ad un'analisi un po' più attenta ci si accorge in effetti che potrebbe capitare che un
file sia già presente con un nome che verrebbe assegnato ad un altro file; ad es. se
prima dell'esecuzione del programma è già presente un file dal nome 'File4New.htm' nella
stessa directory di 'Paperino.htm' (e il file non viene selezionato per la rinomina)
sorgerebbe un conflitto.
In questo caso ci possiamo comportare in più di un modo:
- cancellare il file che crea il conflitto e continuare imperterriti la rinomina; (non ci
pensate nemmeno)
- bloccare l'algoritmo non appena si genera un conflitto e lasciare i files ancora
rimanenti con i loro nomi inalterati; (non molto elegante)
- rivelare subito il conflitto ed impedire che si avvii l'algoritmo di rinomina; (un po'
più elegante, ma alquanto rigido)
- saltare quel particolare nome che esiste già e continuare con l'algoritmo incrementando
semplicemente il numero corrente (magari mostrando un messaggio che avvisi l'utente).
Useremo quest'ultimo metodo che sembra il più conservativo e discreto. Una sua possibile
esecuzione con il verificarsi del conflitto è mostrata nella tabella che riportiamo
ancora una volta per fugare eventuali equivoci e metterci di nuovo in accordo
sull'obiettivo da raggiungere.
| PRIMA DELL'ESECUZIONE DEL PROGRAMMA | DOPO L'ESECUZIONE DEL PROGRAMMA |
| Pippo.txt (selezionato) Pluto.bmp (selezionato) Topolino.log (selezionato) Paperino.htm (selezionato) QuiQuoQua.bmp (selezionato) File4New.htm (non selezionato) |
File1New.txt (ex Pippo.txt) File2New.bmp (ex Pluto.bmp) File3New.log (ex Topolino.log) File4New.htm (inalterato) File5New.htm (ex Paperino.htm) File6New.bmp (ex QuiQuoQua.bmp) |
Quindi in caso di conflitto, nessun danno ed una ragionevole soluzione.
Di certo si potrebbero verificare altri problemi. Di molti ci si accorge solo in fase di
testing (e si provvede a correggerli). Altri potrebbero addirittura essere completamente
imprevedibili. Ad esempio, se il programma fosse stato scritto prima dell'avvento del
formato per i nomi dei files a 255 caratteri, non ci saremmo certamente preoccupati di
poter gestire stringhe così lunghe.
In casi patologici come questi... ciccia. Il buon senso di un programmatore dovrebbe
essere ben desto in ogni momento e questo in genere aiuta in molte situazioni, ma di certo
non si può essere infallibili.
E fin qui nessun accenno al Delphi, come è giusto che sia.
Operazioni Preliminari
La prima cosa che vi verrebbe in mente di fare... ne sono sicuro... è
piazzare il pulsante che avvia l'algoritmo sulla finestra, cambiarne il testo mostrato in
'Rinomina' ed cominciare ad usare tutti i 256 colori nella vostra finestra.
E' l'errore più comune: iniziare dall'interfaccia grafica.
Non che non si possa cominciare a lavorare su di essa per prima cosa (o che non sia bella
una interfaccia grafica 'friendly'), ma per un principiante è una impostazione che può
portare facilmente fuori strada. Durante la creazione del programma infatti sarà
difficile concentrarsi sull'algoritmo centrale da eseguire e non lasciarsi 'distrarre' dai
vari componenti che lo dovranno implementare. Con il risultato che avrete fatto un
programma per niente OO e se vi venisse in mente di cambiarne una parte vi accorgereste
che fareste più presto riscriverlo daccapo. Noi invece continuiamo a seguire il nostro
approccio OOP che spinge a creare dei gusci successivi nei quali contenere i vari strati
della nostra applicazione.
Abbiamo appena fatto un bel salto: dal guscio più esterno, nel quale vedevamo la nostra
applicazione perfettamente funzionante a quello più interno nel quale non abbiamo
niente... solo il giravite e la pinza.
Cominceremo dunque a scrivere il cuore del programma, la procedura di
rinomina. Vedremo in seguito che sarà poi un gioco da ragazzi (davvero la cosa meno
difficile) collegare questa procedura ad i controlli che ne permettono l'esecuzione
(proprio come giocare con le costruzioni). Aggiungo appena che questa cosa dovrebbe
invitarci a riflettere sulla seguente questione: se l'interfaccia grafica è l'ultima
parte del nostro programma ed impegna poco tempo, non sarà difficile un giorno adattare
il codice scritto ad un altro ambiente o portarlo in Dos che controlli non ne ha (cosa un
po' improbabile, ma pur sempre fattibile) o su Mac i cui controlli differiscono da quelli
di Windows. Basterà disporre nei vari ambienti solo di un compilatore Pascal (il guscio
più interno nel quale ci troviamo attualmente) e progettare di volta in volta solo
l'interfaccia grafica.
Nota: ci sono linguaggi detti multipiattaforma per i quali
non è necessario neanche riprogrettare l'interfaccia grafica. I programmi sviluppati con
tali linguaggi si 'appoggiano' ad un interfaccia di basso livello. Ed è solo questa
interfaccia che è stata realizzato una volta per tutte su ciascuna architettura (software
o hardware), cosicché ogni programma possa essere facilmente trasportato sui vari
sistemi. Uno di questi linguaggi è Java.
Iniziamo davvero.
Apriamo quindi Delphi. Ci appare sul monitor la finestra principale di Delphi con gli
Speed Button (familiari agli utenti Windows) ed una parte dei (un po' meno familiari)
componenti disponibili. Sulla sinistra c'è l'Object Inspector che impareremo ad usare
più tardi e la finestra vuota del vostro programma (in Delphi una finestra come questa si
chiama Form). Sotto la Form, un editor di testo un po' particolare. Se
scorrete il testo in esso contenuto potrete notare un po' di codice che Delphi ha scritto
per voi. E' la unit che è destinata a contenere tutto (e possibilmente solo)
quello che la nostra form è e sa fare. Non preoccupatevi troppo se qualcosa non vi è
chiaro, lo sarà al più presto.
Potete notare la sezione di interfaccia della unit che contiene innanzitutto la clausola
di utilizzo di alcune altre unit contenute nel pacchetto Delphi. C'è poi la definizione
di tipo della vostra form (un po' scarna in questo momento) con una sezione destinata a
dichiarare i suoi campi privati e pubblici. Poi c'è la clausola di
istanziazione di una variabile appartenente al tipo appena definito e quindi, di seguito,
la sezione implementazione vuota (a parte uno strano codice tra parentesi graffe... non
preoccupatevi... non morde). Se provate a salvare il progetto (magari con il nome
'Primo') e la unit allegata ad esso (magari con il nome 'UMain') ed a compilare e lanciare
il programma noterete che esso parte regolarmente pur se mostra una semplice form vuota di
nessuna utilità. Se provate poi ad andare nella directory in cui è presente il vostro
progetto troverete anche il file eseguibile (che reca lo stesso nome del progetto:
'Primo.exe') che può essere fatto girare su qualunque calcolatore (con grande
soddisfazione di tutti). Anche se probabilmente non ve ne siete accorti, sappiate che
Delphi ha già fatto tanto lavoro per voi permettendovi di comunicare al sistema operativo
(Windows) di creare e lanciare una applicazione, creare e visualizzare una finestra con
certe caratteristiche, in una certa posizione, con un certo titolo, con certe dimensioni,
etc., ed i programmatori Windows sanno bene quanto queste operazioni siano noiose.
Ecco spiegato perché le dimensioni dell'eseguibile sono di ben 200KB circa anche se esso
non serve a molto.
Le altre caratteristiche di Delphi le dovreste imparare a conoscere col tempo e magari con
un po' di sperimentazione.
Scrittura del codice
Ritorniamo quindi a concentrarci sul nostro problema. Dobbiamo creare la
procedura che rinomini i files.
Chiamiamola 'RinominaFiles'. Come chiunque conosca un po' di Pascal sa, a questa procedura
bisogna passare dei parametri che indichino ad essa su quali dati lavorare. Quali
parametri passereste voi a questa procedura?
Non è sempre facile individuare subito quali sono i giusti parametri da passare ad una
procedura (o funzione), capita spesso che si passi più di quanto serva, e ciò che eccede
viene usato male.
Forse, essendo agli inizi, il modo migliore per individuare subito cosa passare è
immaginare di avere a che fare con una persona un po' nervosa e sfaticata, ma istruita sul
da farsi, ed incaricarla di realizzare il lavoro che la vostra procedura dovrebbe fare.
Cosa dareste voi a questa persona in modo da permettergli di lavorare correttamente? In
questo caso vi dovrebbe venire spontaneo risponedere: un foglio con la lista dei nomi dei
files da rinominare e la maschera. Questa persona, da quel momento in poi può accendere
il vostro calcolatore, cercare ad uno ad uno i files e cambiarne il nome di ciascuno in
quello giusto. Non ci dovrebbe essere possibilità di errore. In qualunque momento
vogliate che lui agisca, non dovrete fare altro che dargli il fogliettino e la maschera e
lui lo farà.
Di certo non fareste una cosa buona se gli diceste: vatti a prendere il nome dei files
dalla tale ListBox presente nella finestra del programma e rinominali. Quale errore
commettereste infatti? Sono certo che abbiate già indovinato. Avreste fatto l'ipotesi che
ci sia una listBox nella finestra e perfino che ci sia una finestra, cosa non necessaria.
Perché mai uno che è incaricato di rinominare i file dovrebbe sapere cosa sono le
ListBox o trattare con le finestre? Gli si chiederebbe qualcosa di non strettamente
necessario. E se un giorno il programma dovesse essere passato su DOS (per un arcano
motivo) bisognerebbe cambiare la procedura per lavorare senza finestre pur dovendo essa
fare perfettamente le stessa cosa.
Certo ci sarebbe il vantaggio di passare, piuttosto che una lista di nomi (che è pur
sempre un tipo strutturato, anzi, addirittura un oggetto), il puntatore
alla finestra (il suo nome in altre parole), molto più semplice da fare. Ed è questa la
tentazione che vi ingannerebbe e vi porterebbe sulla falsa via.
Per concludere potremmo trarre questa legge di carattere generale: nel progetto di una
procedura è bene non fare delle ipotesi se non si è sicuri che queste siano sempre
verificate.
Nota: bisogna aggiungere anche che è tuttavia
stupido non fare delle assunzioni che invece si sa che sussisteranno sempre. Ad esempio,
nella creazione di un programma che contenga un calendario, optare per una struttura
statica (facile da usare) a 12 locazioni per contenere i nomi dei mesi è abbastanza
ragionevole. Certo con un po' di sforzo in più si potrebbe implementare una struttura
dinamica per permettere anche di variare il numero dei mesi, ma quando si userebbe questa
caratteristica addizionale? Anche se non si possono mettere dei limiti alla provvidenza,
non bisogna neanche torturarsi inutilmente.
La chiamata alla procedura di rinomina sarà del seguente tipo:
RinominaFiles(ListaFiles, Maschera); |
Bene.
Ora dobbiamo scegliere il tipo dei parametri da passare. Direi che per il secondo
(Maschera) non ci sono dubbi: il tipo String. Per il primo parametro sarebbe
l'ideale una struttura dati dinamica capace di mantenere una lista di stringhe con le
usuali operazioni di aggiunta ed eliminazione. Se disponessimo di un compilatore Turbo
Pascal (nella versione 6.0, per esempio) a questo punto ci dovremmo armare di santa
pazienza e dovremmo costruire questa struttura introducendo un tipo Object
destinato allo scopo. In Delphi invece è già bello e pronto da usare: si chiama TStrings
ed è più precisamente una classe. Essa permette proprio la manipolazione di una lista di
stringhe e la loro gestione attraverso molti metodi (Add, Delete, Change, Exchange,
etc.). La sua definizione è contenuta nella unit Classes allegata al Delphi.
Permettiamoci ancora un'ultima piccola aggiunta.
Si potrebbe passare alla procedura anche un numero intero dal quale cominci il conteggio
per i nuovi nomi dei files. In questo modo si potrebbe ottenere non solo la sequenza File1.ext,
File2.ext, ... ma anche File100.ext, File101.ext, .... Questo parametro
sarà banalmente un Integer (e lo chiamiamo BaseInt)..
A questo punto possiamo definire la procedura:
procedure RinominaFiles(ListaFiles: TStrings; Maschera: String; BaseInt: Integer); |
Non ci resta che implementarla.
L'implementazione è qualcosa di abbastanza individuale; ognuno implementerebbe questa
procedura nel modo che crede, purché funzioni. Questo però non deve ripercuotersi
sull'andamento del programma proprio perché una volta creata l'interfaccia della
procedura abbiamo univocamente definito il suo comportamento. Io quindi vi presenterò una
possibile soluzione.
procedure RinominaFiles(ListaFiles: TStrings; Maschera: String ; BaseInt: Integer);
const
Marker = '#';
var
MarkerPos: Integer;
NewName: String;
i, j: Integer;
Dir: String;
Ext: String;
begin
//Ricerco il Marker e se c'e' lo cancello memorizzandone la posizione
MarkerPos := Pos(Marker, Maschera);
if (MarkerPos > 0) then
Delete(Maschera, MarkerPos, Length(Marker));
//Rinomina dei files
j := 0;
for i := 0 to ListaFiles.Count - 1 do
begin
Dir := ExtractFilePath(ListaFiles[i]);
Ext := ExtractFileExt(ListaFiles[i]);
repeat
NewName := Maschera;
if (MarkerPos > 0) then
begin
Insert(IntToStr(BaseInt + j), NewName, MarkerPos);
NewName := Dir + NewName + Ext;
end else
NewName := Dir + Maschera + IntToStr(BaseInt + j) + Ext;
Inc(j);
until not FileExists(NewName);
RenameFile(ListaFIles[i], NewName);
ListaFiles[i] := NewName; //Attenzione a questa riga
end;
end;
|
La procedura è abbastanza semplice.
Nella prima parte si provvede a cancellare il marker dalla maschera memorizzandone la
posizione che dovrà essere occupata dal numero progressivo. La funzione 'Pos' ricerca una
sottostringa in una stringa e ne ritorna la posizione. Se la sottostringa non dovesse
essere presente il risultato restituito è 0.
Nella seconda parte si calcola il nuovo nome, si controlla se esiste già un file con quel
nome e (se non esiste) lo si applica al file relativo ad una delle righe della lista
oppure (se esiste) lo si cambia con il successivo. Ci siamo serviti delle procedure ExtractFilePath,
ExtractFileExt, FileExists, RenameFile tutte contenute nella unti SysUtils.
Quando il programma sarà completo e potrà 'girare', potremo lasciare svanire eventuali
dubbi sul suo corretto funzionamento andandola ad analizzare riga per riga, mentre
funziona. L'IDE permette infatti al programmatore in fase di debug di eseguire il
programma una riga dopo l'altra con la possibilità di andare a sbirciare in ogni momento
il valore di cisacuna delle variabili. Basterà posizionare un breakpoint (con un click
sulla parte sinistra dell'editor, nel gutter, in corrispondenza della riga)
sulla prima riga della procedura ed avviare il programma. Non appena quella riga verrà
eseguita si arresterà l'esecuzione del programma e da quel momento in poi potrà essere
eseguita una riga per volta attraverso la pressione del tasto F7. Inoltre posizionandosi
con il mouse su una variabile si vedrà apparire il suo valore in un piccolo riquadro
giallo.
Questa procedura non aggiunge molto alle conoscenze di un programmatore Pascal con un
minimo di esperienza, anche se lascia intravedere l'uso di una classe presente in Delphi
(la TStrings) e la presenza di un po' di comode funzioni di sistema.
Una cosa su cui va messo l'accento è la presenza dell'ultima riga della procedura che
provvede a sostituire alla riga della lista il nuovo nome (con percorso completo) del file
che era presente in quella riga. Quindi all'interno della procedura la lista viene
alterata.
Quest'alterazione è visibile anche all'esterno?
Il fatto che il particolare parametro sia stato scambiato per valore (e non per
riferimento), che non ci sia la clausola var, in parole povere, vi
potrebbe indurre ad affermare che questa modifica resti isolata all'interno della
procedura e non faccia sentire i suoi effetti fuori di essa. In questo caso però non è
così. Infatti, come già detto, la TStrings è una classe e la istanza di una classe fa
riferimento non alla vera e propria struttura dati presente in memoria (come in C++) ma al
puntatore alla struttura. Quindi si capisce che passare il puntatore per valore o per
variabile, in questo caso, non fa nessuna differenza visto che esso non viene modificato,
la parte di memoria su cui andiamo a lavorare è sempre la stessa (sia fuori che dentro la
procedura) e non c'è alcuna duplicazione di dati (come i programmatori Pascal sanno
avvenire in uno scambio di dati per valore). Le modifiche apportate all'interno della
procedura saranno visibili anche fuori di essa. Questa caratteristica, come vedremo, ci
farà comodo e quindi lasciamo tutto così com'è. Quella appena evidenziata è una
caratteristica peculiare del Delphi (ed in particolare del tipo class),
nota come "adozione del modello ad oggetti per riferimento".
Il cuore del programma è pronto. Nessun'altra azione elaborativa deve essere svolta
durante il programma e quindi a quanto pare non ci resta che occuparci dell'interfaccia
con l'utente.
L'interfaccia grafica
Eccoci finalmente al momento che voi tutti stavate aspettando.
Abbiamo davanti a noi la form vuota ed il relativo codice contenente solo la procedura RinominaFiles
(oltre che tutto quello che Delphi aveva inserito). Bisogna ora fare in modo da creare
un'interfaccia grafica che permetta di preparare i dati da fornire alla procedura
cosicché essa possa funzionare correttamente.
Sulla form ci dovranno presumibilmente essere anche dei pulsanti che permettano di avviare
l'operazione di scelta dei files ed altre operazioni che vedremo. Cominciamo quindi subito
con il posizionare sulla form un pannello destinato ad accogliere questi pulsanti.
Selezioniamo quindi il componente Panel dalla paletta dei componenti Standard
facendo click su di esso. Subito dopo facciamo click sulla form vuota. Il pannello verrà
depositato sulla form. Come prima cosa diamo un nome con un minimo di significato al
pannello (cosa importante specialmente quando sulla form cominciano ad esserci parecchi
componenti). Andiamo perciò nell'Object Inspector dove troviamo una serie di proprietà
relative al pannello (ed, in genere, all'oggetto correntemente selezionato). Scegliamo la
proprietà Name ed impostiamone il valore a PTools. Continuiamo poi con
l'adattare le caratteristiche del pannello al suo uso. Scegliamo quindi la proprietà Align
e impostiamone il suo valore a alTop. Immediatamente il pannello verrà allineato
sul bordo superiore della form. A questo punto regoliamone anche l'altezza ad un valore
tale da poter ospitare dei piccoli bottoni. Possiamo sia intervenire manualmente con il
mouse selezionando il componente e ridimensionandolo oppure andare ancora nell'Object
Inspector e modificare la sua proprietà Height, per esempio, a 40. Infine
modifichiamo la sua proprietà Caption eliminando il testo in essa presente così
che il pannello non mostri alcun testo ed impostiamo la proprietà BevelOuter a bvNone.
L'effetto delle modifiche è già visibile a Design-time.
Pensiamo ora al componente che dovrà memorizzare e visualizzare la lista dei files da
rinominare. Quello che meglio si adatta a questo compito è la ListBox, presente
sempre nella stessa pagina della component palette, ed è un altro componente di
certo a voi molto familiare poiché fa parte dell'Object Windows (i componenti
Delphi che incapsulano i componenti standard di Windows si chiamano wrapper). Con
lo stesso procedimento di prima posizioniamo la ListBox sulla form (attenzione a
non fare click sul pannello per depositare la ListBox, ma sull'area libera della
form). Diamole il nome LBFiles attraverso la modifica della sua proprietà Name.
Facciamo poi in modo che la ListBox occupi tutto il resto dell'area della form
impostando la sua proprietà Align al valore alClient.
Se ora andiamo a dare uno sguardo al sorgente Delphi ci rendiamo conto che le aggiunte
apportate hanno provocato delle modifiche anche nel codice. Troveremo in particolare nella
definizione di tipo della form i due componenti aggiunti nella parte che precede l'area
destinata a contenere le dichiarazioni private della form. Durante la vostra carriera di
programmatori Delphi imparerete a capire a cosa serva quella area. Per ora vi basti sapere
che dovrebbe essere solo Delphi a scriverci dentro, e non voi.
Aggiungiamo ora i controlli necessari alle operazioni: apertura dei files, eliminazione
dei files dalla ListBox, contenimento della maschera e contenimento del valore
iniziale. Per non farla troppo lunga schematizziamo in tabella i componenti da aggiungere
e le sole proprietà che vanno modificate. Aggiungeteli voi uno ad uno coerentemente con
quanto scritto.
| Uso | Tipo | Pagina della paletta | Nome | Proprietà |
| Scelta dei files | TSpeedButton | Additional | SBApriFiles | Hint: Apri i files |
| Eliminazione dei files dalla ListBox | TSpeedButton | Additional | SBEliminaFiles | Hint: Elimina tutti i fles |
| Contenimento della maschera | TEdit | Standard | EMaschera | Hint: Carattere '#' al posto del numero Width: 120 Text: vuoto |
| Contenimento dell'intero iniziale | TSpinEdit | Samples | SEBaseInt | Width: 60 |
| Avviamento dell'operazione | TButton | Standard | BRinomina | Caption: Rinomina |
Aggiungiamo anche sopra ai componenti EMaschera ed SEBaseInt delle Labels
che possono essere prelevate dalla pagina Standard e che informino l'utente
dell'uso dei rispettivi componenti.
Per fare in modo che i piccoli suggerimenti nel riquadro giallo impostati appaiano è
necessario impostare la proprietà ShowHint di ciascun componente a True
oppure lasciare la proprietà ParentShowHint attivata per tutti ed impostare
solamente la proprietà ShowHint della form su True.
Nota: Per selezionare la form (in modo da poter impostare le sue proprietà) quando non c'è area libera in essa, come in questo caso, è sufficiente selezionare un componente a piacere e poi rifare click su du esso tenendo premuto il tasto Shift.
Se selezionate uno dei due pulsanti, provate ad andare sulla sua proprietà Glyph
e fate click sul pulsante contenente i tre puntini, vedrete apparire un editor di
proprietà che vi permetterà di caricare un file immagine. Potrete scegliere quelli più
adatti allo scopo magari cercando sotto la directory images/buttons presente
nella directory di installazione di Delphi.
Se provate ora ad avviare l'applicazione vedrete apparire sullo schermo una finestra proprio uguale a quella che abbiamo disegnato, con tutti i componenti funzionanti. Di certo ancora non eseguono il lavoro richiesto (sarebbe troppo bello), ma i bottoni rispondono ai click del mouse, la finestra di Edit accetta testo (ed ha perfino un menù popup funzionante) e la SpinEdit funziona anch'essa nel modo corretto. Ancora una volta Delphi ha fatto un po' di lavoro al posto vostro.
Cominciamo allora a collegare il pulsante SBApriFiles. Dobbiamo fare in modo
che quando l'utente fa click su di esso si apra una finestra standard che permetta di
scegliere un file. Un componente che incapsula questa Dialog Form si trova nella
pagina Dialogs sotto il nome OpenDialog. Prelevatelo dalla component
palette e riponetelo sulla form dove volete. Esso è un componente non visuale, vale a
dire che è visibile solo al design-time ma non durante l'esecuzione del
programma. Sarà però sempre disponibile ad eseguire le sue funzioni, in questo caso
quella di fare apparire la form sullo schermo (attraverso il suo metodo Execute)
e permetterne il corretto utilizzo.
Per permettere la selezione multipla dei file impostiamo a True il flag ofAllowMultiSelect
presente sotto la proprietà Options del componente.
Ora selezioniamo il pulsante OpenDialog1 e nell'Object Inspector dopo aver
richiamato la pagina Events facciamo doppio-click sull'evento OnClick.
Vi apparirà subito una procedura (meglio, un metodo) vuota che dovremo riempire con del
codice. Esso verrà eseguito quando a run-time viene premuto il pulsante. Il metodo è
riportato in tabella.
procedure TForm1.SBApriFilesClick(Sender: TObject);
var
x: Integer;
begin
with OpenDialog1 do
begin
if Execute then
begin
for x := Files.Count - 1 downTo 0 do
if (LBFiles.Items.IndexOf(Files[x]) = -1) then
LBFiles.Items.Add(Files[x]);
end;
end;
end;
|
Attraverso la chiamata del metodo Execute come anticipato il componente
visualizza la form di dialogo in modalità modal. L'esecuzione del programma,
cioè, si arresta fino alla chiusura della form. A questo punto attraverso la proprietà Files
(che è, guarda caso, della classe TStrings) si prelevano ad uno ad uno (in
ordine inverso, perché? :) ) i files selezionati dall'utente e si aggiungono alla lista.
Per evitare che lo stesso file appaia due volte nella lista si provvede ad effettuare un
controllo prima dell'inserimento. Il metodo IndexOf della classe TStrings
consente infatti di trovare l'indice della stringa desiderata (ottenendo come risultato -1
se la stringa non c'è).
Allo stesso modo nell'evento OnClick del pulsante SBEliminaFiles
associamo il metodo seguente:
procedure TForm1.SBEliminaFilesClick(Sender: TObject);
begin
if (MessageDlg('Eliminare tutti i files dalla lista?', mtConfirmation, [mbYes, mbNo], 0) = mrYes) then
begin
LBFiles.Items.Clear;
end;
end;
|
Infine non ci resta che associare all'evento OnClick del pulsante BRinomina il seguente codice:
procedure TForm1.BRinominaClick(Sender: TObject); begin RinominaFiles(LBFiles.Items, EMaschera.Text, SEBaseInt.Value); end; |
Una sola riga di codice per svolgere l'intero lavoro non è altro che il frutto del lavoro prima svolto e dell'oculata modularizzazione del programma. Non disdegnate mai di perdere qualche minuto in più nelle fasi iniziali perché non è tempo sprecato. E' l'opinione di tutti i programmatori con un minimo di esperienza.
A questo punto non vi resta che avviare il programma e controllare se funziona.
Testing
Con delle prove approfondite vi dovreste accorgere che c'è qualcosa che non va.
Infatti se proviamo a caricare una lista di files e li rinominiamo (con una maschera a
piacere) e subito dopo ripremiamo più volte consecutive il tasto di rinomina possiamo
osservare uno strano effetto.
Prima di tutto cerchiamo di capire bene a cosa è dovuto il problema. Quando ripremiamo
per la seconda volta il tasto rinomina il programma trova come primo file (per esempio) il
file File0.txt. Nell'esecuzione dovrà richiamare il primo file nello stesso
modo, ma quel file già esiste e quindi salta il numero e riprova a rinominare il file.
Come risolvere questo problema? E soprattutto a che livello?
Per il come una possibilità (forse la più semplice) sarebbe quella di dare a
tutti files un nome temporaneo (che si possa verificare con bassissima probabilità) e poi
rinominarli con i giusti nomi. Non dobbiamo preoccuparci di dover effettuare due volte il
lavoro di rinomina poiché oramai la procedura è pronta e non ci costa altra fatica.
Sul dove invece potreste lasciarvi fuorviare ancora dalla voglia di fare in
fretta. Il tempo che risparmiereste adesso lo perdereste più in là... e con gli
interessi maturati nel tempo. :)
Il modo più veloce di risolvere il problema sarebbe quello di sostituire l'unica riga di
codice della procedura OnClick del tasto di rinomina con le seguenti due righe:
procedure TForm1.BRinominaClick(Sender: TObject); begin RinominaFiles(LBFiles.Items, '~TMP~', SEBaseInt.Value); RinominaFiles(LBFiles.Items, EMaschera.Text, SEBaseInt.Value); end; |
In questo modo però continueremmo ad aver scritto una procedura RinominaFiles
che non funziona. E' infatti 'colpa' della procedura se le cose non vanno come dovrebbero.
Ritornando all'esempio della persona incaricata di rinominare i files, se facesse un
errore del genere gli potremmo tranquillamente dare del... tempo per rifletterci su.
Sarebbe stupido dargli due volte la lista dei files da rinominare.
Dobbiamo quindi riuscire ad incapsulare le modifiche nella procedura lasciando inalterata
la sua interfaccia così che niente di quanto già creato debba essere minimamente
modificato.
Una possibile soluzione potrebbe essere la seguente:
procedure RinominaFiles(ListaFiles: TStrings; Maschera: String ; BaseInt: Integer);
procedure BlindRenFiles(ListaFiles: TStrings; Maschera: String; BaseInt: Integer);
const
Marker = '#';
var
MarkerPos: Integer;
NewName: String;
i, j: Integer;
Dir: String;
Ext: String;
begin
//Ricerco il Marker e se c'e' lo cancello memorizzandone la posizione
MarkerPos := Pos(Marker, Maschera);
if (MarkerPos > 0) then
Delete(Maschera, MarkerPos, Length(Marker));
//Rinomina dei files
j := 0;
for i := 0 to ListaFiles.Count - 1 do
begin
Dir := ExtractFilePath(ListaFiles[i]);
Ext := ExtractFileExt(ListaFiles[i]);
repeat
NewName := Maschera;
if (MarkerPos > 0) then
begin
Insert(IntToStr(BaseInt + j), NewName, MarkerPos);
NewName := Dir + NewName + Ext;
end else
NewName := Dir + Maschera + IntToStr(BaseInt + j) + Ext;
Inc(j);
until not FileExists(NewName);
RenameFile(ListaFIles[i], NewName);
ListaFiles[i] := NewName; //Attenzione a questa riga
end;
end;
//RinominaFiles
begin
BlindRenFiles(ListaFiles, '~TMP~', BaseInt);
BlindRenFiles(ListaFiles, Maschera, BaseInt);
end;
|
Abbiamo usato il meccanismo di innesto delle procedure. La nostra vecchia RinominaFiles è diventata una procedura interna (invisibile dall'esterno) scherzosamente chiamata BlindRenFiles (Blind = cieco). Il blocco principale della nuova procedura RinominaFiles richiama due volte BlindRenFiles come avevamo fatto prima. Fuori nessuno si è accorto di niente ed ora tutto funziona per bene.
Nota: personalmente non amo innestare le procedure una dentro l'altra. Anche se è concettualmente corretta questa tecnica mi dà l'impressione di conferire una minore leggibilità al codice. Ci sono altri strumenti che permettono di modularizzare correttamente il programma. In questo caso, per esempio, avrei preferito piuttosto definire una nuova unit, mettere nella sezione implementation entrambe le procedure (non innestate) ed esportare solo la nuova.
Notiamo che con questa modifica abbiamo sfruttato il fatto che la lista di file viene modificata dalla procedura. Se ciò non avvenisse la procedura non funzionerebbe.
A questo punto il programma dovrebbe essere concluso... puff puff.
Nessuno ci assicurerà mai che non ci siano bug pronti a spuntare fuori da un momento
all'altro. La cosa più auspicabile sarebbe quella di farlo mettere alla prova da una
nutrita schiera di utenti e se dopo molto tempo non ci saranno state segnalazioni si può
ragionevolmente pensare che il programma funzioni bene. Una buona probabilità di corretto
funzionamente ci deve soddisfare.
Questa piccola lezione termina qui.
Più che una lezione sull'uso vero e proprio del Delphi, che imparerete solo col tempo e
con tante... tante prove, ho cercato di dare delle linee guida da seguire nella
progettazione di un software, piccolo o grande che sia (uno piccolo potrebbe dover
diventare grande, se 'tira'), e di suggerire un'impostazione (anzi, scusate se delle volte
sono stato un po' assillante). Arrivare ad un buon livello richiede del tempo per
assorbire i concetti, talvolta abbastanza profondi, su cui lo sviluppo di software ruota.
Con l'andar del tempo si è avuto bisogno di software sempre più completi e complessi; è
andata così nascendo una nuova disciplina, l'ingegneria del software che, per
essere davvero giovane, oggi vanta della basi molto solide. In fin dei conti, anche se
pochi lo ammettono, quale sia il linguaggio che si usa di volta in volta non ha
un'importanza fondamentale. Se si possiedono le conoscenze di base bisognerebbe essere in
grado nelle diverse situazioni di cavarsela sempre egregiamente.
Poi... certo... la passione è un'altra cosa e... Delphi è quello che è. :)
| Marcello Esposito - 24 febbraio 1999 v1.02.1 - data ultima modifica: 18/03/01 E-Mail: supix@planet1.it, supix@libero.it |
Ringrazio Lino Califano per avermi suggerito il programma presentato in questa lezione. |