Torna alla mespo's home page

Lezione di DELPHI per principianti


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.

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.

Torna alla mespo's home page