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 SaveDoc : Boolean;
Function SaveDocAs : Boolean;
Property DocCount : Integer read FDocCount;
Property 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.