LOA Hacklab - Corso di Perl - 06/05/2001 ----------------------------------------------------------------------- 1) Intro 2) Win32::GUI Descrizione e reperibilita' Concetti generali I package Oggetti e opzioni La fase dialog e gli eventi Esempi Hello world INTRO ----------------------------------------------------------------------- In questa lezione studieremo le librerie Perl per la creazione di GUI sotto Windows e i protocolli di rete ad alto livello, come SMTP e POP3: lo scopo della lezione e' di aiutarvi a creare piccole applicazioni Windows che vi permettano di collegarvi alla vostra casella di posta elettronica, di vedere una preview dei vostri nuovi messaggi, di inviare e-mail e cosi' via. Win32::GUI ----------------------------------------------------------------------- Descrizione e reperibilita' --------------------------- Win32::GUI e' un toolkit per Perl che consente di creare GUI per Win32. Esso consiste in un'implementazione della maggior parte delle funzioni che si trovano all'interno di user32.dll e di gdi32.dll, con un'interfaccia perl orientata agli oggetti e un'architettura a eventi simile a quella di Visual Basic. Potete scaricare il file Win32-GUI-0.0.558.tar.gz (ultima versione disponibile, del 22/01/2001, 407K) dall'homepage "dada's perl lab", all'indirizzo http://dada.perl.it. Concetti generali ----------------- *** I package I package di Win32::GUI possono essere divisi in tre categorie principali: finestre, controlli e risorse. Questi tre termini hanno la stessa accezione che viene utilizzata normalmente in Windows: le finestre sono i "contenitori" dei vostri programmi; i controlli sono praticamente tutti gli oggetti che trovate all'interno di una finestra, come ad esempio i pulsanti, le caselle di testo, le etichette e cosi' via; le risorse sono oggetti di tipo diverso, come ad esempio i font, le icone e i cursori del mouse, e possono essere associati alle finestre o ai controlli (ad esempio, potete impostare un particolare font per una finestra o per un controllo). Ci sono due altri package che non possono essere compresi all'interno delle categorie descritte: essi sono Win32::GUI::Class, utilizzato per creare nuovi oggetti di classe Window, e Win32::GUI::DC, che consente di gestire l'aspetto grafico "a basso livello" delle applicazioni Windows (colorazione di pixel, creazione di forme geometriche e cosi' via). Nonostante Win32::GUI presenti tanti oggetti diversi, tutti i componenti dell'interfaccia grafica ereditano dal package Win32::GUI stesso e tutte le funzioni di carattere generale sono definite al suo interno. Questo significa che la maggior parte delle funzioni che si riferiscono alle finestre, come ad esempio Show, Hide, Height, Enable e cosi' via, possono essere utilizzate per ogni altro oggetto come loro metodi. Notate tuttavia che non tutte le funzioni possono essere utilizzate in questo modo: alcune, come ad esempio GetCursorPos, non possono essere applicate ad alcun oggetto e richiedono una chiamata completa del nome del package: ($x, $y) = Win32::GUI::GetCursorPos (); Giusto per permettervi di farvi un'idea su cosa potete effettivamente creare con Win32::GUI, ecco ora un breve elenco dei package (divisi per categoria) che potete usare all'interno dei vostri script in perl: Finestre: Window DialogBox Controlli: Animation Button Checkbox Combobox Label ListView Listbox NotifyIcon ProgressBar RadioButton Rebar RichEdit Slider StatusBar TabStrip Textfield Timer Toolbar Tooltip TreeView UpDown Risorse: Bitmap Brush Cursor Font Icon ImageList Menu MenuButton MenuItem Pen Altri: Class DC *** Oggetti e opzioni Potete creare un oggetto utilizzando il loro costruttore, che e' solitamente del tipo: $oggetto = new Win32::GUI::( ); dove e', appunto, il nome della classe dell'oggetto che desiderate creare e e' l'elenco di opzioni che desiderate associare all'oggetto. Una volta creata la variabile $oggetto, potete richiamarne i metodi in questo modo: $oggetto->Metodo(); Se state lavorando con dei controlli potete usare un approccio differente e decisamente piu' elegante: se, ad esempio, desiderate aggiungere un pulsante all'interno di una finestra, potete usare il metodo $finestra->AddButton( ); che vi consente di creare un "sotto-oggetto" all'interno dell'oggetto $finestra al quale potete riferirvi utilizzando il nome specificato tramite l'opzione speciale "-name" in fase di creazione dell'oggetto. Ad esempio, tornando al nostro pulsante: $finestra->AddButton( -name => "Pulsante1" ); $finestra->Pulsante1->Show(); #o $finestra->{'Pulsante1'}->Show(); Crea il pulsante all'interno della nostra finestra e lo visualizza. Notate il secondo metodo descritto per riferirci all'oggetto pulsante: esso e' OBBLIGATORIO qualora decidiate di creare un oggetto il cui nome corrisponde a una funzione di Win32::GUI. Se, ad esempio, volete chiamare il pulsante "Show" (con tanti nomi a disposizione, proprio quello dovete scegliere?), dovrete scrivere $finestra->{'Show'}->Show(); Naturalmente, cosi' come potete aggiungere un pulsante all'interno di una finestra con il comando AddButton, potete utilizzare metodi analoghi per aggiungere ogni altro tipo di componente: l'elenco completo di comandi e' il seguente AddAnimation(%OPTIONS) AddButton(%OPTIONS) AddCheckbox(%OPTIONS) AddCombobox(%OPTIONS) AddGroupbox(%OPTIONS) AddHeader(%OPTIONS) AddLabel(%OPTIONS) AddListbox(%OPTIONS) AddListView(%OPTIONS) AddMenu() AddNotifyIcon(%OPTIONS) AddProgressBar(%OPTIONS) AddRadioButton(%OPTIONS) AddRebar(%OPTIONS) AddRichEdit(%OPTIONS) AddSlider(%OPTIONS) AddStatusBar(%OPTIONS) AddTabStrip(%OPTIONS) AddTextfield(%OPTIONS) AddTimer(NAME, ELAPSE) AddToolbar(%OPTIONS) AddTreeView(%OPTIONS) AddUpDown(%OPTIONS) Si parlava, in precedenza, di _opzioni_: ebbene, queste sono tutte le diverse caratteristiche che fanno di un oggetto quello che e', come ad esempio il nome, le sue dimensioni, la posizione e cosi' via. La definizione delle opzioni avviene tramite un hash, all'interno del quale i nomi delle opzioni sono sempre in minuscolo e sono preceduti da un trattino, mentre il loro valore puo' essere di tipi differenti. Le opzioni comuni a tutti gli oggetti sono le seguenti: -background => COLOR background color -class => CLASS classe della finestra -- non fate casini :) -disabled => 0/1 stato del controllo (default abilitato) -font => FONT font associato al controllo -foreground => COLOR foreground color -group => 0/1 TBD -height => NUMBER altezza in pixel dell'oggetto -left => NUMBER coordinata orizzontale (assoluta o relativa) -menu => MENU oggetto menu associato alla finestra -name => STRING nome dell'oggetto: IMPORTANTISSIMO!!! -style => NUMBER TBD -tabstop => 0/1 selezionabile con TAB in una dialog Box -text => STRING Testo associato al controllo (titolo o contenuto) -title => STRING sinonimo x text -top => NUMBER coordinata verticale (assoluta o relativa) -visible => 0/1 stato di visibilita' del controllo (def. 1) -width => NUMBER larghezza in pixel dell'oggetto Come avrete notato dalla sua descrizione, il parametro "-name" e' fondamentale per ogni oggetto: esso, infatti, non solo e' necessario per riferirsi a un particolare sotto-oggetto creato con il comando Add*, ma anche (come vedrete meglio in seguito) per riferirsi a tutti gli eventi che sono legati a quell'oggetto. Ad esempio, la funzione che viene chiamata quando fate clic su un pulsante si chiama _Click. Analizzeremo in dettaglio gli eventi fra poche righe. Ogni oggetto, oltre alle opzioni comuni a tutti quanti, ha una serie di opzioni specifiche: ad esempio, un pulsante puo' avere anche -align => left/center/right (default left) -valign => top/center/bottom -default => 0/1 (default 0) -ok => 0/1 (default 0) -cancel => 0/1 (default 0) -bitmap => Win32::GUI::Bitmap object -picture => see -bitmap -icon => Win32::GUI::Icon object Per gli altri oggetti, potete consultare direttamente la documentazione di Win32::GUI sul sito o fornita direttamente con il pacchetto. *** La fase dialog e gli eventi Durante la descrizione di Win32::GUI si era parlato di una "architettura ad eventi simile a quella di Visual Basic": cosa significa quest'affermazione? Se per caso avete gia' provato a utilizzare Visual Basic, sicuramente avete gia' capito il senso di queste parole e vi sta colando dell'acquolina dalla bocca... in caso contrario eccovi una breve spiegazione (che sicuramente non manchera' di farvi scendere acquolina dalla bocca!). Uno script Perl event-driven (non so se esiste il termine, ma suonava figo) presenta un funzionamento di questo tipo: - esegue un certo numero di istruzioni, dall'inizio dello script fino a un punto particolare specificato dal comando Win32::GUI::Dialog() - a questo punto l'esecuzione del codice Perl si arresta e il programma entra in un ciclo, all'interno del quale controlla il verificarsi di tutti gli eventi possibili legati a tutti gli oggetti presenti nel programma - se si verifica uno degli eventi descritti, viene chiamata automaticamente la funzione NomeOggetto_NomeEvento. Questo e' uno dei motivi per cui il parametro "-name" durante la creazione di un oggetto e' assolutamente fondamentale! - la fase ciclica termina quando uno degli eventi restituisce il valore -1 oppure quando si verifica un errore e voi premete il pulsante Annulla nella finestra di dialogo che appare. A questo punto, l'esecuzione del codice Perl prosegue dalla riga successiva all'istruzione Win32::GUI::Dialog() Grandioso, no? Voi non dovete preoccuparvi di effettuare il controllo di tutti gli eventi, ma semplicemente prevedere quelli che ritenete interessanti e scrivere il codice ad essi relativo... moolto Visual Basic, pero' con tutti i vantaggi di Perl! Ancora due piccole note... Prima di tutto, il comando Win32::GUI::Dialog vi restituisce un valore all'uscita: quindi, se volete verificare qual e' il valore ritornato all'uscita dal ciclo potete scrivere, ad esempio, $exitcode = Win32::GUI::Dialog(); La seconda nota riguarda proprio i valori restituiti dalle subroutine che vengono associate agli eventi. Tali valori, infatti, possono essere di tre tipi diversi: 1: la fase dialog continua e l'evento viene passato all'event processor 0: la fase dialog continua ma l'evento non viene passato all'event processor -1: la fase dialog finisce e il controllo ritorna al vostro programma, a partire dalla riga successiva al comando Win32::GUI::Dialog. Ad esempio, per terminare il vostro programma userete codice di questo tipo: sub Window_Terminate { return -1; } Cosa significa tutto questo? Proviamo a fare un esempio banale con la sub Window_Terminate, ritornando i tre diversi valori (se volete provarlo in pratica, usate uno degli esempi descritti in seguito): 1: La finestra viene chiusa regolarmente, ma il programma non esce dalla fase dialog. Se il programma e' costituito da una sola finestra, una volta chiusa non possiamo piu' fare nulla. 0: La finestra non viene chiusa e la fase dialog continua regolarmente. -1: La finestra viene chiusa e il programma, nel caso piu' banale, finisce regolarmente. Questo ci fa anche capire il significato di event processor: esso non e' altro che una funzione predefinita che viene eseguita comunque, a meno che non si ritorni il valore 0 dalla nostra sub. Questo significa che voi non vi dovrete mai preoccupare di definire le operazioni standard legate agli oggetti: in particolare, non dovrete mai chiudere "a mano" una finestra, ma vi bastera' restituire un valore diverso da 0 per fare in modo che l'event processor relativo all'evento Window_Terminate la chiuda da solo. Quando usare i vari valori? 1: Avete un programma che apre piu' finestre, ma naturalmente volete che esso termini solo quando chiudete la finestra principale. Allora, per tutte le altre, alla fine della sub windowXX_Terminate ritornate il valore 1. 0: Per qualche particolare motivo, volete fare un override delle operazioni predefinite relative a un oggetto. Ad esempio, quando terminate una finestra volete prima verificare se i dati generati dal programma sono stati salvati e, in caso negativo, NON chiuderla. A questo punto vi basta effettuare un semplice controllo (di un flag, ad esempio) e ritornare il valore 0 se non e' stato effettuato il salvataggio, 1 in caso contrario. -1: Volete uscire dal programma :) Esempi ------ *** Hello world use Win32::GUI; $main = Win32::GUI::Window->new( -name => 'Main', -width => 100, -height => 100, ); $main->AddLabel(-text => "Hello, world"); $main->Show(); Win32::GUI::Dialog(); sub Main_Terminate { return -1; } *** Hello world (potenziato) use Win32::GUI; $text = defined($ARGV[0]) ? $ARGV[0] : "Hello, world"; $main = Win32::GUI::Window->new(-name => 'Main', -text => 'Perl'); $label = $main->AddLabel(-text => $text); $ncw = $main->Width() - $main->ScaleWidth(); $nch = $main->Height() - $main->ScaleHeight(); $w = $label->Width() + $ncw; $h = $label->Height() + $nch; $main->Resize($w, $h); $main->Show(); Win32::GUI::Dialog(); sub Main_Terminate { -1; } *** Hello world (versione finale) use Win32::GUI; # leggo la stringa da visualizzare dalla riga di comando $text = defined($ARGV[0]) ? $ARGV[0] : "Hello, world"; # creo la finestra $main = Win32::GUI::Window->new( -name => 'Main', -text => 'Perl', ); # creo il font $font = Win32::GUI::Font->new( -name => "Comic Sans MS", -size => 24, ); # aggiungo 1 etichetta alla finestra $label = $main->AddLabel( -text => $text, -font => $font, -foreground => [255, 0, 0], ); # calcolo lo spazio "non client" (bordi &co.) della finestra $ncw = $main->Width() - $main->ScaleWidth(); $nch = $main->Height() - $main->ScaleHeight(); # larghezza e altezza finali della finestra = dimensioni # dell'etichetta + spazio non client $w = $label->Width() + $ncw; $h = $label->Height() + $nch; $main->Change(-minsize => [$w, $h]); $main->Resize ($w,$h); $main->Show(); Win32::GUI::Dialog(); sub Main_Terminate { -1; } sub Main_Resize { my $w = $main->ScaleWidth(); my $h = $main->ScaleHeight(); my $lw = $label->Width(); my $lh = $label->Height(); $label->Left(int(($w - $lw) / 2)); $label->Top(int(($h - $lh) / 2)); } *** Altri oggetti use Win32::GUI; # leggo la stringa da visualizzare dalla riga di comando $text = defined($ARGV[0]) ? $ARGV[0] : "Hello, world"; # creo la finestra $main = Win32::GUI::Window->new( -name => 'Main', -text => 'MaLa', ); $label = $main->AddLabel( -text => $text, ); $textfield = $main->AddTextfield ( -name => 'Text', -text => $text, -width => 100, -height => 20, -top => $label->Height(), ); $lw=$label->Width(); $tw=$textfield->Width(); if ($lw>$tw-10) { $textfield->{-width}=$lw+10; } # calcolo lo spazio "non client" (bordi &co.) della finestra $ncw = $main->Width() - $main->ScaleWidth(); $nch = $main->Height() - $main->ScaleHeight(); # larghezza e altezza finali della finestra = dimensioni # dell'etichetta + spazio non client $w = $label->Width() + $ncw + 10; $h = $label->Height()+$textfield->Height() + $nch; $main->Change(-minsize => [$w, $h]); $main->Resize ($w,$h); $main->Show(); Win32::GUI::Dialog(); sub Main_Terminate { -1; } sub Text_Change { $label->{-text} = $textfield->{-text}; }