dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuepy04_it.pdf · adult contemporary/chris...

41

Upload: buinguyet

Post on 18-May-2018

249 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 50 1indice ^

Full CircleEDIZIONE SPECIALE SERIE PROGRAMMAZIONE

LA RIVISTA INDIPENDENTE PER LA COMUNITÀ LINUX UBUNTU

full circle magazine non è affiliata né sostenuta da Canonical Ltd.

PPRROOGGRRAAMMMMAARREEIINN PPYYTTHHOONNVVOOLLUUMMEE 44

EDIZIONE SPECIALE

SERIE PROGRAMMAZIONE

Page 2: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

Cos'è Full Circle

Full Circle è una rivista gratuita e

indipendente, dedicata alla

famiglia Ubuntu dei sistemi

operativi Linux. Ogni mese

pubblica utili articoli tecnici e

articoli inviati dai lettori.

Full Circle ha anche un podcast di

supporto, il Full Circle Podcast,

con gli stessi argomenti della

rivista e altre interessanti notizie.

Si prega di notare che questaedizione speciale viene fornitasenza alcuna garanzia: né chi hacontribuito né la rivista Full Circlehanno alcuna responsabilità circaperdite di dati o danni chepossano derivare ai computer oalle apparecchiature dei lettoridall'applicazione di quantopubblicato.

Come contattarci

Sito web:http://www.fullcirclemagazine.org/

Forum:http://ubuntuforums.org/forumdisplay.php?f=270

IRC:#fullcirclemagazine suchat.freenode.net

Gruppo editoriale

Capo redattore: Ronnie Tucker(aka: RonnieTucker)[email protected]

Webmaster: Rob Kerfia(aka: admin / [email protected]

Podcaster: Robin Catling(aka RobinCatling)[email protected]

Manager delle comunicazioni:Robert Clipsham(aka: mrmonday) [email protected]

Ecco a voi un altro Speciale monotematico!

Come richiesto dai nostri lettori, stiamo assemblando inedizioni dedicate alcuni degli articoli pubblicati in serie.

Quella che avete davanti è la ristampa della serieProgrammare in Python, parti 22-26, pubblicata nei numeri48-52: niente di speciale, giusto quello che abbiamo giàpubblicato.

Vi chiediamo, però, di badare alla data di pubblicazione: leversioni attuali di hardware e software potrebbero esserediverse rispetto ad allora.Controllate il vostro hardware e il vostro software prima diprovare quanto descritto nelle guide di queste edizioni speciali.Potreste avere versioni più recenti del software installato odisponibile nei repository delle vostre distribuzioni.

Buon divertimento!

Gli articoli contenuti in questa rivista sono stati rilasciati sotto la licenza Creative Commons Attribuzione - Non commerciale - Condividi allo stesso modo 3.0.Ciò significa che potete adattare, copiare, distribuire e inviare gli articoli ma solo sotto le seguenti condizioni: dovete attribuire il lavoro all'autore originale inuna qualche forma (almeno un nome, un'email o un indirizzo Internet) e a questa rivista col suo nome ("Full Circle Magazine") e con suo indirizzo Internetwww.fullcirclemagazine.org (ma non attribuire il/gli articolo/i in alcun modo che lasci intendere che gli autori e la rivista abbiano esplicitamente autorizzato voi

o l'uso che fate dell'opera). Se alterate, trasformate o create un'opera su questo lavoro dovete distribuire il lavoro risultante con la stessa licenza o una simile o compatibile.Full Circle magazine è completamente indipendente da Canonical, lo sponsor dei progetti di Ubuntu, e i punti di vista e le opinioni espresse nella rivista non sono in alcun modo da

attribuire o approvati da Canonical.

Full CircleLA RIVISTA INDIPENDENTE PER LA COMUNITÀ LINUX UBUNTU

Page 3: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 48 7 indice ^

HHOOWW--TTOOScritto da Greg Walters PPrrooggrraammmmaarree iinn PPyytthhoonn -- PPaarrttee 2222

Per iniziare col piede giustodovrete avereplaylistmaker.glade eplaylistmaker.py del mese

scorso. In caso contrario,provvedete di conseguenza. Primadi iniziare, diamo un'occhiata acos'è un file playlist. Esistono molteversioni di liste d'esecuzione etutte hanno estensioni differenti.Quella che creeremo noi sarà deltipo *.m3u. Nella sua forma piùsemplice, è solo un file di testo cheinizia con "#EXTM3U" e presentauna riga per ciascuna canzone daascoltare, incluso l'intero percorso.

È possibile aggiungere informazioniulteriori prima di ciascuna vocecome la durata della canzone, iltitolo dell'album, il numero ditraccia e il titolo. Per il momento ciconcentreremo sulla versione base.

Ecco un esempio di playlist M3U...

#EXTM3UAdult Contemporary/ChrisRea/Collection/02 - On TheBeach.mp3Adult Contemporary/ChrisRea/Collection/07 - Fool (IfYou Think It's Over).mp3Adult Contemporary/ChrisRea/Collection/11 - LookingFor The Summer.mp3

Tutti i percorsi sono relativi allaposizione del file playlist.

Ok... iniziamo a scrivere codice. Adestra trovate l'inizio del codicesorgente del mese scorso.

Ora dobbiamo creare una funzioneper la gestione di ciascun eventogià configurato. Notate cheon_MainWindow_destroy eon_tbtnQuit_clicked sono già statirealizzati, così ne dobbiamorealizzare solamente altri 10

Errata Corrige

Il mese scorso, nella parte 21, viera stato detto di salvare il lavorocome "PlaylistMaker.glade" ma,nel codice, vi si facevariferimento come"playlistmaker.glade". Sonosicuro che avrete notato che unaversione ha le maiuscole e l'altrano. Il codice funzionerà solousando la stessa versione, con osenza le maiuscole.

#!/usr/bin/env pythonimport sysfrom mutagen.mp3 import MP3try:

import pygtkpygtk.require("2.0")

except:pass

try:import gtkimport gtk.glade

except:sys.exit(1)

quindi la definizione della classeclass PlayListCreator:

def __init__(self):self.gladefile = "playlistmaker.glade"self.wTree = gtk.glade.XML(self.gladefile,"MainWindow")

e la routine mainif __name__ == "__main__":

plc = PlayListCreator()gtk.main()

Quindi abbiamo il dizionario che dovrebbe essere inserito dopo lafunzione __init__ .def SetEventDictionary(self):dict = {"on_MainWindow_destroy": gtk.main_quit,

"on_tbtnQuit_clicked": gtk.main_quit,"on_tbtnAdd_clicked": self.on_tbtnAdd_clicked,"on_tbtnDelete_clicked": self.on_tbtnDelete_clicked,"on_tbtnClearAll_clicked": self.on_tbtnClearAll_clicked,"on_tbtnMoveToTop_clicked": self.on_tbtnMoveToTop_clicked,"on_tbtnMoveUp_clicked": self.on_tbtnMoveUp_clicked,"on_tbtnMoveDown_clicked": self.on_tbtnMoveDown_clicked,"on_tbtnMoveToBottom_clicked": self.on_tbtnMoveToBottom_clicked,"on_tbtnAbout_clicked": self.on_tbtnAbout_clicked,"on_btnGetFolder_clicked": self.on_btnGetFolder_clicked,"on_btnSavePlaylist_clicked": self.on_btnSavePlaylist_clicked}

self.wTree.signal_autoconnect(dict)

Page 4: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 48 8 indice ^

(mostrati in alto a destra). Per oracreate solo lo scheletro.

Le modificheremo tra pochi minuti.Per il momento questo dovrebbeessere sufficiente a eseguirel'applicazione e potremo fare delleprove nel corso dello sviluppo. Maprima di poter eseguirel'applicazione dobbiamo inserireun'ulteriore riga alla funzione__init__. Dopo la riga self.wTree,aggiungete...

self.SetEventDictionary()

Ora potete eseguire l'applicazione,osservare la finestra e fare clic sulPulsante Esci della barra strumentiper uscire correttamentedall'applicazione. Salvate il codicecome "playlistmaker-1a.py" eprovate. Ricordate di salvarlo nellastessa cartella insieme al file gladedell'ultima volta o copiate il fileglade nella stessa cartella in cuiavete salvato questo codice.

Dobbiamo anche definire alcunevariabili per un uso successivo.Inserite le seguenti dopo lachiamata SetEventDictionary nellafunzione __init__.

self.CurrentPath = ""self.CurrentRow = 0

self.RowCount = 0

Ora creeremo una funzione che cipermette di mostrare una finestra acomparsa ogniqualvolta si debbanodare informazioni all'utente. Cisono alcune funzioni predefiniteche useremo, ma creeremo unaroutine nostra per semplificarci illavoro. È la funzionegtk.MessageDialog e la sintassi è laseguente...

gtk.MessageDialog(parent,flags,MessageType,Buttons,message)

Sono necessari alcuni chiarimentiprima di procedere. Il tipo dimessaggio può essere uno deiseguenti...

GTK_MESSAGE_INFO - MessaggioinformativoGTK_MESSAGE_WARNING -Messaggio di avviso nonfataleGTK_MESSAGE_QUESTION -Domanda richiedente unasceltaGTK_MESSAGE_ERROR - Messaggiodi errore fatale

E i tipi di pulsante sono...

GTK_BUTTONS_NONE - nessunpulsanteGTK_BUTTONS_OK - un pulsanteOK

GTK_BUTTONS_CLOSE - unpulsante ChiudiGTK_BUTTONS_CANCEL - Unpulsante AnnullaGTK_BUTTONS_YES_NO - pulsantiSì e NoGTK_BUTTONS_OK_CANCEL -pulsanti Ok e Annulla

Normalmente userete il seguente,o simile, codice per creare unafinestra di dialogo, mostrarla,attendere la risposta e quindieliminarla dalla memoria.

dlg =gtk.MessageDialog(None,0,gtk.MESSAGE_INFO,gtk.BUTTONS_OK,"This is a test message...")response = dlg.run()

dlg.destroy()

Comunque, se volete mostrare unmessaggio all'utente più di una odue volte, si tratta di scrivereparecchio. La regola generale è chese scrivete una serie di righe più diuna o due volte è certamentemeglio creare una funzione e quindichiamarla. Ragionate in questomodo: se vogliamo mostrare unmessaggio all'utente, diciamo diecivolte, equivale a scrivere 10x3 (o30) righe di codice. Creando unafunzione (usando l'esempio appenapresentato) avremo 10+3 righe dicodice da scrivere. Più volterichiamiamo il messaggio, meno

def on_tbtnAdd_clicked(self,widget):pass

def on_tbtnDelete_clicked(self,widget):pass

def on_tbtnClearAll_clicked(self,widget):pass

def on_tbtnMoveToTop_clicked(self,widget):pass

def on_tbtnMoveUp_clicked(self,widget):pass

def on_tbtnMoveDown_clicked(self,widget):pass

def on_tbtnMoveToBottom_clicked(self,widget):pass

def on_tbtnAbout_clicked(self,widget):pass

def on_btnGetFolder_clicked(self,widget):pass

def on_btnSavePlaylist_clicked(self,widget):pass

PROGRAMMARE IN PYTHON - PARTE 22

Page 5: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 48 9 indice ^

codice ci tocca scrivere e piùleggibile risulterà il nostro codice.La nostra funzione (in alto a destra)ci permetterà di chiamare ciascunodei quattro tipi di messaggio consolo una funzione e parametridifferenti.

Si tratta di una funzione moltosemplice che possiamo chiamare inquesto modo:

self.MessageBox("info","Thebutton QUIT was clicked")

Notate che se scegliamo di usare iltipo MESSAGE_QUESTION, lafinestra potrà ritornare duepossibili risposte , "Si" o "No".Qualunque pulsante l'utente scelgariceveremo l'informazione nelnostro codice. Per usare la finestradi dialogo domanda, la chiamatasarà simile a...

response =self.MessageBox("question","Are you sure you want to dothis now?")if response ==gtk.RESPONSE_YES:

print "Yes was clicked"elif response ==gtk.RESPONSE_NO:

print "NO was clicked"

Potete vedere come controllare ilvalore del pulsante scelto. Quindi

ora sostituiamo lachiamata "pass" inciascuna delle funzionidi gestione degli eventicon qualcosa simile aquello mostrato inbasso a destra.

Non è la versionedefinitiva, ma vi daràun'indicazione visivache i pulsantifunzionano comedesiderato. Salvate ilcodice come"playlistmaker-1b.py" etestate il programma.Ora creeremo unafunzione per impostarei riferimenti per i nostri widget.Questa funzione sarà chiamata una

sola volta, ma renderà il codice piùgestibile e leggibile. In pratica,vogliamo creare variabili locali che

referenziano i widget nella finestradi glade, così da poterli richiamare,se necessario, a piacimento.

def MessageBox(self,level,text):if level == "info":

dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_INFO,gtk.BUTTONS_OK,text)elif level == "warning":

dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_WARNING,gtk.BUTTONS_OK,text)elif level == "error":

dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_ERROR,gtk.BUTTONS_OK,text)elif level == "question":

dlg = gtk.MessageDialog(None,0,gtk.MESSAGE_QUESTION,gtk.BUTTONS_YES_NO,text)if level == "question":

resp = dlg.run()dlg.destroy()return resp

else:resp = dlg.run()dlg.destroy()

def on_tbtnAdd_clicked(self,widget):self.MessageBox("info","Button Add was clicked...")

def on_tbtnDelete_clicked(self,widget):self.MessageBox("info","Button Delete was clicked...")

def on_tbtnClearAll_clicked(self,widget):self.MessageBox("info","Button ClearAll was clicked...")

def on_tbtnMoveToTop_clicked(self,widget):self.MessageBox("info","Button MoveToTop was clicked...")

def on_tbtnMoveUp_clicked(self,widget):self.MessageBox("info","Button MoveUp was clicked...")

def on_tbtnMoveDown_clicked(self,widget):self.MessageBox("info","Button MoveDown was clicked...")

def on_tbtnMoveToBottom_clicked(self,widget):self.MessageBox("info","Button MoveToBottom was clicked...")

def on_tbtnAbout_clicked(self,widget):self.MessageBox("info","Button About was clicked...")

def on_btnGetFolder_clicked(self,widget):self.MessageBox("info","Button GetFolder was clicked...")

def on_btnSavePlaylist_clicked(self,widget):self.MessageBox("info","Button SavePlaylist was clicked...")

PROGRAMMARE IN PYTHON - PARTE 22

Page 6: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 48 10 indice ^

Inserite questa funzione (pag.precedente, in alto a destra) sottola funzione SetEventDictionary.

Si prega di notare che c'è una cosanon referenziata nella nostraroutine. Si tratta del widget vistaalbero. Creeremo il riferimentoquando imposteremo la vistastessa. Notate anche l'ultima rigadella funzione. Per usare la barra distato dovremo riferirci ad essaattraverso il suo id contestuale. Louseremo più avanti.

Proseguiamo impostando lafunzione che mostra la finestra"informazioni" quando si fa clic sulpulsante "Informazioni" nella barrastrumenti. Di nuovo, a tal scopo c'èuna funzione predefinita fornitadalla libreria GTK. Inserite il codicein basso a destra dopo la funzioneMessageBox.

Salvate e fate una prova. Dovrestevedere una finestra a comparsa,centrata rispetto all'applicazione,che mostra qualunque cosaabbiamo inserito. La finestraInformazioni possiede altri attributi(documentati all'indirizzohttp://www.pygtk.org/docs/pygtk/class-gtkaboutdialog.html), maquesti sono quelli che considero di

base.

Prima di proseguiredobbiamo discutere di quelloche avverrà esattamente daquesto momento. L'ideagenerale è che l'utente facciaclic sul pulsante "Aggiungi"nella barra strumenti, si aprauna finestra per aggiungerefile alla playlist e quindivengano mostrate leinformazioni dei file nellavista ad albero. Da qui èpossibile aggiungere altri file,cancellare singole voci,cancellare tutto, muovere unelemento in alto, in basso,

def SetWidgetReferences(self):self.txtFilename = self.wTree.get_widget("txtFilename")self.txtPath = self.wTree.get_widget("txtPath")self.tbtnAdd = self.wTree.get_widget("tbtnAdd")self.tbtnDelete = self.wTree.get_widget("tbtnDelete")self.tbtnClearAll = self.wTree.get_widget("tbtnClearAll")self.tbtnQuit = self.wTree.get_widget("tbtnQuit")self.tbtnAbout = self.wTree.get_widget("tbtnAbout")self.tbtnMoveToTop = self.wTree.get_widget("tbtnMoveToTop")self.tbtnMoveUp = self.wTree.get_widget("tbtnMoveUp")self.tbtnMoveDown = self.wTree.get_widget("tbtnMoveDown")self.tbtnMoveToBottom = self.wTree.get_widget("tbtnMoveToBottom")self.btnGetFolder = self.wTree.get_widget("btnGetFolder")self.btnSavePlaylist = self.wTree.get_widget("btnSavePlaylist")self.sbar = self.wTree.get_widget("statusbar1")self.context_id = self.sbar.get_context_id("Statusbar")

e quindi aggiungete una chiamata ad essa giusto dopo la chiamata self.SetEventDictionary() nella funzione__init__.

self.SetWidgetReferences()

def ShowAbout(self):about = gtk.AboutDialog()about.set_program_name("Playlist Maker")about.set_version("1.0")about.set_copyright("(c) 2011 by Greg Walters")about.set_comments("Written for Full Circle Magazine")about.set_website("http://thedesignatedgeek.com")about.run()about.destroy()

Ora, commentate (o rimuovete semplicemente) la chiamata messageboxnella funzione on_tbtnAbout_clicked e sostituitela con una chiamata allafunzione ShowAbout. Fate in modo che somigli a questa.

def on_tbtnAbout_clicked(self,widget):#self.MessageBox("info","Button About was clicked...")self.ShowAbout()

PROGRAMMARE IN PYTHON - PARTE 22

Page 7: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 48 11 indice ^

all'inizio o alla fine della vista adalbero. Eventualmente, si potràdefinire il percorso dove salvare ilfile, fornire un nome conestensione "m3u" e fare clic sulpulsante salva file. Anche se questosembra tutto abbastanza semplice,molto deve avvenire dietro lequinte. Poiché molta della magiariguarda il widget vista ad albero,incominciamo a parlarne. Ciaddentreremo abbastanza, quindileggete con attenzione dato che lacorretta comprensione vi eviteràerrori futuri.

Una vista ad albero può esseresemplice come la rappresentazionedi una colonna di dati di un foglio dilavoro o di un database, o puòessere più complessa come unalista di file e cartelle con elementigenitori e figli, dove una cartellapotrebbe essere il genitore e i filein quella cartella potrebbero esserei figli, o qualcosa di ancora piùcomplesso. Per questo progettouseremo il primo esempio, una listacolonnare. Questa lista conterrà trecolonne. Una per il nome del filemusicale, una per l'estensione delfile stesso (mp3, ogg, wav, etc) e lacolonna finale per il percorso.Combinando il tutto in una stringa(percorso, nome e estensione)

otterremo la voce dainserire nella playlist.Potete, se vi dovesseoccorrere, aggiungerealtre colonne ma per ilmomento ci fermeremo aqueste tre.

Una vista ad albero è uncontenitore che conservae mostra un modello. Ilmodello è il "dispositivo"attuale che contiene emanipola i nostri dati. Cisono due modelli predefiniti chepossono essere usati con la vistaalbero, ma ovviamente potetecrearne di vostri. Detto questo, nelcirca il 98% dei casi uno dei duemodelli predefiniti sarà sufficienteai nostri bisogni. I due tipi sonoGTKListStore e GTKTreeStore.Come suggerisce il loro nome ilmodello ListStore si usa di solitoper le liste, TreeStore per gli alberi.Nella nostra applicazione useremoGTKListStore. I passi base sono:

• Creare un riferimento al widgetTreeView.• Aggiungere le colonne.• Impostare il tipo di visualizzazioneda usare.• Creare una ListStore• Impostare come modello della

vista albero il nostro modello• Inserire i dati

Il terzo passo riguarda la scelta deltipo di visualizzazione usato dallacolonna per mostrare i dati. Sitratta di una semplice funzione perdisegnare i dati nel modello adalbero. GTK fornisce molti tipi divisualizzazioni differenti ma quelliche userete maggiormente sonoGtkCellRenderText eGtkCellRendererToggle.

Allora, creiamo una funzione(mostrata in alto) per configurare ilwidget vista albero. La chiameremoSetupupTreeview. Primadefiniremo alcune variabili per lecolonne, quindi la variabile che fariferimento alla stessa vista albero,aggiungiamo le colonne,

impostiamo ListStore e il modello.Inseritela dopo la funzioneSetWidgetReferences.

Le variabili cFName, cFType ecFPath definiscono i numeri dicolonna. Le variabili sFName,sFType e sFPath conterranno i nomidelle colonne in fase divisualizzazione. La settima rigaconfigura la variabile che fariferimento al widget vista alberoutilizzando lo stesso nome usatonel nostro file glade.

Procediamo chiamando unafunzione (pagina seguente, in alto adestra), che creeremo a breve, perciascuna colonna. Quindi definiamoGTKListStore con tre campi di testoe per finire impostiamo l'attributomodello del widget vista albero con

def SetupTreeview(self):self.cFName = 0self.cFType = 1self.cFPath = 2self.sFName = "Filename"self.sFType = "Type"self.sFPath = "Folder"self.treeview = self.wTree.get_widget("treeview1")self.AddPlaylistColumn(self.sFName,self.cFName)self.AddPlaylistColumn(self.sFType,self.cFType)self.AddPlaylistColumn(self.sFPath,self.cFPath)self.playList = gtk.ListStore(str,str,str)self.treeview.set_model(self.playList)self.treeview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)

PROGRAMMARE IN PYTHON - PARTE 22

Page 8: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 48 12 indice ^

il nostro GTKListStore.Proseguiamo creando la funzioneAddPlaylistColumn. Inseritela dopoSetupTreeview.

Ciascuna colonna viene creatautilizzando questa funzione. Lepassiamo il titolo della colonna(quello che viene mostrato in cimaa ciascuna colonna) e unidentificativo. In questo casopasseremo le variabili create inprecedenza (sFName and cFname).Quindi nel widget vista alberocreiamo una colonna assegnandoleun titolo, il tipo di visualizzazioneche userà e, per finire, unidentificativo. Quindi rendiamo lacolonna ridimensionabile,ordinabile in base all'identificativoe finiamo aggiungendo la colonnaalla vista ad albero.

Aggiungete queste due funzioni.Ho deciso di inserirle subito dopo lafunzione SetWidgetReferences, mapotete inserirle ovunque nellaclasse PlayListCreator. Aggiungetela seguente riga dopo la chiamata aSetWidgetReferences() nellafunzione __init__ per richiamare lafunzione.

self.SetupTreeview()

Salvate ed eseguite il programma e

vedrete cheora il nostrowidget vistaalberocontiene trecolonne conintestazioni.

Ci sono molte cose ancora da fare.Dobbiamo trovare un modo perricevere dall'utente i nomi dei fileaudio e inserirli nell'albero comerighe di dati. Dobbiamo creare lefunzioni di eliminazione, pulizia espostamento, quella di salvataggioe quella di gestione del percorsodei file, più altre cose "carine" chedaranno un aspetto piùprofessionale all'applicazione.Iniziamo con la funzione Aggiungi.Dopo tutto è il primo pulsante dellanostra barra strumenti. Quandol'utente fa clic sul pulsanteAggiungi, vogliamo che compaiauna finestra standard per i file checi permetta una selezione multipla.Quando l'utente ha ultimato laselezione, dobbiamo recuperare idati e inserirli nella lista, comedetto sopra. Quindi la prima cosalogica da fare è lavorare sullafinestra di scelta dei file. Ancora,GTK fornisce un sistema perrichiamare una finestra"standard". Potremmo inserire il

codice direttamente nel gestoreevento on_tbtnAdd_clicked macreiamo una classe a sé per loscopo. Visto che ci siamo, larealizzeremo in maniera tale chenon solo gestisca la finestra APRIma anche quella SELEZIONA. Comevisto prima con la funzioneMessageBox, potete raccoglieretutti questi frammenti di codice inun file di funzioni riutilizzabili.Iniziamo definendo una nuovaclasse chiamata FileDialog checonterrà solo una funzionechiamata ShowDialog. La funzioneaccetterà due parametri, unochiamato 'which' ('0' o '1'), cheindicherà se creare una finestra peraprire un file o selezionare unacartella, e l'altro,chiamatoCurrentPath, è il percorso sui cui lavista si aprirà. Create questa classesubito prima la funzione main infondo al file sorgente.

class FileDialog:def

ShowDialog(self,which,CurrentPath):

La prima parte del codice dovrebbeessere un'istruzione IF

if which == 0: # file chooser...

else: # folder chooser...

Prima di procedere ulteriormente,esploriamo come la finestra perfile/cartelle è chiamata e utilizzata.La sintassi è la seguente

gtk.FileChooserDialog(title,parent,action,buttons,backend)

e ritorna un oggetto di tipo finestradi dialogo. La prima riga (sotto ifwhich == 0) sarà la riga mostratasotto.

Come potete vedere, il titolo è

def AddPlaylistColumn(self,title,columnId):column = gtk.TreeViewColumn(title,gtk.CellRendererText(),text=columnId)column.set_resizable(True)column.set_sort_column_id(columnId)self.treeview.append_column(column)

dialog = gtk.FileChooserDialog("Select files to add...",None,gtk.FILE_CHOOSER_ACTION_OPEN,(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN, gtk.RESPONSE_OK))

PROGRAMMARE IN PYTHON - PARTE 22

Page 9: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 48 13 indice ^

"Select files to add...", il genitore èimpostato su None. In questo casol'azione da compiere sarà Apri File evogliamo i pulsanti Annulla e Apri,entrambi che usano le iconepredefinite. Stiamo ancheimpostando i codici di ritorno pergtk.RESPONSE_CANCEL egtk.RESPONSE_OK quando l'utentefa la sua scelta. La chiamata perScelta Cartella sotto la clausola Elseè simile.

In pratica, l'unica differenza tra ledue definizioni è il titolo (mostratoin alto a destra) e il tipo di azione.Quindi il codice finale per la classeora dovrebbe essere quellomostrato al centro a destra.

Abbiamo impostato come rispostapredefinita il pulsante OK, e quindiattivato la seleziona multipla cosìche l'utente può selezionare (aveteindovinato) più file da aggiungere.Se non lo avessimo fatto, la finestraavrebbe permesso la selezione diun file alla volta dato cheset_select_multiple è impostato, didefault, a falso. Le righe successiveconfigurano il percorso corrente equindi viene mostrata la finestrastessa. Prima di inserire il codice,fatemi spiegare perché èimportante configurare un

percorso. Ogni volta che si apre unafinestra di dialogo per i file e NONimpostate un percorso, viene sceltoquello in cui risiede il programma.Quindi diciamo che i file musicaliche cerchiamosono in/media/music_files/ e sono quindisuddivisi pergenere, quindiper artista equindi per album.Ipotizziamoancora chel'utente abbiainstallatol'applicazione in/home/utente2/playlistmaker.Ogni volta cherichiamiamo lafinestra la cartella iniziale sarebbe/home/utente2/playlistmaker. Benpresto questo stuferebbe l'utenteche vorrebbe come cartella inizialequella aperta per ultima. Ha senso?Ok. Allora, in basso a destra ci sonole prossime righe di codice.

Qui controlliamo le risposteritornate. Se l'utente ha fatto clicsul pulsante 'Apri' che restituiscegtk.RESPONSE_OK, otteniamo ilnome o i nomi dei file selezionati

dialog = gtk.FileChooserDialog("Select Save Folder..",None,gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN, gtk.RESPONSE_OK))

class FileDialog:def ShowDialog(self,which,CurrentPath):

if which == 0: #file chooser#gtk.FileChooserDialog(title,parent,action,buttons,backend)dialog = gtk.FileChooserDialog("Select files to add...",None,

gtk.FILE_CHOOSER_ACTION_OPEN,(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN, gtk.RESPONSE_OK))

else: #folder chooserdialog = gtk.FileChooserDialog("Select Save Folder..",None,

gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN, gtk.RESPONSE_OK))

Le due righe seguenti saranno (fuori dall'istruzione IF/ELSE)...

dialog.set_default_response(gtk.RESPONSE_OK)dialog.set_select_multiple(True)

if CurrentPath != "":dialog.set_current_folder(CurrentPath)

response = dialog.run()

Quindi dobbiamo gestire la risposta dalla finestra di dialogo.

if response == gtk.RESPONSE_OK:fileselection = dialog.get_filenames()CurrentPath = dialog.get_current_folder()dialog.destroy()return (fileselection,CurrentPath)

elif response == gtk.RESPONSE_CANCEL:print 'Closed, no files selected'dialog.destroy()

PROGRAMMARE IN PYTHON - PARTE 22

Page 10: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 48 14 indice ^

dall'utente, si imposta il percorsocorrente alla cartella in cui siamo,rimuove la finestra di dialogo dallamemoria e ritorna i dati allafunzione chiamante. Se, d'altraparte, l'utente ha scelto il pulsante'Annulla', si rimuovesemplicemente la finestra dallamemoria. Ho inserito un'istruzioneprint solo per mostrarvi che lapressione del pulsante hafunzionato. Potete lasciarla orimuoverla. Notate cheselezionando il pulsante Aprisaranno restituiti due insiemi divalori:'fileselection', una lista di fileselezionati dall'utente, eCurrentPath.

Affinché la funzione faccia qualcosaaggiungete la seguente riga sottola routine on_tbtnAdd_click...

fd = FileDialog()selectedfiles,self.CurrentPath =fd.ShowDialog(0,self.CurrentPath)

Qui recuperiamo i due valori diritorno. Per il momento,aggiungete il codice seguente pervedere l'aspetto dell'informazioneritornata.

for f in selectedfiles:

print "User selected %s"%fprint "Current path is %s"%self.CurrentPath

Quando eseguite il programma,fate clic sul pulsante 'Aggiungi'.Vedrete la finestra di dialogo. Oraspostatevi dove tenete i file eselezionateli. Potete tenerepremuto il tasto [ctrl] e fare clic supiù file per selezionarliindividualmente, o il tasto [shift]per selezionare più file contigui.Fate clic sul pulsante 'Apri' econtrollate la risposta nelterminale. Si noti che se fate clic sulpulsante 'Annulla' proprio ora,otterrete un messaggio di errore.Questo perché il codice soprapresume che nessun file siaselezionato. Per il momento nonpreoccupatevene, ce neoccuperemo a breve. Quello che mipreme evidenziare è che vediatecosa otteniamo quando si sceglie ilpulsante 'Apri'. Una cosa chedovremmo fare è aggiungere unfiltro alla finestra per i file. Datoche ci aspettiamo che l'utenteselezioni file musicali, noidovremmo (1) dare l'opzione pervisualizzare solo questi e (2) dare lapossibilità per mostrare tutti i file,se necessario. Lo facciamoconfigurando l'attributo FileFilter

della finestra di dialogo. Ecco ilcodice da inserire nella sezionewhich == 0 subito dopo la riga cheimposta la finestra.

filter = gtk.FileFilter()filter.set_name("MusicFiles")filter.add_pattern("*.mp3")filter.add_pattern("*.ogg")filter.add_pattern("*.wav")dialog.add_filter(filter)filter = gtk.FileFilter()filter.set_name("All files")filter.add_pattern("*")dialog.add_filter(filter)

Stiamo configurando due "gruppi",uno per i file audio(filter.set_name("Music Files")) el'altro per tutti i file. Usiamo unoschema per definire i tipi di file chedesideriamo. Ho definito treschemi, ma potete aggiungerli orimuoverli a piacere. Ho inseritoprima il filtro per la musica, datoche dovrebbe essere quello che piùinteressa all'utente. Quindi i passisono...• Definire la variabile filter.• Impostare un nome.• Aggiungere uno schema.• Aggiungere il filtro alla finestra didialogo.Potete avere quanti filtri volete.Notate anche che una voltaaggiunto il filtro è possibileriutilizzarlo.

Ritorniamo alla funzioneon_tbtnAdd_clicked, commentatele ultime righe inserite esostituitele con questa

line.self.AddFilesToTreeview(selec tedfiles)

così ora la nostra funzione apparecome il codice mostrato nellapagina seguente.

Così, quando riceviamo la rispostadella finestra di dialogo, invieremola lista contenente i file selezionatia questa funzione. Una volta qui,definiamo una variabile contatore(quanti file stiamo aggiungendo),quindi scorriamo la lista. Ricordateche ciascuna voce contiene il nomecompleto di percorso edestensione. Divideremo questastringa in percorso, nome edestensione. Prima troviamo l'ultimo'punto' e assumiamo che questo sial'inizio dell'estensione eassegniamo la sua posizione allastringa extStart. Quindi cerchiamol'ultimo '/' per determinare l'iniziodel nome del file. Quindisuddividiamo la stringa inestensione, nome del file epercorso. Inseriamo questi valori inuna lista chiamata 'data' el'aggiungiamo alla playlistListStore. Poiché abbiamo

PROGRAMMARE IN PYTHON - PARTE 22

Page 11: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 48 15 indice ^

def on_tbtnAdd_clicked(self,widget):fd = FileDialog()selectedfiles,self.CurrentPath =

fd.ShowDialog(0,self.CurrentPath)self.AddFilesToTreeview(selectedfiles)

Ora dobbiamo creare la funzione che abbiamo appena chiamato.Inserite questa funzione dopo on_btnSavePlaylist_clicked.

def AddFilesToTreeview(self,FileList):counter = 0for f in FileList:

extStart = f.rfind(".")fnameStart = f.rfind("/")extension = f[extStart+1:]fname = f[fnameStart+1:extStart]fpath = f[:fnameStart]data = [fname,extension,fpath]self.playList.append(data)counter += 1

self.RowCount += counterself.sbar.push(self.context_id,"%s files added

for a total of %d" % (counter,self.RowCount))

PROGRAMMARE IN PYTHON - PARTE 22

terminato il lavoro, incrementiamoil contatore. Per finireincrementiamo la variabileRowCount che contiene il numerototale delle righe in ListStore equindi mostriamo un messaggionella barra di stato.Ora potete avviare il programma evedere i dati nella vista ad albero.

Come sempre, l'intero codice puòessere trovato all'indirizzohttp://pastebin.com/JtrhuE71.

La prossima volta finalizzeremol'applicazione, aggiungendo lefunzioni mancanti, ecc.

Greg Walters è il proprietario dellaRainyDay Solutions, LLC, una societàdi consulenza in Aurora, Colorado eprogramma dal 1972. Ama cucinare,fare escursioni, ascoltare musica epassare il tempo con la sua famiglia.

GLISPECIALINONPERDETEVELI!

Edizioni speciali di FullCircle distribuite inmondo ignaro*

IL SERVER PERFETTOEDIZIONE SPECIALEQuesta è una edizionespeciale di Full Circle che è laristampa diretta degli articoliIl Server Perfetto che sonostati inizialmente pubblicatinei numeri da 31 a 34 di FullCircle Mgazine.

http://fullcirclemagazine.org/special-edition-1-the-perfect-server/

PYTHONEDIZIONE SPECIALE #01

Questa è una ristampa diProgrammare in Pythonparte 1- 8 di Greg Walters

http://fullcirclemagazine.org/python-special-edition-1/

* Né Full Circlemagazine, né i suoi creatori, si scusanoper eventuali isterie causate dal rilascio delle loro pubblicazioni.

Page 12: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 49 7 indice ^

HHOOWW--TTOOScritto da Greg Walters PPrrooggrraammmmaarree iinn PPyytthhoonn -- PPaarrttee 2233

Con questa lezionetermineremo ilprogramma playlistmaker.L'ultima volta ne abbiamo

realizzato buona parte, ma lasciandoalcune cose incomplete. Nonpossiamo salvare la playlist, lefunzioni di spostamento non sonodisponibili, non è possibile scegliereil percorso di salvataggio del file, ecosì via. Ci sono, però, ancora alcunecose da fare prima di iniziare colcodice di oggi. Primo, dobbiamotrovare un'immagine logo dainserire nella finestra Informazioni equando il programma èminimizzato.Potreste cercare in /usr/share/iconsun'icona di vostro gradimento, oandare su internet e recuperarneuna o crearne una voi stessi.Qualunque sia la vostra scelta,inseritela nella cartella con il fileglade e il codice sorgente del mesescorso. Chiamatela logo.png. Quindi,dobbiamo aprire il file glade eapportare alcunemodifiche.

Prima di tutto, usandoMainWindow andate nella schedaGenerale, scorrete in basso fino atrovare Icona. Usando lo strumentosfoglia, cercate la vostra icona e

selezionatela. Ora il campodi testo dovrebbecontenere "logo.png".Proseguite selezionandotreeview1 nell'ispettore,attivate la scheda Segnali ein corrispondenza diGtkTreeView|cursorchanged selezionateon_treeview1_cursor_changed. Ricordate, come vi hodetto il mese scorso, di fareclic all'esterno per applicarela modifica. Per finire,sempre nell'Ispettoreselezionate txtFilename eaprite la scheda Segnali.Scorrete fino a trovare'GtkWidget', quindispostatevi in basso un altropo' fino a 'key_press_event'.Selezionate'on_txtFilename_key_press_event'.Salvate il vostro progetto e chiudeteGlade.

Ora è arrivato il momento dicompletare il progetto.Riprenderemo da dove abbiamointerrotto, usando il codicedell'ultimomese.

La prima cosa che voglio fare èmodificare il codice della classeFileDialog. Se ricordate dall'ultimavolta, se l'utente faceva clic sulpulsante 'Annulla' compariva unerrore. Prima sistemeremo questo.Alla fine della funzione avrete ilcodice mostrato in alto.

Come potete immaginare, non faaltro che controllare il valore di

ciascun tasto premuto, quandol'utente si trova nel campo di testoTxtFilename e lo confronta con ilvalore 65293, che sarebbe il codicedel tasto INVIO. Se corrispondeallora chiama la funzioneSavePlaylist. L'utente non deve fareclic su nessun pulsante.

Ora il nuovo codice. Occupiamocidel pulsante Pulisci della barra degli

elif response == gtk.RESPONSE_CANCEL:print 'Closed, no files selected'dialog.destroy()

Notate come non venga restituito nulla. Questo causa l’errore. Quindi, percorreggerlo, dobbiamo inserire la riga seguente dopo dialog.destroy().

Return ([],"")

Questa eviterà che si verifichi l’errore. Quindi, aggiungiamo il gestore eventodel campo di testo creato con glade. Al nostro dizionario, inseriamo laseguente riga.

"on_txtFilename_key_press_event": self.txtFilenameKeyPress,

Come ricorderete, così si crea la funzione per gestire l’evento keypress.Creiamo a seguire la funzione.

def txtFilenameKeyPress(self,widget,data):if data.keyval == 65293: # The value of the return key

self.SavePlaylist()

Page 13: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 49 8 indice ^

PROGRAMMARE IN PYTHON - PARTE 23strumenti. Quando è premutodall'utente, vogliamo che treeview eListScore vengano puliti. È unasemplice riga che possiamo inserirenella funzioneon_tbtnClearAll_clicked.

defon_tbtnClearAll_clicked(self,widget):

self.playList.clear()

Stiamo semplicementechiedendo a ListStore di pulirsi dasolo. È stato facile. Passiamo alpulsante Elimina. Più difficile, mauna volta all'interno, capirete.

Prima di tutto dobbiamodiscutere su come si ricava unaselezione dal widget vista albero eda ListScore. È complicato, quindiprocediamo con calma. Per ottenerei dati da ListScore dobbiamo primaricavare gtk.TreeSelection che non èaltro che un oggetto di supporto permaneggiare la selezione in una vistaad albero. Quindi usiamo questooggetto di supporto per recuperareil tipo di modello e un iteratore checontiene le righe selezionate.

So che state pensando "Cosadiamine è un iteratore?" Beh, liavete già usati senza saperlo.Considerate il codice seguente (in

alto a destra) dalla funzioneAddFilesToTreeview, del mesescorso.

Osservate l'istruzione 'for'.Usiamo un iteratore per spostarcinella lista chiamata FileList. Inpratica, in questo caso, l'iteratore sisposta semplicemente attraversociascun elemento della listarestituendoceli uno alla volta.Quello che andremo a fare è creareun iteratore, riempirlo con le righeselezionate nella vista albero eusarlo come una lista. Quindi ilcodice (a destra, al centro) dion_tbtnDelete_clicked sarà.

La prima riga crea l'oggettoTreeSelection. Lo usiamo perricavare le righe selezionate (chesaranno solo una perché nonabbiamo previsto che ilmodello supporti la selezionemultipla) che inseriamo nellalista chiamata iters e quindila scorriamo rimuovendo(come il metodo .clear). Di paripasso decrementiamo la variabileRowCount e quindi mostriamo ilnumero di file nella barra di stato.

Ora, prima di passare allefunzioni di spostamento,concentriamoci su quella di

salvataggio del file. Useremo laclasse FileDialog come prima.Inseriremo l'intero codice (in basso adestra) nella funzioneon_btnGetFolder_clicked.

L'unica differenza sta nell'ultimariga. Inseriremo il percorso ottenutoda FileDialog nel campo di testo

impostato precedentementeusando il metodo set_text.Ricordate che i dati ritornati sonosotto forma di lista, anche nel casodi una sola voce. Per questo usiamo'filepath[0]'.

Passiamo alla funzione persalvare il file. Possiamo

def AddFilesToTreeview(self,FileList):counter = 0for f in FileList:

extStart = f.rfind(".")fnameStart = f.rfind("/")extension = f[extStart+1:]fname = f[fnameStart+1:extStart]fpath = f[:fnameStart]data = [fname,extension,fpath]self.playList.append(data)counter += 1

def on_tbtnDelete_clicked(self,widget):sel = self.treeview.get_selection()(model,rows) = sel.get_selected_rows()iters=[]for row in rows:

iters.append(self.playList.get_iter(row))for i in iters:

if i is not None:self.playList.remove(i)self.RowCount -= 1

self.sbar.push(self.context_id,"%d files in list." %(self.RowCount))

def on_btnGetFolder_clicked(self,widget):fd = FileDialog()filepath,self.CurrentPath = fd.ShowDialog(1,self.CurrentPath)self.txtPath.set_text(filepath[0])

Page 14: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 49 9 indice ^

tranquillamente farlo prima dioccuparci delle funzioni dispostamento. Creeremo unafunzione chiamata savePlaylist. Laprima cosa da fare (in alto a destra)è controllare se txtPath contienequalcosa. Quindi se il campo di testotxtFilename contiene il nome delfile. In entrambi i casi usiamo ilmetodo .get_text().

Ora che sappiamo di avere unpercorso (fp) ed un nome (fn)possiamo aprire il file, inserirvil’intestazioneM3U e processare laplaylist. Il percorso è contenuto (seben ricordate) nella colonna 2, ilnome del file nella colonna 0 el’estensione nella colonna 1.Semplicemente creiamo (a destra)una stringa e la scriviamo nel file equindi lo chiudiamo.

Possiamo ora iniziare a lavoraresulle funzioni di spostamento.Iniziamo dalla routine Spostaall’inizio. Proprio come abbiamofatto nel caso della funzioneElimina, recuperiamo la selezione equindi la riga selezionata. Poidobbiamomuoverci tra le righe perrecuperare due variabili. Lechiameremo path1 e path2. path2,in questo caso, sarà impostata a 0,che è la riga “bersaglio”. path1 è il

percorso selezionato dall’utente.Finiamo usando il metodomodel.move_before() per muoverela riga selezionata fino a quella 0,praticamente spostando tutto giù.Inseriremo il codice (in basso adestra) direttamente nella funzione

PROGRAMMARE IN PYTHON - PARTE 23

def SavePlaylist(self):fp = self.txtPath.get_text() # Get the filepath from the text boxfn = self.txtFilename.get_text() # Get the filename from the filename text box

Ora controlliamo i valori...

if fp == "": # IF the path is blank...self.MessageBox("error","Please provide a filepath for the playlist.")

elif fn == "": # IF the filename is blank...self.MessageBox("error","Please provide a filename for the playlist file.")

else: # Otherwise we are good to go.

plfile = open(fp + "/" + fn,"w") # Open the fileplfile.writelines('#EXTM3U\n') # Print the M3U Headerfor row in self.playList:

plfile.writelines("%s/%s.%s\n" % (row[2],row[0],row[1])) #Write the line dataplfile.close # Finally close the file

Per finire, facciamo comparire la finestra che informa l’utente che il file è stato salvato.

self.MessageBox("info","Playlist file saved!")

Dobbiamo ora inserire una chiamata a questa funzione nella routine di gestione dell’eventoon_btnSavePlaylist_clicked.

def on_btnSavePlaylist_clicked(self,widget):self.SavePlaylist()

Salvate il codice e testatelo. La playlist dovrebbe essere salvata correttamente e somigliare all’esempiofornito il mese scorso.

def on_tbtnMoveToTop_clicked(self,widget):sel = self.treeview.get_selection()(model,rows) = sel.get_selected_rows()for path1 in rows:

path2 = 0iter1=model.get_iter(path1)iter2 = model.get_iter(path2)model.move_before(iter1,iter2)

Page 15: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 49 10 indice ^

on_tbtnMoveToTop_clicked.

Per la funzioneMoveToBottom,useremo quasi lo stesso codice diMoveToTopma, invece del metodomodel.move_before() useremomodel.move_after() e invece diimpostare path2 a 0 sarà impostatoa self.RowCount-1. Ora è evidente ilsenso della variabile RowCount.Ricordate che il conteggioincomincia da 0 così dobbiamo usareRowCount-1 (in alto a destra).

Ora diamo un’occhiata a cosaserve per creare la funzioneMoveUp. Ancora una volta è moltosimile alle ultime due. Questa voltauseremo il numero della rigaselezionata, assegnata a path1, edassegnandolo, diminuito di uno, apath2. Quindi se path2 (la rigabersaglio) è maggiore o uguale a 0,useremo il metodomodel.swap() (laseconda in basso a destra).

Stessa cosa per MoveDown.Questa volta però controlleremo sepath2 èMINORE o uguale al valoredi self.RowCount-1 (la terza in bassoa destra).

Ora facciamo alcuni cambiamentialle funzioni della nostra playlist.Nell’articolo del mese scorso vi

mostrai il formato base del fileplaylist (in basso).

Ma vi dissi anche che c’era unformato esteso nel quale prima diciascuna voce può essere aggiuntauna riga contenente informazioniextra sulla canzone. La nuova riga hail seguente formato...

#EXTINF:[Length of song inseconds],[Artist Name] –[Song Title]

Potreste esservi chiesti perchéabbiamo incluso la libreria mutagenfin dall’inizio senza averla mai usata.Bene, lo faremo ora. Per rinfrescarvila memoria, la libreria mutagenserve per accedere alle informazioniID3 contenute all’interno dell’mp3.Per una discussione completa fateriferimento al numero 35 di FullCircle che contiene la parte 9 diquesta serie. Creeremo unafunzione che si occuperà di leggereil file mp3 e che restituisca il nomedell’artista, il titolo della canzone, lasua durata in secondi, che sono i tredati di cui abbiamo bisogno per lariga di informazioni extra. Inserite la

funzione dopo ShowAboutall’interno della classePlaylistCreator (pagina seguente, inalto a destra).

Sempre per rinfrescarvi lamemoria, analizzerò il codice. Primaripuliremo le tre variabili di ritornocosì se non accade nulla resterannovuote una volta restituite. Quindipassiamo il nome del file mp3 da

PROGRAMMARE IN PYTHON - PARTE 23

def on_tbtnMoveToBottom_clicked(self,widget):sel = self.treeview.get_selection()(model,rows) = sel.get_selected_rows()for path1 in rows:

path2 = self.RowCount-1iter1=model.get_iter(path1)iter2 = model.get_iter(path2)model.move_after(iter1,iter2)

def on_tbtnMoveUp_clicked(self,widget):sel = self.treeview.get_selection()(model,rows) = sel.get_selected_rows()for path1 in rows:

path2 = (path1[0]-1,)if path2[0] >= 0:

iter1=model.get_iter(path1)iter2 = model.get_iter(path2)model.swap(iter1,iter2)

def on_tbtnMoveDown_clicked(self,widget):sel = self.treeview.get_selection()(model,rows) = sel.get_selected_rows()for path1 in rows:

path2 = (path1[0]+1,)iter1=model.get_iter(path1)if path2[0] <= self.RowCount-1:

iter2 = model.get_iter(path2)model.swap(iter1,iter2)

#EXTM3UAdult Contemporary/Chris Rea/Collection/02 - On The Beach.mp3Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3Adult Contemporary/Chris Rea/Collection/11 - Looking For The Summer.mp3

Page 16: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 49 11 indice ^

PROGRAMMARE IN PYTHON - PARTE 23controllare. Poi inseriamo le chiavi(sì, avete indovinato) nell’iteratore elo controlleremo in cerca di dueetichette specifiche. Sono ‘TPE1’ checontiene il nome dell’artista, e ‘TIT2’che è il titolo della canzone. Ora, sela chiave non esiste, otterremo unerrore così inseriamo in ciascunachiamata l’istruzione ‘try|except’.Quindi estraiamo la durata dellacanzone dall’attributoaudio.info.length e restituiamo iltutto.

Ora dobbiamomodificare lafunzione SavePlaylist affinchésupporti le informazioni extra.Controlliamo se il nome del fileesiste e, in caso affermativo,segnalarlo all’utente e uscire dallafunzione. Inoltre, per facilitarel’utente, dato che non supportiamoaltri tipi di file, aggiungiamoautomaticamente, se non esiste,l’estensione ‘.m3u’ al percorso enome del file. Prima inseriteall’inizio del codice, tra import sys eimport mutagen, una riga cheimporti os.path (in basso a destra).

Come nella funzioneAddFilesToTreeview, useremo ilmetodo ‘rfind’ per trovare laposizione dell’ultimo punto (‘.’) nelnome del file fn. Se non c’è, il valore

restituito è -1. Quindicontrolliamo se il valore è-1 e, in questa eventualità,aggiungiamo l’estensione esubito dietro il nome delfile, nel campo di testo,rendendolo più gradevole.

if os.path.exists(fp+ "/" + fn):

self.MessageBox("error","The filealready exists.Please selectanother.")

Quindi, nel resto della

def GetMP3Info(self,filename):artist = ''title = ''songlength = 0audio = MP3(filename)keys = audio.keys()for key in keys:

try:if key == "TPE1": # Artist

artist = audio.get(key)except:

artist = ''try:

if key == "TIT2": # Song Titletitle = audio.get(key)

except:title = ''

songlength = audio.info.length # Audio Lengthreturn (artist,title,songlength)

import os.path

Quindi proseguite e commentate la funzione SavePlaylist preesistente e sostituitela.

def SavePlaylist(self):fp = self.txtPath.get_text() # Get the file path from the text boxfn = self.txtFilename.get_text() # Get the filename from the text boxif fp == "": # IF filepath is blank...

self.MessageBox("error","Please provide a filepath for the playlist.")elif fn == "": # IF filename is blank...

self.MessageBox("error","Please provide a filename for the playlist file.")else: # Otherwise

Fino a questo punto, la funzione è la stessa. Ecco dove iniziano le modifiche.

extStart = fn.rfind(".") # Find the extension start positionif extStart == -1:

fn += '.m3u' #append the extension if there isn't one.self.txtFilename.set_text(fn) #replace the filename in the text box

Page 17: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 49 12 indice ^

PROGRAMMARE IN PYTHON - PARTE 23funzione, inseriamo una clausolaIF|ELSE (in alto a destra) così se il fileesiste già, usciremo semplicementedalla funzione. Useremoos.path.exists(filename) pereseguire il controllo.

Il resto del codice è praticamentesimile al precedente, macontrolliamolo comunque.

La riga 2 apre il file su cuiandiamo a scrivere. La riga 3inserisce l’intestazioneM3U. La riga4 inizia la fase di processamento diListStore. La riga 5 crea il nome delfile dalle tre colonne di ListStore. Lariga 6 chiama GetMP3Info e salva ivalori restituiti nelle variabili. La riga7 controlla se le tre variabilicontengono un valore. In casoaffermativo, inseriamo leinformazioni estese con la riga 8,

altrimenti no. La riga 9 scrive il nomedel file come prima. La riga 10chiude correttamente il file e la riga11 fa comparire il messaggio cheinforma l’utente che il processo èterminato.

Proseguite salvando il codice etestatelo.

A questo punto manca solo diaggiungere qualche suggerimento

per i nostri controlli quando l’utentesi ferma sopra col puntatore delmouse. Aggiunge un tocco diprofessionalità (in basso). Creiamoora la funzione.

Usiamo i riferimenti ai widgetcreati precedentemente eimpostiamo il testo delsuggerimento attraverso (aveteindovinato) l’attributoset_tooltip_text. Quindi dobbiamo

aggiungere la chiamata allafunzione. Ritorniamo alla routine__init__ e aggiungiamo, dopoself.SetWidgetReferences,

self.SetupToolTops()

Infine, ma certamente non perimportanza, vogliamo inserire ilnostro logo nella finestraInformazioni. Come per tutto ilresto, c’è un attributo apposito.Aggiungete la seguente riga alla

else:plfile = open(fp + "/" + fn,"w") # Open the fileplfile.writelines('#EXTM3U\n') #Print the M3U headerfor row in self.playList:

fname = "%s/%s.%s" % (row[2],row[0],row[1])artist,title,songlength = self.GetMP3Info(fname)if songlength > 0 and (artist != '' and title != ''):

plfile.writelines("#EXTINF:%d,%s - %s\n" % (songlength,artist,title))plfile.writelines("%s\n" % fname)

plfile.close # Finally Close the fileself.MessageBox("info","Playlist file saved!")

def SetupToolTips(self):self.tbtnAdd.set_tooltip_text("Add a file or files to the playlist.")self.tbtnAbout.set_tooltip_text("Display the About Information.")self.tbtnDelete.set_tooltip_text("Delete selected entry from the list.")self.tbtnClearAll.set_tooltip_text("Remove all entries from the list.")self.tbtnQuit.set_tooltip_text("Quit this program.")self.tbtnMoveToTop.set_tooltip_text("Move the selected entry to the top of the list.")self.tbtnMoveUp.set_tooltip_text("Move the selected entry up in the list.")self.tbtnMoveDown.set_tooltip_text("Move the selected entry down in the list.")self.tbtnMoveToBottom.set_tooltip_text("Move the selected entry to the bottom of the list.")self.btnGetFolder.set_tooltip_text("Select the folder that the playlist will be saved to.")self.btnSavePlaylist.set_tooltip_text("Save the playlist.")self.txtFilename.set_tooltip_text("Enter the filename to be saved here. The extension '.m3u' will be added for

you if you don't include it.")

Page 18: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 49 13 indice ^

funzione ShowAbout.

about.set_logo(gtk.gdk.pixbuf_new_from_file("logo.png"))

Questo è tutto. Ora avete unprogramma totalmentefunzionante, bello e che che fa unottimo lavoro nella creazione delleplaylist per i vostri file musicali.

L’intero codice sorgente, inclusoil file glade creato il mese scorso, lopotete trovare su pastebin:http://pastebin.com/tQJizcwT

Fino alla prossima volta, gioitedelle vostre nuove abilità.

GregWaltersè il proprietariodellaRainyDaySolutions, LLC, una societàdiconsulenza inAurora, Coloradoeprogrammadal 1972.Amacucinare, fareescursioni, ascoltaremusicaepassare iltempocon la sua famiglia. Il suo sitoweb:www.thedesignatedgeek.com.

Edizioni speciali di FullCircle distribuite inmondo ignaro*

IL SERVER PERFETTOEDIZIONE SPECIALE

Questa è una edizionespeciale di Full Circle che è laristampa diretta degli articoliIl Server Perfetto che sonostati inizialmente pubblicatinei numeri da 31 a 34 di FullCircle Mgazine.http://fullcirclemagazine.org/special-edition-1-the-perfect-server/

* Né Full Circle magazine, né i suoi creatori, si scusano per eventuali isterie causate dal rilascio delle loro pubblicazioni.

PYTHONEDIZIONE SPECIALE #01

Questa è una ristampa diProgrammare in Pythonparte 1-8 di Greg Walters.

http://fullcirclemagazine.org/python-special-edition-1/

PROGRAMMARE IN PYTHON - PARTE 23

GLI SPECIALI!

NON PERDETEVELI!

Page 19: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 50 8 indice ^

HHOOWW--TTOOScritto da Greg Walters PPrrooggrraammmmaarree iinn PPyytthhoonn -- PPaarrttee 2244

C aspita!Èdifficile credereche questa è la parte 24.Sonodue anni che stiamoimparandoPython! Avete

fatto una lunga strada.

Questa volta ci occuperemodi dueargomenti. Il primoè stampare conuna stampante, il secondo è lacreazione di file RTF (Rich TextFormat).

Stampare su Linux

Allora iniziamo con la stampatramite stampante. L’idea per questoargomento è venuta da unamailinviata daGordCampbell. Oggi èfacile stampare su Linux, più facile chesu altri sistemi operativi che inizianoper “Win”, di cui nonmi occupo.

Finché non si vuole che stamparedel semplice testo, senza grassetto,corsivo, caratterimultipli, ecc è tuttofacile. Ecco un'applicazione sempliceche stamperà direttamente con lavostra stampante...

import os

pr = os.popen('lpr','w')

pr.write('print test fromlinux via python\n')

pr.write('Print finished\n')

pr.close()

È facile da comprendere seespandete unpo' la vostramente. Nelcodice sopra, 'lpr' è lo spooler distampa. L'unico requisito è aver giàconfigurato e avviato 'lpd'. Ci sonobuonepossibilità che usandoUbuntusia già tutto pronto. In genere ci siriferisce a 'lpd' comeadun "filtromagico" capace di convertireautomaticamente diversi tipi didocumenti in qualcosa che può esserecompreso dalla stampante.Stamperemo sul dispositivo/oggetto'lpr'. Pensate ad esso come fosse unsemplice file. Lo apriamo.Dobbiamoimportare 'os'. Quindi nella riga 2,apriamo 'lpr' con permessi di scrittura,assegnandolo alla variabile 'pr'. Quindiusiamo 'pr.write' passandogli tuttoquello che vogliamo stampare. Perfinire, riga 5, chiudiamo il file e i datisaranno inviati alla stampante.

Possiamoanche creare un file ditesto e inviarlo alla stampante in

questomodo...

import os

filename = 'dummy.file'

os.system('lpr %s'%filename)

In questo caso stiamoancorausando l'oggetto lprma tramitel'istruzione 'os.system' che in praticacrea un comando simile a quello cheavremmousato da terminale.

Ora vi lascio giocarci.

PyRTF

Oraoccupiamoci dei file RTF. IlformatoRTF (che è comedire numeroPIN, dato chePIN sta perNumerodiIdentificazionePersonale, cosicché sitraduce inNumeroNumero-di-Identificazione-Personale. Forse daldipartimento del dipartimento diridondanza, eh?) fu creato in originedaMicrosoft Corporation nel 1987 ela sua sintassi venne influenzata dallinguaggio typesetting TeX. PyRTF èuna libreriameravigliosa che rendesemplice scrivere file RTF. Dovetepianificare in anticipo l'aspetto dei

vostri file,ma il risultato ne vale lapena.

Primadi tutto dovete scaricare einstallare il pacchetto PyRTF. Andatesu http://pyrtf.sourceforge.net erecuperate il pacchetto PyRTF-0.45.tar.gz. Salvatelo e usate ilgestore degli archivi perscompattarlo. Quindi dal terminaleposizionatevi dove lo avete estratto.Primabisogna installarlo, quindidigitate "sudopython setup.pyinstall" e verrà installato per voi.Notate che c'è una cartella chiamataexamples. Contiene informazioni utilisu comeeseguire operazioniavanzate.

Eccoci qua. Comenostra abitudinecreiamo lo scheletro del programmache èmostrato nella pagina seguente,in alto a destra.

“ Caspita! È difficilecredere che questa èla parte 24. Sonodue anni che stiamoimparando Python!

Page 20: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 50 9 indice ^

PROGRAMMARE IN PYTHON - PARTE 24

Primadi procedere, discuteremodi cosa abbiamo fatto. La riga 2importa la libreria PyRTF.Osservatel'uso di un formato di import diversodal solito. Serve per importare tutto,dalla libreria.

La nostra routine principale dilavoro èMakeExample. Per ora solo loscheletro. La funzioneOpenFile crea ilfile con il nomepassatogli, aggiungel'estensione "rtf", attiva lamodalità"scrittura" e restituisce l'oggetto.

Abbiamogià discusso inprecedenza la funzione __name__ma,per rinfrescarvi lamemoria, seeseguiamo il programmada solo, lavariabile interna __name__ èimpostata a "__main__". Se invece lousiamo importandolo in un altroprogrammaallora questa porzione dicodice sarà ignorata.

Qui creiamouna istanzadell'oggettoRender, chiamiamo laroutineMakeExample e recuperiamol'oggetto restituito doc.Quindiscriviamo il file (in doc) usando lafunzioneOpenFile.

Ora la parte principale dellaroutineMakeExample. Sostituitel'istruzione pass con il codice

mostrato in basso.

Diamoun'occhiata a quello cheabbiamo fatto. Nella prima rigaabbiamo creato un’istanza diDocument.Quindi abbiamo creatouna istanza del foglio di stile.Proseguiamo creandoun’istanzadell'oggetto section e lo aggiungiamoal documento. Pensate a una sezionecomeaun capitolo di un libro.Quindicreiamounparagrafo usando lo stileNormale. L'autore di PyRTF lo hadefinito con carattereArial, 11 punti.Possiamo inserire qualunque testo nelparagrafo, aggiungentelo alla sezionee restituendo il nostro documentodoc.

Èmolto facile. Ancora, dovetepianificare l'aspetto finale conmoltaattenzione,manon inmanieraonerosa.

Salvate il programma come

"rtftesta.py" ed eseguitelo.Quandoha finito, usateOpenOffice (oLibreOffice) per aprire e controllare ilfile.

Ora facciamoqualcosa di piùsfizioso. Primo, aggiungeremoun'intestazione. Ancora una volta,l'autore di PyPDF ci ha fornito unostile predefinito chiamatoHeader1.Lo useremoper la nostra intestazione.

Tra le righe doc.Sections.append ep=Paragraph, aggiungete quanto segue.

p =Paragraph(ss.ParagraphStyles.Heading1)

p.append('Example Heading1')

section.append(p)

#!/usr/bin/env pythonfrom PyRTF import *

def MakeExample():pass

def OpenFile(name) :return file('%s.rtf' % name, 'w')

if __name__ == '__main__' :DR = Renderer()doc = MakeExample()DR.Write(doc, OpenFile('rtftesta'))print "Finished"

doc = Document()ss = doc.StyleSheetsection = Section()doc.Sections.append(section)

p = Paragraph(ss.ParagraphStyles.Normal)p.append('This is our first test writing to a RTF file. '

'This first paragraph is in the preset style called normal ''and any following paragraphs will use this style until we change it.')

section.append(p)

return doc

Page 21: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 50 10 indice ^

Cambiate il nomedel file rtf in“rtftestb”. Dovrebbe assomigliare aquesto:

DR.Write(doc,OpenFile('rtftestb'))

Salvatelo come rtftestb.py edeseguitelo.Ora abbiamoun'intestazione. Sono sicuro chestarete già pensando alle numeroseoperazioni che potremmo fare. Eccoun elenco degli stili predefiniti fornitidall'autore.

Normale, Normale Breve,Intestazione 1, Intestazione 2,NormaleNumerato, NormaleNumerato 2. C'è anche lo stile Elenco,che lascerò scoprire a voi stessi. Sevolete saperne di più, su questo oaltre cose, gli stili sonodefiniti nel fileElements.py, nel percorso diinstallazione.

Anche se questi stili vannogeneralmente bene, vorreste poter

usare qualcosa di nuovo. Vediamocome cambiare al volo tipo,dimensione e attributi (grassetto,corsivo, ecc) dei caratteri. Dopo ilnostro paragrafomaprimadirestituire l'oggetto documentinseriamo il codicemostrato in alto adestra, e cambiamo il nomedel filerisultante in rtftestc. Salvate il filecome rtftestc.py. Ed eseguitelo. Lanuova porzione di codice dovrebbesomigliare a questo...

È anche possibile fornire nuoveversioni per gli elementi di uno stile.Per esempio, potete cambiare solo ladimensione del carattere a 24 punti oil tipo a Impact o anche più attributicomegrassetto o corsivo o entrambi.

Cosa abbiamo fatto adesso? Lariga 1 crea un nuovoparagrafo.Quindi iniziamo, come fatto prima, ainserire il nostro testo.Osservate la

quarta riga (TEXT(' size to 24point',size = 48),). Usando il qualificatoreTEXT, stiamodicendo aPyRTFdi farequalcosa di diverso nelmezzo dellafrase, che in questo caso è cambiare ladimensione del carattere (Arial inquesto punto) a 24 punti, seguito dalcomando 'size = '.Ma, aspettate unattimo. 'size =' dice 48 equello chestiamo stampandodice 24 punti, el'output sarà a 24 punti. Cosa èaccaduto? Bé, il comando size è inmezzi punti. Così se vogliamouncarattere a 8 punti dobbiamousaresize = 16. Ha senso?

Quindi, proseguiamoe cambiamoil carattere con il comando 'font ='.Ancora, riguarderà tutto quelloinserito tra gli apici singoli delqualificatore TEXT.

Ok, Se tutto questo ha senso,cos'altro possiamo fare?

Possiamo impostare il colore deltesto del comando in linea TEXT.Comequesto.

p = Paragraph()

p.append('This is a newparagraph with the word',

TEXT('RED',colour=ss.Colours.Red),

' in Red text.')

section.append(p)

Notate che non abbiamobisognodi impostare lo stile del paragrafo suNormale, poiché verrà usato fino alprossimo cambiamento. Notateanche che se vivete negli U.S.A.dovrete usare il nomedi colore"appropriato".

PROGRAMMARE IN PYTHON - PARTE 24

p = Paragraph(ss.ParagraphStyles.Normal)p.append( 'It is also possible to provide overrides for elements of a style. ',

'For example you can change just the font ',TEXT(' size to 24 point', size=48),' or',TEXT(' typeface to Impact', font=ss.Fonts.Impact),' or even more Attributes like',TEXT(' BOLD',bold=True),TEXT(' or Italic',italic=True),TEXT(' or BOTH',bold=True,italic=True),'.' )

section.append(p)

“ Vediamo comecambiare al volotipo, dimensione eattributi (grassetto,corsivo, ecc) deicaratteri.

Page 22: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 50 11 indice ^

Ecco i colori (di nuovo) predefiniti:Black, Blue, Turquoise, Green, Pink,Red, Yellow,White, BlueDark, Teal,GreenDark, Violet, RedDark,YellowDark, GreyDark andGrey.

Ed ecco un elenco dei caratteripredefiniti (nellanotazioneperusarli):

Arial, ArialBlack, ArialNarrow,BitstreamVeraSans,BitstreamVeraSerif, BookAntiqua,BookmanOldStyle, BookmanOldStyle,Castellar, CenturyGothic,ComicSansMS, CourierNew,FranklinGothicMedium, Garamond,Georgia, Haettenschweiler, Impact,LucidaConsole, LucidaSansUnicode,MicrosoftSansSerif, PalatinoLinotype,MonotypeCorsiva, Papyrus, Sylfaen,Symbol, Tahoma, TimesNewRoman,TrebuchetMSeVerdana.

Ora starete pensando che tuttoquesto è bello e buono,ma comecreare degli stili propri? Èmolto facile.Spostiamoci all'inizio del nostro file e,primadella riga di intestazione,aggiungiamo il codice seguente.

result = doc.StyleSheet

NormalText =TextStyle(TextPropertySet(result.Fonts.CourierNew,16))

ps2 =ParagraphStyle('Courier',NormalText.Copy())

result.ParagraphStyles.append(ps2)

Primadi scrivere il codice perusarlo, osserviamo cosa abbiamofatto. Abbiamo creato una istanza diun nuovo foglio di stile chiamatoresult. Nella seconda riga, abbiamoimpostato il carattere a CourierNew8punti e quindi registrato lo stile comeCourier. Ricordate, dobbiamousare16 comedimensione dato che ladimensione del carattere è inmezzipunti.

Ora, primadella riga return infondo alla funzione, inseriamounnuovoparagrafo usando lo stileCourier.

Così ora possedete un nuovo stileche potrete usare a piacimento.

Potete usare qualunque caratteredall'elenco precedente e creare unostile personalizzato. Basta copiare ilcodice e sostituire carattere edimensione comedesiderato.Possiamo fare anche questo...

NormalText =TextStyle(TextPropertySet(result.Fonts.Arial,22,bold=True,colour=ss.Colours.Red))

ps2 =ParagraphStyle('ArialBoldRed',NormalText.Copy())

result.ParagraphStyles.append(ps2)

E aggiungere il codice in basso...

p =Paragraph(ss.ParagraphStyles.ArialBoldRed)

p.append(LINE,'Andnow we are using theArialBoldRedstyle.',LINE)

section.append(p)

perstampare lostileArialBoldRed.

Tabelle

Molte volte, le tabelle sono l'unicomodoper rappresentarepropriamente dati in un documento.Creare tabelle inmodalità testo èdifficile e, in alcuni casi, è più sempliceconPyRTF. Vi spiegherò questaaffermazione più avanti in questoarticolo.

Osserviamouna tabella standard(mostrata in basso) inOpenOffice/LibreOffice. Sembra unfoglio di calcolo, dove tutto èincolonnato.

Le righe vannoda sinistra a destra,

PROGRAMMARE IN PYTHON - PARTE 24

p = Paragraph(ss.ParagraphStyles.Courier)p.append('Now we are using the Courier style at 8 points. '

'All subsequent paragraphs will use this style automatically. ''This saves typing and is the default behaviour for RTF documents.',LINE)

section.append(p)p = Paragraph()p.append('Also notice that there is a blank line between the previous paragraph ',

'and this one. That is because of the "LINE" inline command.')

section.append(p)

Page 23: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 50 12 indice ^

PROGRAMMARE IN PYTHON - PARTE 24le colonne verso il basso. Un concettosemplice.

Iniziamouna nuova applicazione echiamiamola rtfTable-a.py. Partiamodal nostro codice standard (mostratonella pagina seguente) e proseguiamoda lì.

Non c'èmotivo di discuterne datoche è in pratica lo stesso codice usatoprima.Ora, arricchiremo la funzioneTableExample. Sto, in pratica, usandoparte del codice d'esempio fornitodall'autore di PyRTF. Sostituitel'istruzione pass nella funzione con ilcodice seguente...

doc = Document()

ss = doc.StyleSheet

section = Section()

doc.Sections.append(section)

Questa parte è la stessa dellaprecedente, quindi glissiamo.

table =Table(TabPS.DEFAULT_WIDTH *7,

TabPS.DEFAULT_WIDTH * 3,

TabPS.DEFAULT_WIDTH * 3)

Questa riga (sì, è davvero una riga,ma spezzettata per renderla piùleggibile) crea la nostra tabella base.Stiamo creandouna tabella con 3colonne, la prima è larga 7 tabulazioni,la seconda e la terza 3 tabulazioni.Oltre alle tabulazioni è possibile usareanche il twip. Di più a riguardo tra unattimo.

c1 = Cell(Paragraph('RowOne, Cell One'))

c2 = Cell(Paragraph('RowOne, Cell Two'))

c3 = Cell(Paragraph('RowOne, Cell Three'))

table.AddRow(c1,c2,c3)

Qui stiamo impostando i dati cheandranno in ciascuna cella della primariga.

c1 =Cell(Paragraph(ss.ParagraphStyles.Heading2,'Heading2Style'))

c2 =Cell(Paragraph(ss.ParagraphStyles.Normal,'Back to NormalStyle'))

c3 = Cell(Paragraph('MoreNormal Style'))

table.AddRow(c1,c2,c3)

Questo gruppodi codice imposta idati per la seconda riga. Notate comesia possibile impostare uno stiledifferente per una opiù celle.

c1 =Cell(Paragraph(ss.ParagraphStyles.Heading2,'Heading2Style'))

c2 =Cell(Paragraph(ss.ParagraphStyles.Normal,'Back to NormalStyle'))

c3 = Cell(Paragraph('MoreNormal Style'))

table.AddRow(c1,c2,c3)

Questo imposta la riga finale.

section.append(table)

return doc

Questo aggiunge la tabella allasezione e restituisce il documento perla stampa.

Salvate ed eseguite l'applicazione.Notate che tutto è comeprevistotranne che per l'assenza di bordi nellatabella. Questo ci complica il lavoro.Sistemiamolo. Ancora, useròprincipalmente il codice dal filed'esempio fornito dall'autore diPyRTF.

Salvate il file come rtftable-b.py.Ora, cancellate tutto quello tra'doc.Sections.append(section)' e'return doc' nella funzioneTableExample e sostituetolo con il

#!/usr/bin/env python

from PyRTF import *

def TableExample():pass

def OpenFile(name):return file('%s.rtf' % name, 'w')

if __name__ == '__main__':DR = Renderer()doc = TableExample()DR.Write(doc, OpenFile('rtftable-a'))print "Finished"

Page 24: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 50 13 indice ^

seguente...

thin_edge =BorderPS(width=20,style=BorderPS.SINGLE )

thick_edge =BorderPS(width=80,style=BorderPS.SINGLE )

thin_frame =FramePS(thin_edge,thin_edge, thin_edge,thin_edge )

thick_frame =FramePS(thick_edge,thick_edge, thick_edge,thick_edge )

mixed_frame =FramePS(thin_edge,thick_edge, thin_edge,thick_edge )

Qui stiamo impostando ledefinizioni deimargini e delle corniciche useremo.

table = Table(TabPS.DEFAULT_WIDTH * 3,TabPS.DEFAULT_WIDTH * 3,TabPS.DEFAULT_WIDTH * 3

)

c1 = Cell( Paragraph( 'R1C1'), thin_frame )

c2 = Cell( Paragraph( 'R1C2') )

c3 = Cell( Paragraph( 'R1C3'), thick_frame )

table.AddRow( c1, c2, c3 )

Nella prima riga, la cella nellaprima colonna (cornice sottile) equella nella terza colonna (cornicespessa) avrannounbordo tutto intorno.

c1 = Cell( Paragraph( 'R2C1') )

c2 = Cell( Paragraph( 'R2C2') )

c3 = Cell( Paragraph( 'R2C3') )

table.AddRow( c1, c2, c3 )

Nessuna cella della seconda rigaavrà il bordo.

c1 = Cell( Paragraph( 'R3C1'), mixed_frame )

c2 = Cell( Paragraph( 'R3C2') )

c3 = Cell( Paragraph( 'R3C3'), mixed_frame )

table.AddRow( c1, c2, c3 )

Ancora una volta, le celle dellaprima e terza colonna della terza rigapresentano una cornicemista.

section.append( table )

Finito. Avete quasi tutto quellonecessario per creare, col codice,documenti RTF.

Ci vediamo la prossima volta!

Il codice può essere recuperato, comeal solito, su pastebin. La primaparte latrovate all'indirizzohttp://pastebin.com/3Rs7T3D7 che èla sommadi rtftest.py (a-e),mentre laseconda, rtftable.py (a-b), la trovateall’indirizzohttp://pastebin.com/XbaE2uP7.

GregWaltersè il proprietariodellaRainyDaySolutions, LLC, una societàdiconsulenza inAurora, Coloradoeprogrammadal 1972.Amacucinare, fareescursioni, ascoltaremusicaepassare iltempocon la sua famiglia. Il suo sitowebèwww.thedesignatedgeek.com.

PROGRAMMARE IN PYTHON - PARTE 24

Page 25: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 51 7 indice ^

HHOOWW--TTOOScritto da Greg Walters PPrrooggrraammmmaarree iinn PPyytthhoonn -- PPaarrttee 2255

A lcuni di voi hannolasciato commenti sugliarticoli dedicati allaprogrammazione di GUI e

su quanto vi siano piaciuti. Comerisposta inizieremo a dareun’occhiata ad un diverso gruppo distrumenti per creare interfaccegrafiche, chiamato Tkinter. Si trattadegli strumenti “ufficiali” percreare GUI in python. Tkinter è incircolazione da parecchio e si èfatto una brutta fama per il suostile un po’ retró. Recentemente lecose sono cambiate così pensopossiamo cercare di sfatare questopregiudizio.NOTATE - Tutto il codice quipresentato riguarda unicamentePython 2.x. In uno dei prossimiarticoli parleremo di Tkinter conPython 3.x. Se siete “costretti” ausare la versione 3.x sostituitel’istruzione import con “fromtkinter import *”.

Una breve storia equalche antefatto

TKinter sta per “Tk interface”,interfaccia Tk. Tk è un linguaggio di

programmazione unico e il moduloTkinter permette di usarne icomponenti grafici. Ci sono diversiwidget nativi. Il contenitore dettoToplevel (la finestra), pulsanti,etichette, cornici, campi di testo,pulsanti di selezione singola omultipla, canvas, campi di testomulti-riga ed altri. Ci sono moltimoduli che aggiungono ulteriorifunzionalità. Questo mese ciconcentreremo su 4 widget. IlToplevel (da questo momentosemplicemente finestra radice),cornici, etichette e pulsanti. Nelprossimo articolo ci occuperemopiù approfonditamente di altriwidget.

In pratica il contenitore di altolivello contiene gli altri widget. Sitratta della finestra radice omaster. All’interno di questaposizioniamo i controlli chevogliamo usare nel nostroprogramma. Ciascuno di questi, adeccezione della finestra principale,ha un genitore. Nonnecessariamente il genitore deveessere la finestra. Può essere unaltro widget. Di più a riguardo ilmese prossimo. Questo mese tutti i

widget avrannocome genitore lafinestra principale.

Per posizionare evisualizzare i widgetfigli, dobbiamousare la cosiddetta“gestione geometrica”. Si tratta dicome i vari componenti sonoinseriti nella finestra. La maggiorparte dei programmatori usa unodei tre tipi di organizzazione, pack,grid o place. In base alla mia umileopinione il metodo pack è moltosciatto. Lascio a voi constatarlo. Ilmetodo di gestione place permetteun accurato posizionamento deiwidget, ma può risultarecomplicato. Ne parleremo in unodei prossimi articoli. Per questavolta ci concentreremo sul metodogrid, a griglia.

Pensate a un foglio di calcolo. Cisono righe e colonne. Le colonnesono verticali, le righe orizzontali.Ecco una semplicerappresentazione testuale degliindirizzi delle celle in una tabella di5 colonne e 4 righe (in alto adestra).

Allora, il genitore contiene lagriglia, i widget vanno nelle celle. Aprima vista questo può sembraremolto limitante. Però i widgetpossono estendersi e occupare piùcelle di una colonna, di una riga o dientrambe.

Primo esempio

Il nostro primo esempio èMOLTO facile (solo quattro righe),ma è un buon inizio.

from Tkinter import *

root = Tk()

button = Button(root, text ="Hello FullCircle").grid()

root.mainloop()

Allora, cosa abbiamo fatto? Lariga uno importa la libreria Tkinter.Nella seguente, istanziamo

COLONNE - >RIGHE| 0,0 | 1,0 | 2,0 | 3,0 | 4,0 |

| | 0,1 | 1,1 | 2,1 | 3,1 | 4,1 || 0,2 | 1,2 | 2,2 | 3,2 | 4,2 || 0,3 | 1,3 | 2,3 | 3,3 | 4,3 |

Page 26: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 51 8 indice ^

PROGRAMMARE IN PYTHON - PARTE 25l’oggetto Tk usando root (Tk èparte di Tkinter). Ecco la riga tre:

button = Button(root, text ="Hello FullCircle").grid()

Creiamo un pulsante chiamatobutton, impostiamo come suogenitore la finestra root,impostiamo il suo testo a “HelloFullCircle” e lo sistemiamo nellagriglia. Per finire, chiamiamo ilmainloop della finestra. Moltosemplice dalla nostra prospettiva,ma molto avviene dietro la scena.Per fortuna, non dobbiamo capirloin questo momento.

Eseguite il programma eosservate cosa accade. Sul miocomputer la finestra principale èmostrata nell’angolo in basso asinistra dello schermo. Nel vostrocaso, potrebbe essere da tutt’altraparte. Sistemiamo la cosa con ilprossimo esempio.

Secondo esempio

Questa volta creeremo unaclasse chiamata App. Conterrà lanostra finestra. Iniziamo.

from Tkinter import *

È l’istruzione per la libreria

Tkinter.

Definiamo la nostra classe e,nella funzione __init__,configuriamo i widget e liposizioniamo nella griglia.

La prima riga nella routine__init__ crea una cornice (frame)che sarà il genitore di tutti gli altriwidget. Il genitore della cornice è lafinestra (widget Toplevel). Quindidefiniamo una etichetta e duepulsanti. Osserviamo la riga checrea l’etichetta.

self.lblText = Label(frame,text = "This is a labelwidget")

Abbiamo creato l’etichettachiamandola self.lblText. È eridatadall’oggetto Label. Impostiamo ilsuo genitore (frame) e il testo chevogliamo visualizzare (text = “this isa label widget”). Tutto quì.

Potremmo fare di più, ma per ilmomento è sufficiente. A seguireconfiguriamo i due pulsanti di cuiabbiamo bisogno:

self.btnQuit = Button(frame,text="Quit", fg="red",command=frame.quit)

self.btnHello = Button(frame,text="Hello",command=self.SaySomething)

Assegnamo un nome, il lorogenitore (frame) e il testo damostrare. btnQuit ha l’attributo fgimpostato su “red”. Avrete intuitoche serve a colorare di rosso ilcolore in primo piano o il colore deltesto. L’ultimo attributo serve aconfigurare il comando di supporto(callback) che vogliamo utilizzarequando l’utente fa clic sul pulsante.In questo caso si tratta diframe.quit, che termina ilprogramma. È una funzionepredefinita, quindi non dobbiamo

crearla. Nel caso di btnHello sitratta della funzione chiamataself.SaySomething. Questadobbiamo crearla, ma primaabbiamo altro da fare.

Dobbiamo inserire i widget nellagriglia. Ecco ancora le righe:

frame.grid(column = 0, row =0)

self.lblText.grid(column = 0,row = 0, columnspan = 2)

self.btnHello.grid(column =0, row = 1)

self.btnQuit.grid(column = 1,row = 1)

Prima assegnamo una griglia allacornice. Quindi impostiamol’attributo grid di ciascun widgetcon la posizione nella griglia.Notate la riga columnspan perl’etichetta (self.lblText). Serve perfar occupare all’etichetta due

class App:def __init__(self, master):

frame = Frame(master)self.lblText = Label(frame, text = "This is a label widget")self.btnQuit = Button(frame, text="Quit", fg="red", command=frame.quit)self.btnHello = Button(frame, text="Hello", command=self.SaySomething)frame.grid(column = 0, row = 0)self.lblText.grid(column = 0, row = 0, columnspan = 2)self.btnHello.grid(column = 0, row = 1)self.btnQuit.grid(column = 1, row = 1)

Page 27: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 51 9 indice ^

colonne. Poiché abbiamo solo duecolonne, allora si tratta dell’interalarghezza dell’applicazione. Orapossiamo creare la funzione disupporto:

def SaySomething(self):

print "Hello to FullCircleMagazine Readers!!"

Questo, semplicemente, stampanel terminale il messaggio “Hello toFullCircle Magazine Readers!!”

Per finire instanziamo la classeTk, la nostra classe App, edeseguiamo il ciclo principale.

root = Tk()

app = App(root)

root.mainloop()

Proviamola. Ora le coseeffettivamente funzionano. Ma dinuovo la posizione della finestra èinappropriata. Sistemiamola nelprossimo esempio.

Terzo esempio

Salvate l’ultimo esempio comeexample3.py. È tutto esattamenteuguale tranne che per una riga. È infondo alla nostra funzioneprincipale. Ecco le righe precedenticon quella nuova:

root = Tk()

root.geometry('150x75+550+150')

app = App(root)

root.mainloop()

Il suo scopoè forzare lafinestra inizialead unalarghezza di 150pixel e ad unaaltezza di 75pixel. Inoltre sisceglie diposizionarel’angolo in alto asinistra a 550pixel dal

margine sinistro dello schermo e a150 dal margine superiore. Comeho ottenuto questi numeri? Hoiniziato con dei valori approssimatie li ho perfezionati un po’ alla volta.È un metodo un po’ complicato, mail risultato è meglio di niente.

Quarto esempio - Unsemplice calcolatore

Ora proviamo a realizzarequalcosa di più complicato. Questavolta creeremo unacalcolatrice “4funzioni”, ovvero con lequattro operazionibase: addizione,sottrazione,moltiplicazione edivisione.

Ci dedicheremo adessa e spiegherò man mano ilcodice (al centro a destra).

Ad eccezione dell’istruzionegeometry, questo (a sinistra)dovrebbe ora essere facile dacomprendere. Ricordate, prendetedei valori approssimati, aggiustatelie proseguite.

Iniziamo con la definizione dellaclasse e impostiamo la funzione

__init__. Configuriamo tre variabilicome segue:• CurrentValue: conterrà il valoreattuale inserito nella calcolatrice.• HolderValue: conterrà il valoreinserito prima di scegliereun’operazione.• CurrentFunction: è un semplicepromemoria dell’operazioneattualmente usata.

A seguire, definiamo la variabile

PROGRAMMARE IN PYTHON - PARTE 25

-----------------| 0 |-----------------| 1 | 2 | 3 | + |-----------------| 4 | 5 | 6 | - |-----------------| 7 | 8 | 9 | * |-----------------| - | 0 | . | / |-----------------| = |-----------------| CLEAR |-----------------

from Tkinter import *

def StartUp():global val, w, rootroot = Tk()root.title('Easy Calc')root.geometry('247x330+469+199')w = Calculator(root)root.mainloop()

class Calculator():def __init__(self,root):

master = Frame(root)self.CurrentValue = 0self.HolderValue = 0self.CurrentFunction = ''self.CurrentDisplay = StringVar()self.CurrentDisplay.set('0')self.DecimalNext = Falseself.DecimalCount = 0self.DefineWidgets(master)self.PlaceWidgets(master)

Page 28: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 51 10 indice ^

CurrentDisplay e l’assegnamoall’oggetto StringVar. Si tratta di unoggetto speciale di Tkinter.Qualunque widget gli vengaassegnato il valore di quest’ultimoè aggiornato automaticamente. Inquesto caso, lo useremo perconservare qualunque cosavogliamo che il widget etichetta,bé..., mostri. Dobbiamo instanziarloprima di assegnarlo al widget.Quindi usiamo la funzione nativa‘set’. Poi definiamo una variabilebooleana chiamata DecimalNext, lavariabile DecimalCount echiamiamo prima la funzioneDefineWidgets, che crea tutti iwidget, e poi PlaceWidget, che liposiziona nella finestra.

defDefineWidgets(self,master):

self.lblDisplay =Label(master,anchor=E,relief=SUNKEN,bg="white",height=2,textvariable=self.CurrentDisplay)

Precedentemente abbiamodefinito una etichetta. Però questavolta aggiungeremo altri attributi.Notate che non usiamo l’attributo‘text’. Invece assegneremol’etichetta al genitore (master),quindi imposteremo l’ancoraggio

(o, per i nostri scopi,l’allineamento) deltesto, quando vienescritto. In questocaso, stiamo dicendoall’etichetta diallineare tutto iltesto a Est o sul latodestro del widget.Esiste l’attributo justify, ma èutilizzato per i campi di testo multi-riga. L’attributo anchor ha leseguenti opzioni: N, NE, E, SE, S,SW, W, NW e CENTER. Quellopredefinito è CENTER. Doveteimmaginare una bussola. Incondizioni normali, gli unici valoridavvero utilizzabili sono E (destra),W(sinistra) e Center.

Quindi configuriamo il margine ostile visivo dell’etichetta. Le opzioni“legali” sono FLAT (piatto), SUNKEN(incavato), RAISED (rialzato),GROOVE (scanalato) e RIDGE(rialzato). Il valore predefinito, senon viene specificato nulla, è FLAT.Siete liberi di provare voi stessi lealtre combinazioni. A seguireimpostiamo uno sfondo (bg) biancoper differenziarlo un po’ dal restodella finestra. Impostiamo l’altezzaa 2 (cioè due righe di testo, nonpixel) e, per finire, assegnamo lavariabile definita un attimo fa

(self.CurrentDisplay) all’attributotextvariable. Ogniqualvolta il valoredi self.CurrentDisplay cambia,l’etichettà rifletterà talecambiamento.

Come mostrato in basso,creeremo alcuni pulsanti.

Vi ho mostrato solo quattropulsanti. Questo perché, comepotete vedere, il codice è quasiesattamente lo stesso. Di nuovo,abbiamo creato i pulsantiprecedentemente in questalezione, ma osserviamo piùattentamente cosa abbiamo fatto.

Iniziamo definendo il genitore(master), il testo che vogliamo sulpulsante, larghezza e altezza.Notate come la larghezza siaespressa in caratteri e l’altezza inrighe di testo. Se avete intenzionedi inserire un’immagine nelpulsante allora dovrete usare ipixel. Questa differenza potrebbe

confondere un po’ prima diprenderci la mano. A seguire,impostiamo l’attributo bind.Quando, negli esempi precedenti,abbiamo creato i pulsanti abbiamousato l’attributo ‘command=’ perindicare la funzione da chiamare incaso di clic dell’utente. Questavolta useremo l’attributo ‘.bind’. Èpraticamente la stessa cosa, ma piùsemplice e con la possibilità dipassare argomenti alla funzione,che è statica. Notate che stiamousando ‘<ButtonRelease-1>’ comeinnesco dell’associazione. In questocaso, vogliamo assicurarci che solodopo il rilascio del pulsante sinistrodel mouse venga chiamata lafunzione di callback. Infine,definiamo la funzione di supportoda chiamare e cosa le andremo apassare. Ora i più astuti, cioe tuttivoi, avranno notato una novità. Lachiamata ‘lambda e:’.

In Python, si usa Lambda perdefinire funzioni anonime che

PROGRAMMARE IN PYTHON - PARTE 25

self.btn1 = Button(master, text = '1',width = 4,height=3)self.btn1.bind('<ButtonRelease-1>', lambda e: self.funcNumButton(1))self.btn2 = Button(master, text = '2',width = 4,height=3)self.btn2.bind('<ButtonRelease-1>', lambda e: self.funcNumButton(2))self.btn3 = Button(master, text = '3',width = 4,height=3)self.btn3.bind('<ButtonRelease-1>', lambda e: self.funcNumButton(3))self.btn4 = Button(master, text = '4',width = 4,height=3)self.btn4.bind('<ButtonRelease-1>', lambda e: self.funcNumButton(4))

Page 29: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 51 11 indice ^

PROGRAMMARE IN PYTHON - PARTE 25appaiono all’interprete comeistruzioni valide. Questo cipermette di inserire segmentimultipli in una singola riga dicodice. Immaginatela come unamini-funzione. In questo casostiamo impostando il nome dellafunzione di supporto e il valore chesi vuole inviarle, oltre al tag event(e:). Parleremo piùapprofonditamente di Lambda inuno dei prossimi articoli. Per oralimitatevi a seguire l’esempio.

Vi ho fornito i primi quattropulsanti. Copiate e incollate ilcodice sopra per i pulsanti da 5 a 9e per lo 0. Sono uguali ad eccezionedel nome e del valore inviato allafunzione callback. I passi seguentisono mostrati a destra.

Le uniche cose di cui non cisiamo ancora occupati sonocolumnspan e l’attributo sticky.Come detto prima, un widget puòestendersi a più colonne o righe. Inquesto caso, si “stira” il widgetetichetta per quattro colonne.Questo è quello che determinal’attributo “columnspan”. Esiste unequivalente “rowspan”. L’attributo“sticky” dice al widget doveallineare i suoi margini. Pensate adesso a come il widget riempie la

self.btnDash = Button(master, text = '-',width = 4,height=3)self.btnDash.bind('<ButtonRelease-1>', lambda e: self.funcFuncButton('ABS'))self.btnDot = Button(master, text = '.',width = 4,height=3)self.btnDot.bind('<ButtonRelease-1>', lambda e: self.funcFuncButton('Dec'))

btnDash rende assoluto il valore inserito. 523 resta 523 e -523 diventa 523. Il pulsante btnDot inserisce ilpunto decimale. Questi esempi, e quelli sotto, usano la funzione di callback funcFuncButton.

self.btnPlus = Button(master,text = '+', width = 4, height=3)self.btnPlus.bind('<ButtonRelease-1>', lambda e: self.funcFuncButton('Add'))self.btnMinus = Button(master,text = '-', width = 4, height=3)self.btnMinus.bind('<ButtonRelease-1>', lambda e:

self.funcFuncButton('Subtract'))self.btnStar = Button(master,text = '*', width = 4, height=3)self.btnStar.bind('<ButtonRelease-1>', lambda e: self.funcFuncButton('Multiply'))self.btnDiv = Button(master,text = '/', width = 4, height=3)self.btnDiv.bind('<ButtonRelease-1>', lambda e: self.funcFuncButton('Divide'))self.btnEqual = Button(master, text = '=')self.btnEqual.bind('<ButtonRelease-1>', lambda e: self.funcFuncButton('Eq'))

Ecco i quattro pulsanti per le funzioni matematiche.self.btnClear = Button(master, text = 'CLEAR')self.btnClear.bind('<ButtonRelease-1>', lambda e: self.funcClear())

Per finire, ecco il pulsante clear. Ovviamente, ripulisce le variabili contenitore e lo schermo. Ora posizioniamoi widget nella funzione PlaceWidget. Prima inizializziamo la griglia, quindi mettiamo i widget in essa. Ecco laprima parte della routine.

def PlaceWidgets(self,master):master.grid(column=0,row=0)self.lblDisplay.grid(column=0,row=0,columnspan = 4,sticky=EW)self.btn1.grid(column = 0, row = 1)self.btn2.grid(column = 1, row = 1)self.btn3.grid(column = 2, row = 1)self.btn4.grid(column = 0, row = 2)self.btn5.grid(column = 1, row = 2)self.btn6.grid(column = 2, row = 2)self.btn7.grid(column = 0, row = 3)self.btn8.grid(column = 1, row = 3)self.btn9.grid(column = 2, row = 3)self.btn0.grid(column = 1, row = 4)

Page 30: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 51 12 indice ^

PROGRAMMARE IN PYTHON - PARTE 25griglia. In alto a sinistra c’è il restodei pulsanti.

Prima di procedereulteriormente diamo un’occhiata acome funziona il tutto quandol’utente preme i pulsanti.

Diciamo che l’utente vuoleinserire 563 + 127 ed ottenere larisposta. Lui premerà o farà clic(logicamente) su 5, 6, 3, quindi “+” e1, 2, 7, e infine il pulsante “=”. Comelo traduciamo in codice? Abbiamogià impostato la funzione disupporto per i pulsanti dei numeri afuncNumButton. Ci sono due modidi procedere. Possiamo mantenerel’informazione inserita comestringa e quando ne abbiamobisogno convertirla in un numero, oconservarla tutto il temposottoforma numerica. Useremo laseconda strada. Per farlo,conserveremo il valore inserito (0quando si inizia) in una variabilechiamata “self.CurrentValue”.Quando viene inserito un numero,si prende la variabile, la si moltiplicaper 10 e si aggiunge il nuovo valore.Così, quando l’utente inserisce 5, 6e 3, facciamo quanto segue...

L'utente fa clic su 5 – 0 *10 + 5 (5)

L'utente fa clic su 6 – 5 *10 + 6 (56)

L'utente fa clic su 3 – 56 *10 + 3 (563)

Ovviamente visualizziamo ilcontenuto di “self.CurrentValue”nell’etichetta.

Quindi l’utente fa clic su “+”.Prendiamo il valore in“self.CurrentValue” e lo mettiamoin “self.HolderValue” e azzeriamo“self.CurrentValue”. Quindiripetiamo il procedimento per 1, 2 e7. Quando l’utente fa clic su “=”allora sommiamo i valori di“self.CurrentValue” e“self.HolderValue”, lo mostriamo epoi ripuliamo entrambe le variabili

per continuare.

In alto c’è il codice per iniziare adefinire le funzioni di supporto.

La routine “funcNumButton”riceve il valore che le abbiamopassato alla pressione del pulsante.L’unica differenza dall’esempiosopra è quello che accade sel’utente preme il pulsante col puntodecimale (“.”). In basso, vedrete cheusiamo una variabile booleana perricordare questa occorrenza e, alclic successivo, lo gestiamo. Questoè il significato della riga “ifself.DecimalNext == True:”.Analizziamola.

L’utente fa clic su 3, poi 2, quindi

il punto decimale, poi 4 per creare“32.4”. Gestiamo i clic su 3 e 2 conla funzione “funcNumButton”.Controlliamo self.DecimalNext pervedere se il valore è vero (che inquesto caso non lo è finche non sifa clic sul pulsante “.”). In casocontrario, moltiplichiamo il valoreconservato (self.CurrentValue) per10 e aggiungiamo il valore in arrivo.Quando l’utente fa clic su “.”, lafunzione di callback“funcFuncButton” è chiamata con ilvalore “Dec”. Tutto quello chefacciamo è impostare la variabilebooleana “self.DecimalNext” avero. Quando l’utente fa clic su 4,testeremo il valore di“self.DecimalNext” e, poiché èvero, faremo una magia. Prima

self.btnDash.grid(column = 0, row = 4)self.btnDot.grid(column = 2, row = 4)self.btnPlus.grid(column = 3,row = 1)self.btnMinus.grid(column = 3, row = 2)self.btnStar.grid(column = 3, row = 3)self.btnDiv.grid(column=3, row = 4)self.btnEqual.grid(column=0,row=5,columnspan = 4,sticky=NSEW)self.btnClear.grid(column=0,row=6,columnspan = 4, sticky = NSEW)

def funcNumButton(self,val):if self.DecimalNext == True:

self.DecimalCount += 1self.CurrentValue = self.CurrentValue + (val * (10**-self.DecimalCount))

else:self.CurrentValue = (self.CurrentValue * 10) + val

self.DisplayIt()

Page 31: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 51 13 indice ^

PROGRAMMARE IN PYTHON - PARTE 25incrementiamo la variabileself.DecimalCount. Questa ci dicecon quanti posti decimali stiamolavorando. Quindi prendiamo ilvalore in arrivo, lo moltiplichiamoper (10**-self.DecimalCount).Questo operatore magico cifornisce la funzione “eleva allapotenza di”. Per esempio 10**2restituisce 100. 10**-2 restituisce0.01. Potrebbe accadere cheusando questa funzione si incappiin problemi di arrotondamento, ma,nel caso della nostra semplicecalcolatrice, funzionerà per lamaggior parte dei numeri decimali.Lascio a voi il compito di trovareuna funzione migliore. Prendetelocome il compito a casa per questomese.

La funzione “funcClear” azzerasemplicemente le due variabilicontenitore, quindi imposta lavisualizzazione.

def funcClear(self):

self.CurrentValue = 0

self.HolderValue = 0

self.DisplayIt()

Ora le funzioni. Abbiamo giàparlato di cosa accade con lafunzione “Dec”. La impostiamo

def funcFuncButton(self,function):if function =='Dec':

self.DecimalNext = Trueelse:

self.DecimalNext = Falseself.DecimalCount = 0if function == 'ABS':

self.CurrentValue *= -1self.DisplayIt()

La funzione ABS prende semplicemente il valore corrente e lo moltiplica per -1.elif function == 'Add':

self.HolderValue = self.CurrentValueself.CurrentValue = 0self.CurrentFunction = 'Add'

La funzione Add copia “self.CurrentValue” in “self.HolderValue”, pulisce “self.CurrentValue” e imposta“self.CurrentFunction” su “Add”. Le funzioni sottrazione, moltiplicazione e divisione fanno la stessa cosainserendo il termine appropriato in “self.CurrentFunction”.

elif function == 'Subtract':self.HolderValue = self.CurrentValueself.CurrentValue = 0self.CurrentFunction = 'Subtract'

elif function == 'Multiply':self.HolderValue = self.CurrentValueself.CurrentValue = 0self.CurrentFunction = 'Multiply'

elif function == 'Divide':self.HolderValue = self.CurrentValueself.CurrentValue = 0self.CurrentFunction = 'Divide'

La funzione “Eq” (uguale) è dove avviene la magia. Sara semplice per voi capire ora il codice seguente.elif function == 'Eq':

if self.CurrentFunction == 'Add':self.CurrentValue += self.HolderValue

elif self.CurrentFunction == 'Subtract':self.CurrentValue = self.HolderValue - self.CurrentValue

elif self.CurrentFunction == 'Multiply':self.CurrentValue *= self.HolderValue

elif self.CurrentFunction == 'Divide':self.CurrentValue = self.HolderValue / self.CurrentValue

self.DisplayIt()self.CurrentValue = 0self.HolderValue = 0

Page 32: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 51 14 indice ^

GregWalters è il proprietario dellaRainyDay Solutions, LLC, una società diconsulenza inAurora, Colorado eprogrammadal 1972. Ama cucinare, fareescursioni, ascoltaremusica e passare iltempo con la sua famiglia. Il suo sitowebè:www.thedesignatedgeek.com.

PROGRAMMARE IN PYTHON - PARTE 25prima con l’istruzione “if”. Cispostiamo all’“else” e, se lafunzione è qualcos’altro, ripuliamole variabili “self.DecimalNext” e“self.DecimalCount”.

Il seguente insieme di passi sonomostrati nella pagina seguente (ilbox a destra).

La funzione DisplayItsemplicemente imposta il valorenell’etichetta display. Ricordatequando dicemmo alla etichetta di“monitorare” la variabile“self.CurrentDisplay”. Quandocambia, l’etichettaautomaticamente cambia lavisualizzazione per corrisponderle.Usiamo il metodo “.set” percambiare il valore.

def DisplayIt(self):

print('CurrentValue = {0} -HolderValue ={1}'.format(self.CurrentValue,self.HolderValue))

self.CurrentDisplay.set(self.CurrentValue)

Finalmente abbiamo le righe diavvio.

if __name__ == '__main__':

StartUp()

Ora possiamo eseguire ilprogramma e provarlo.

Come sempre, il codicedell’articolo può essere trovato suPasteBin. Gli esempi 1, 2 e 3 sonoall’indirizzohttp://pastebin.com/mBAS1Umm el’esempio Calc.py si trovaall’indirizzohttp://pastebin.com/LbMibF0u

Il mese prossimo continueremocon Tkinter e i suoi tanti widget. Inun articolo futuro parleremo deldisegnatore di GUI per Tkinterchiamato PAGE. Nel frattempo,divertitevi. Penso che gradireteTkinter.

Page 33: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 52 7 indice ^

HHOOWW--TTOOScritto da Greg Walters PPrrooggrraammmmaarree iinn PPyytthhoonn -- PPaarrttee 2266

I l mese scorso abbiamo parlato ditkInter e di quattro dei suoiwidget: TopLevel, cornici,pulsanti ed etichette. Sempre il

mese scorso vi dissi che avremmodiscusso di come usare altri widget,oltre che TopLevel, come genitore.

Così, questomese, oltre cheapprofondire cornici, pulsanti edetichette introdurremo pulsanti aselezionemultipla (checkbox) esingola (radiobutton), campi di testo(textbox, widget di inserimento),caselle di riepilogo (listbox) con barradi scorrimento verticale emessaggi(messagebox). Prima di iniziare,esaminiamo qualcuno di questiwidget.

I pulsanti a selezionemultiplahanno due opzioni, selezionato e nonselezionato, anche identificabili congli stati on e off. Sono solitamenteusati per fornire una serie di opzionidove è possibile selezionarnequalcuna, molte o tutte. È possibileimpostare un evento che informiquando un controllo a selezionemultipla è selezionato o che valuti ilvalore del widget in qualunquemomento.

I pulsanti a selezione singolahanno anch’essi due possibilità, on eoff. Però sono raggruppati per fornireuna serie di opzioni tra cui scegliernelogicamente una sola. È possibileavere più gruppi di pulsanti aselezione singola che, sepropriamente programmati, noninteragiranno tra loro.

La casella di riepilogo fornisceall’utente una lista di elementi tra cuiscegliere. Il più delle volte si vuole chel’utente possa selezionare un singoloelemento, ma ci sono occasioni in cuisono consentite selezioni multiple.Può anche essere presente una barradi scorrimento orizzontale o verticaleper permettere all’utente unaesplorazione più semplice.

Il nostro progetto consisterà inuna finestra principale e sette corniciche raggrupperanno visivamente inostri widget:

• La prima cornice saràmoltosemplice. Consisterà di varie etichettemostranti le differenti opzioni.

• La seconda, ancora semplice,conterrà i pulsanti relativi alledifferenti opzioni.

• In questa corniceavremo 2 pulsanti aselezionemultipla edun pulsante percambiare il loro statoe ogni variazione saràtrasmessa allafinestra terminale.

• A seguire avremodue gruppi di trepulsanti a selezione singola, ciascunodei quali invierà unmessaggio allafinestra terminale quandoselezionato. I due gruppi sonoseparati.

• Questo ha qualche campo ditesto o di inserimento, che non visono nuovi, ma c’è anche un pulsanteper abilitare o disabilitare uno deidue. Quando disabilitato nel campo ditesto non è possibile inserire nulla.

• Questa è una casella di riepilogocon una barra di scorrimento verticaleche invia unmessaggio al terminaleogni volta si seleziona un elemento, econtiene due pulsanti. Uno perripulire la casella e l’altro per inserirevalori fittizzi.

• La cornice finale conterrà unaserie di pulsanti che richiamerannovari messaggi.

Quindi ora diamo inizio alprogetto. Chiamiamolo“widgetdemo1.py”. Assicuratevi disalvarlo perché lo scriveremo a piccoliblocchi fino all’applicazione finale.Ciascun blocco riguarda una cornice.Noterete chemanmano inserirò deicommenti a cui potrete farriferimento per capire cosa abbiamofatto. In alto ci sono le righe iniziali.

Le prime due (commenti)contengono il nome dell’applicazionee l’oggetto del blocco. La riga trecontiene l’istruzione import. Quindidefiniamo la nostra classe. La rigasuccessiva dà inizio alla routine__init__ che ormai dovreste benconoscerema, se vi siete appena unitia noi, è il codice eseguito quando siistanzia la funzione nelmain delprogramma. Le passiamo la finestra

# widgetdemo1.py# Labelsfrom Tkinter import *

class Demo:def __init__(self,master):

self.DefineVars()f = self.BuildWidgets(master)self.PlaceWidgets(f)

Page 34: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 52 8 indice ^

HOWTO - PROGRAMMARE IN PYTHON - PARTE 26Toplevel o radice, che sarà quellaprincipale. Le ultime, per il momento,tre righe chiamano tre funzionidifferenti. La prima, DefineVars,configurerà varie variabili che ciserviranno via via. La seguente,BuildWidgets, sarà quella checonterrà le definizioni dei nostriwidget, e l’ultima, PlaceWidgets, saràusata per posizionare i controlli nellafinestra principale. Come abbiamofatto l’ultima volta, useremo lagestione geometrica a griglia. Notateche BuildWidgets restituirà l’oggetto“f” (la nostra finestra principale) e lopasseremo alla funzionePlaceWidgets.

In alto a destra c’è la funzioneBuildWidgets. Le righe che inizianocon “self.” sono state divise per duemotivi. Il primo, è buona abitudinecontenere la lunghezza delle righenegli 80 caratteri omeno. Il secondo,è più semplice nel nostromeraviglioso editor. Potete fare duecose. La prima, creare righe lunghe otenerle come sono. Python consentela suddivisione delle righe fintantochésono racchiuse tra parentesi tonde ograffe. Come detto prima, definiamo iwidget prima di inserirli nella griglia.Noterete con una delle prossimefunzioni che è possibile definire unwidget contemporaneamente al suo

posizionamento nella griglia, ma ladefinizione prima del posizionamentorende più semplice il controllo, datoche stiamo facendo lamaggior partedelle definizioni in questa routine.

Allora, prima definiamo la corniceprincipale. Qui inseriremo i rimanentiwidget. Quindi definiamo una cornice,figlia di quella principale, che conterràcinque etichette e la chiamiamolblframe. Qui settiamo i vari attributidel frame. Impostiamo imargini a'SUNKEN', una distanza di 3 pixel asinistra e destra (padx) e 3 pixel in altoe in basso (pady). Impostiamo ancheborderwidth a 2 pixel così chel’effetto incavato sia più visibile,rispetto al valore predefinito 0. Per

finire impostiamo la larghezza totaledel frame a 500 pixel.

Quindi definiamo ciascunaetichetta che andremo ad usare.Come genitore useremo self.lblframee non frame. In questomodo tutte leetichette saranno figlie di lblframeche sarà a sua volta figlia di frame.Notate come ogni definizione siapraticamente la stessa per tutte lecinque etichette ad eccezione delnome del widget (lbl1, lbl2, etc), deltesto, del bordo o effetto visivo. Perfinire, restituiamo il frame allafunzione chiamante (__init__).

La pagina seguente (in alto adestra) mostra la funzione

PlaceWidgets.

Come parametro usiamo l’oggettocornice chiamatomaster. Loassegnamo a frame per adeguarci aquanto già fatto nella funzioneBuildWidgets. A seguire impostiamola griglia principale(frame.grid(column = 0, row = 0)). Senon lo facessimo, nulla funzionerebbecorrettamente. Quindi iniziamo ainserire i widget nelle celle dellagriglia. Prima inseriamo la cornicelblframe che contiene le etichette econfiguriamo i suoi attributi. Lamettiamo nella colonna 0, riga 1,impostiamo una distanza di 5 pixel sututti i lati e le facciamo ricoprire 5colonne (da sinistra a destra) e, per

def BuildWidgets(self,master):# Define our widgetsframe = Frame(master)# Labelsself.lblframe = Frame(frame,relief = SUNKEN,padx = 3, pady = 3,

borderwidth = 2, width = 500)self.lbl1 = Label(self.lblframe,text="Flat Label",relief = FLAT,

width = 13,borderwidth = 2)self.lbl2 = Label(self.lblframe,text="Sunken Label", relief = SUNKEN,

width = 13, borderwidth = 2)self.lbl3 = Label(self.lblframe,text="Ridge Label", relief = RIDGE, width = 13,

borderwidth = 2)self.lbl4 = Label(self.lblframe,text="Raised Label", relief = RAISED,

width = 13, borderwidth = 2)self.lbl5 = Label(self.lblframe,text="Groove Label", relief = GROOVE,

width = 13, borderwidth = 2)return frame

Page 35: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 52 9 indice ^

finire, usiamo l’attributo “sticky” perfar espandere il frame a destra esinistra (“WE”, dalle iniziali degliequivalenti in inglese dei nostri ovested est). Ora arriva la parte in cuivioleremo la regola di cui vi dicevoprima. Stiamo per posizionare unaetichetta come primowidget dellacornice, ma non lo abbiamo ancoradefinito. Lo facciamo ora. Impostiamocome genitore lblframe, come per lealtre etichette. Il testo a “Labels |”, lalarghezza a 15 e l’ancoraggio a destra(‘e’). Se ricordate, la volta passatausando l’attributo anchor abbiamoindicato dove deve essere posizionatoil testo nel widget. In questo caso èlungo il lato destro. Ora la partedivertente. Definiamo la posizionenella griglia, e ogni altro attributonecessario, semplicementeaggiungendo “.grid” in coda delladefinizione dell’etichetta.

Continuiamo allineando tutte leetichette nella griglia, iniziando dallacolonna 1, riga 0.

Ecco la funzione DefineVars.Notate che per il momento stiamosemplicemente usando l’istruzionepass. La sostituiremo dopo, visto cheora non ci serve:

def DefineVars(self):# Define our resources

pass

E per finire il codice dellafunzionemain:

root = Tk()root.geometry('750x40+150+150')root.title("Widget Demo1")demo = Demo(root)root.mainloop()

Prima instanziamo Tk.Quindi impostiamo ladimensione della finestraprincipale a 750 x 40 pixel e laposizioniamo a 150 pixel dal marginesuperiore e sinistro dello schermo.Quindi il titolo e instanziamol’oggetto Demo e, per finire,chiamiamomainloop di Tk.

Provatela. Dovreste vedere le 5etichette più l’etichetta aggiunta allafine, in tutta la loro gloria.

Pulsanti

Ora salvate il tutto comewidgetdemo1a.py e aggiungiamoqualche pulsante. Poiché ilprogramma base a cui liaggiungeremo è già pronto,aggiungeremo solo le parti mancanti.Iniziamo con la funzioneBuildWidgets. Dopo la definizione

delle etichette, e prima della riga“return frame”, aggiungiamo quantomostrato nella pagina seguente, inalto a destra.

Nulla di nuovo fin qui. Abbiamodefinito i pulsanti, con i loro attributi,e settato le funzioni di supporto(callback) attraverso .bind. Notatel’uso di lambda per l’invio dei valori da1 a 5 in base al pulsante premuto.Questo permetterà di sapere, nella

funzione di callback, quale pulsantestiamo trattando. Passiamo allafunzione PlaceWidgets. Inserite ilcodice sotto dopo aver posizionatol’ultima etichetta.

Ancora una volta, nessuna novità,quindi continuiamo. In basso a destrac’è la funzione callback. Inseriteladopo la routine DefineVars.

Ancora niente di davvero

HOWTO - PROGRAMMARE IN PYTHON - PARTE 26

def PlaceWidgets(self, master):frame = master# Place the widgetsframe.grid(column = 0, row = 0)# Place the labelsself.lblframe.grid(column = 0, row = 1, padx = 5, pady = 5,

columnspan = 5,sticky='WE')l = Label(self.lblframe,text='Labels |',width=15,

anchor='e').grid(column=0,row=0)self.lbl1.grid(column = 1, row = 0, padx = 3, pady = 5)self.lbl2.grid(column = 2, row = 0, padx = 3, pady = 5)self.lbl3.grid(column = 3, row = 0, padx = 3, pady = 5)self.lbl4.grid(column = 4, row = 0, padx = 3, pady = 5)self.lbl5.grid(column = 5, row = 0, padx = 3, pady = 5)

# Place the buttonsself.btnframe.grid(column=0, row = 2, padx = 5,

pady = 5, columnspan = 5,sticky = 'WE')l = Label(self.btnframe,text='Buttons |',width=15,

anchor='e').grid(column=0,row=0)self.btn1.grid(column = 1, row = 0, padx = 3, pady = 3)self.btn2.grid(column = 2, row = 0, padx = 3, pady = 3)self.btn3.grid(column = 3, row = 0, padx = 3, pady = 3)self.btn4.grid(column = 4, row = 0, padx = 3, pady = 3)self.btn5.grid(column = 5, row = 0, padx = 3, pady = 3)

Page 36: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 52 10 indice ^

divertente. Usiamo solo una serie diistruzioni IF/ELIF per stampare qualepulsante è stato premuto. La cosaprincipale da osservare, eseguendo ilprogramma, è che il pulsante conl’aspetto incavato non si muovequando facciamo clic. Generalmentenon si dovrebbe usare questo effettoameno che non si crei un pulsanteche resti “schiacciato” quandopremuto. Per finire, dobbiamoaggiustare la geometria persupportare il widget extra inserito:

root.geometry('750x110+150+150')

OK. Questo è pronto. Salvate edeseguitelo.

Salvatelo comewidgetdemo1b.pye passiamo ai pulsanti a sceltamultipla.

Pulsanti a sceltamultipla(checkbox)

Come detto prima, questa partedel programma contiene due pulsantinormali e due pulsanti a sceltamultipla. Il primo checkbox hal’aspetto che ci aspetteremmo. Ilsecondo somiglia più a un pulsante“appiccicoso”: quando non èselezionato, sembra un pulsante

normale, quando lo siseleziona, ha l’aspettodi un pulsantecostantementepremuto. Questafunzione si abilitaimpostando l’attributoindicatoron su falso.Ogni pressione delpulsante “normale”cambierà lo stato deicheckbox daselezionato a nonselezionato e viceversa. Otteniamoquesto chiamando ilmetodo .toggle delcheckbox. Colleghiamo il rilascio delpulsante sinistro delmouse allafunzione che invia, in questo caso, unmessaggio al terminale. In aggiunta atutto questo, configuriamo duevariabili (una per ciascun checkbox)che possiamo interrogare inqualunquemomento. In questo caso,ogniqualvolta si fa clic su un checkboxsi controlla questo valore e lo sistampa. Si faccia attenzione allaporzione di codice relativo allavariabile. Sarà usata per più widget.

Nella funzione BuildWidget, dopoil codice del pulsante appena inseritoe prima dell’istruzione return, inseriteil codicemostrato nella pagina

seguente, in alto a destra.

Ancora, tutto questo lo avete giàvisto. Abbiamo creato la cornice percontenere i controlli. Abbiamoimpostato un pulsante e duecheckbox. Posizioniamoli ora con ilcodice della prossima pagina, al

centro.

Ora definiamo le due variabili permonitorare il valore di ciascuncheckbox. In DefineVars, commentatel’istruzione pass, e aggiungete quantosegue...

HOWTO - PROGRAMMARE IN PYTHON - PARTE 26

# Buttonsself.btnframe = Frame(frame,relief = SUNKEN,padx = 3, pady = 3,

borderwidth = 2, width = 500)self.btn1 = Button(self.btnframe,text="Flat Button",

relief = FLAT, borderwidth = 2)self.btn2 = Button(self.btnframe,text="Sunken Button",

relief = SUNKEN, borderwidth = 2)self.btn3 = Button(self.btnframe,text="Ridge Button",

relief = RIDGE, borderwidth = 2)self.btn4 = Button(self.btnframe,text="Raised Button",

relief = RAISED, borderwidth = 2)self.btn5 = Button(self.btnframe,text="Groove Button",

relief = GROOVE, borderwidth = 2)self.btn1.bind('<ButtonRelease-1>',lambda e: self.BtnCallback(1))self.btn2.bind('<ButtonRelease-1>',lambda e: self.BtnCallback(2))self.btn3.bind('<ButtonRelease-1>',lambda e: self.BtnCallback(3))self.btn4.bind('<ButtonRelease-1>',lambda e: self.BtnCallback(4))self.btn5.bind('<ButtonRelease-1>',lambda e: self.BtnCallback(5))

def BtnCallback(self,val):if val == 1:

print("Flat Button Clicked...")elif val == 2:

print("Sunken Button Clicked...")elif val == 3:

print("Ridge Button Clicked...")elif val == 4:

print("Raised Button Clicked...")elif val == 5:

print("Groove Button Clicked...")

Page 37: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 52 11 indice ^

HOWTO - PROGRAMMARE IN PYTHON - PARTE 26self.Chk1Val = IntVar()self.Chk2Val = IntVar()

Dopo la funzione di callback delpulsante, inserite il testomostrato inbasso a destra.

E, per finire, sostituite l’istruzionegeometry con questa:

root.geometry('750x170+150+150')

Salvate ed eseguite. Salvate comewidgetdemo1c.py e passiamo aipulsanti a scelta singola.

Pulsanti a scelta singola(radiobutton)

Se siete vecchi abbastanza daricordare le autoradio con i pulsantiper selezionare le stazionimemorizzate, allora potete capireperché sono chiamati Radiobutton.Quando si usano i pulsanti a sceltasingola l’attributo variable èmoltoimportante. È ciò che tiene insiemequesti pulsanti. Nel programma, ilprimo gruppo di pulsanti èraggruppato permezzo della variabilechiamata self.RBVal. Il secondo èraggruppato attraverso la variabileRBVal2. Dobbiamo anche impostare ilvalore dell’attributo in fase didefinizione. Questo assicura che i

pulsanti restituiranno unvalore valido quandopremuti.

Tornando a BuildWidgetse, giusto primal’istruzione return,aggiungete il codicemostrato nella paginaseguente.

Una osservazione. Notatela definizione delleetichette nella funzionePlaceWidget. Questelunghe righe sonospezzettate permostrarecome l’uso delleparentesi ci permetta diben formattare righelunghe, con il codice cheancora funziona.

# Check Boxesself.cbframe = Frame(frame, relief = SUNKEN, padx = 3, pady = 3,

borderwidth = 2, width = 500)self.chk1 = Checkbutton(self.cbframe, text = "Normal Checkbox",

variable=self.Chk1Val)self.chk2 = Checkbutton(self.cbframe, text = "Checkbox",

variable=self.Chk2Val,indicatoron = False)self.chk1.bind('<ButtonRelease-1>',lambda e: self.ChkBoxClick(1))self.chk2.bind('<ButtonRelease-1>',lambda e: self.ChkBoxClick(2))self.btnToggleCB = Button(self.cbframe,text="Toggle Cbs")self.btnToggleCB.bind('<ButtonRelease-1>',self.btnToggle)

# Place the Checkboxes and toggle buttonself.cbframe.grid(column = 0, row = 3, padx = 5, pady = 5,

columnspan = 5,sticky = 'WE')l = Label(self.cbframe,text='Check Boxes |',width=15,

anchor='e').grid(column=0,row=0)self.btnToggleCB.grid(column = 1, row = 0, padx = 3, pady = 3)self.chk1.grid(column = 2, row = 0, padx = 3, pady = 3)self.chk2.grid(column = 3, row = 0, padx = 3, pady = 3)

def btnToggle(self,p1):self.chk1.toggle()self.chk2.toggle()print("Check box 1 value is {0}".format(self.Chk1Val.get()))print("Check box 2 value is {0}".format(self.Chk2Val.get()))

def ChkBoxClick(self,val):if val == 1:

print("Check box 1 value is {0}".format(self.Chk1Val.get()))elif val == 2:

print("Check box 2 value is {0}".format(self.Chk2Val.get()))

Page 38: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 52 12 indice ^

HOWTO - PROGRAMMARE IN PYTHON - PARTE 26In DefineVars aggiungete:

self.RBVal = IntVar()

Aggiungete le funzioni per il clic:

def RBClick(self):

print("Radio Button clicked- Value is{0}".format(self.RBVal.get()))

def RBClick2(self):

print("Radio Button clicked- Value is{0}".format(self.RBVal2.get()))

e, per finire, rielaborate l’istruzionegeometry come segue.

root.geometry('750x220+150+150')

Salvate il progetto comewidgetdemo1d.py ed eseguitelo. Ora

# Radio Buttonsself.rbframe = Frame(frame, relief = SUNKEN, padx = 3, pady = 3, borderwidth = 2, width = 500)self.rb1 = Radiobutton(self.rbframe, text = "Radio 1", variable = self.RBVal, value = 1)self.rb2 = Radiobutton(self.rbframe, text = "Radio 2", variable = self.RBVal, value = 2)self.rb3 = Radiobutton(self.rbframe, text = "Radio 3", variable = self.RBVal, value = 3)self.rb1.bind('<ButtonRelease-1>',lambda e: self.RBClick())self.rb2.bind('<ButtonRelease-1>',lambda e: self.RBClick())self.rb3.bind('<ButtonRelease-1>',lambda e: self.RBClick())self.rb4 = Radiobutton(self.rbframe, text = "Radio 4", variable = self.RBVal2, value = "1-1")self.rb5 = Radiobutton(self.rbframe, text = "Radio 5", variable = self.RBVal2, value = "1-2")self.rb6 = Radiobutton(self.rbframe, text = "Radio 6", variable = self.RBVal2, value = "1-3")self.rb4.bind('<ButtonRelease-1>',lambda e: self.RBClick2())self.rb5.bind('<ButtonRelease-1>',lambda e: self.RBClick2())self.rb6.bind('<ButtonRelease-1>',lambda e: self.RBClick2())

In PlaceWidgets aggiungete questo

# Place the Radio Buttons and select the first oneself.rbframe.grid(column = 0, row = 4, padx = 5, pady = 5, columnspan = 5,sticky = 'WE')l = Label(self.rbframe,

text='Radio Buttons |',width=15,anchor='e').grid(column=0,row=0)

self.rb1.grid(column = 2, row = 0, padx = 3, pady = 3, sticky = 'EW')self.rb2.grid(column = 3, row = 0, padx = 3, pady = 3, sticky = 'WE')self.rb3.grid(column = 4, row = 0, padx = 3, pady = 3, sticky = 'WE')self.RBVal.set("1")l = Label(self.rbframe,text='| Another Set |',

width = 15,anchor = 'e').grid(column = 5, row = 0)

self.rb4.grid(column = 6, row = 0)self.rb5.grid(column = 7, row = 0)self.rb6.grid(column = 8, row = 0)self.RBVal2.set("1-1")

Page 39: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 52 13 indice ^

HOWTO - PROGRAMMARE IN PYTHON - PARTE 26inizieremo a lavorare sui campi ditesto standard (owidgetd’inserimento).

Campi di testo (textbox)

Ancora, li abbiamo usati in varieGUI precedentemente. Comunquequesta volta, come detto prima, vimostrerò come prevenire,disabilitandole, la loromodifica daparte dell’utente. È utile quando sivoglia solomostrare qualcosa epermetterne cambiamenti solo nellamodalità “modifica”. A questo puntodovreste essere abbastanza sicuri chela prima cosa da fare è aggiungere ilcodice a destra alla funzioneBuildWidget.

Casella di riepilogo(listbox)

Ora dobbiamo occuparci dellacasella di riepilogo. In BuildWidgets,aggiungete il codice dalla prossimapagina, a destra.

Come al solito, creiamo unacornice. Quindi la barra di scorrimentoverticale. Lo facciamo prima di crearela listbox perché dobbiamoreferenziare il metodo ‘.set’ della

barra di scorrimento.Notate l’attributo'height = 5'. Questoforza la casella amostrare 5 elementialla volta.Nell’istruzione .bindusiamo'<<ListboxSelect>>'come evento. Èchiamato eventovirtuale, poiché non èun evento “ufficiale”.

Ora ci occuperemodel codice aggiuntivoper la funzionePlaceWidgets,mostrato nella paginaseguente, a sinistra.

Messaggi

Questa sezione èsemplicemente unaserie di normalissimipulsanti chechiameranno vari tipi dimessaggi. Li abbiamovisti precedentementeusando altri strumentiper creare GUI. Neconsidereremo solo 5tipi, ma ce ne sono dipiù. In questa sezione ci

# Textboxesself.tbframe = Frame(frame, relief = SUNKEN, padx = 3, pady =

3, borderwidth = 2, width = 500)self.txt1 = Entry(self.tbframe, width = 10)self.txt2 = Entry(self.tbframe, disabledbackground="#cccccc",

width = 10)self.btnDisable = Button(self.tbframe, text =

"Enable/Disable")self.btnDisable.bind('<ButtonRelease-1>',

self.btnDisableClick)

A seguire, aggiungete questo codice nella funzione PlaceWidget:# Place the Textboxesself.tbframe.grid(column = 0, row = 5, padx = 5, pady = 5,

columnspan = 5,sticky = 'WE')l = Label(self.tbframe,text='Textboxes |',width=15,

anchor='e').grid(column=0,row=0)self.txt1.grid(column = 2, row = 0, padx = 3, pady = 3)self.txt2.grid(column = 3, row = 0, padx = 3, pady = 3)self.btnDisable.grid(column = 1, row = 0, padx = 3, pady = 3)

Aggiungete questa riga alla fine della funzione DefineVars:self.Disabled = False

Ora aggiungete la funzione che risponde all’evento clic del pulsante:def btnDisableClick(self,p1):

if self.Disabled == False:self.Disabled = Trueself.txt2.configure(state='disabled')

else:self.Disabled = Falseself.txt2.configure(state='normal')

E per finire, rielaborate l’istruzione geometry:root.geometry('750x270+150+150')

Salvate come widgetdemo1d.py ed eseguitelo.

Page 40: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 52 14 indice ^

HOWTO - PROGRAMMARE IN PYTHON - PARTE 26

# List Box Stuffself.lstframe = Frame(frame,

relief = SUNKEN,padx = 3,pady = 3,borderwidth = 2,width = 500

)# Scrollbar for list boxself.VScroll = Scrollbar(self.lstframe)self.lbox = Listbox(self.lstframe,

height = 5,yscrollcommand = self.VScroll.set)

# default height is 10

self.lbox.bind('<<ListboxSelect>>',self.LBoxSelect)self.VScroll.config(command =

self.lbox.yview)self.btnClearLBox = Button(

self.lstframe,text = "Clear List",command = self.ClearList,width = 11

)self.btnFillLBox = Button(

self.lstframe,text = "Fill List",command = self.FillList,width = 11

)# <<ListboxSelect>> is virtual event# Fill the list boxself.FillList()

# Place the Listbox and support buttonsself.lstframe.grid(column = 0, row = 6, padx = 5,

pady = 5, columnspan = 5,sticky = 'WE')l = Label(self.lstframe,text='List Box |',width=15,

anchor='e').grid(column=0,row=0,rowspan=2)self.lbox.grid(column = 2, row = 0,rowspan=2)self.VScroll.grid(column = 3, row = 0,rowspan = 2,

sticky = 'NSW')self.btnClearLBox.grid(column = 1, row = 0, padx =

5)self.btnFillLBox.grid(column = 1, row = 1, padx =

5)

In DefineVars aggiungete questo...# List for List box itemsself.examples = ['Item One','Item Two','Item

Three','Item Four']

E aggiungete le seguenti funzioni di supporto:def ClearList(self):

self.lbox.delete(0,END)

def FillList(self):# Note, clear the listbox first...no check is donefor ex in self.examples:

self.lbox.insert(END,ex)# insert([0,ACTIVE,END],item)

def LBoxSelect(self,p1):print("Listbox Item clicked")items = self.lbox.curselection()selitem = items[0]print("Index of selected item =

{0}".format(selitem))print("Text of selected item =

{0}".format(self.lbox.get(selitem)))

Per finire, aggiornate la riga geometry.root.geometry('750x370+150+150')

Salvate come widgetdemo1e.py ed eseguitelo. Ora faremo l’ultimamodifica alla nostra applicazione.

Page 41: dl.fullcirclemagazine.orgdl.fullcirclemagazine.org/issuePY04_it.pdf · Adult Contemporary/Chris Rea/Collection/07 - Fool (If You Think It's Over).mp3 Adult Contemporary/Chris Rea/Collection/11

full circle magazine n. 52 15 indice ^

HOWTO - PROGRAMMARE IN PYTHON - PARTE 26occuperemo deimessaggiInformazione, Avviso, Errore,Domanda e Si/No. Sonomolto utiliquando si voglia trasmettereun’informazione all’utente inmanierapalese. Nella funzione BuildWidgetsaggiungete il codicemostrato sotto.

Ecco la routine di supporto. Per iprimi tre (Informazione, Avviso edErrore) si chiama semplicemente‘tkMessageBox.showinfo’, oqualunque sia necessario, con dueparametri. Il primo è il titolo dellafinestra delmessaggio e il secondo èil messaggio stesso damostrare.L’icona è gestita direttamente datkinter. Per i messaggi che richiedonouna risposta (domanda, si/no),bisogna fornire una variabile chericeve il valore corrispondente alpulsante selezionato. Nel caso delmessaggio con domanda, la rispostasarà “si” o “no”, nel caso delmessaggio si/no la risposta sarà“vero” o “falso”.

Per finire, modifichiamo la riga congeometry:root.geometry('750x490+550+150')

Salvate comewidgetdemo1f.py egiocateci.

Ho inserito il codice di

widgetdemo1f.py su pastebinall’indirizzohttp://pastebin.com/ZqrgHcdG.

# Buttons to show message boxes and dialogsself.mbframe = Frame(frame,relief = SUNKEN,padx = 3, pady = 3, borderwidth = 2)self.btnMBInfo = Button(self.mbframe,text = "Info")self.btnMBWarning = Button(self.mbframe,text = "Warning")self.btnMBError = Button(self.mbframe,text = "Error")self.btnMBQuestion = Button(self.mbframe,text = "Question")self.btnMBYesNo = Button(self.mbframe,text = "Yes/No")self.btnMBInfo.bind('<ButtonRelease-1>', lambda e: self.ShowMessageBox(1))self.btnMBWarning.bind('<ButtonRelease-1>', lambda e: self.ShowMessageBox(2))self.btnMBError.bind('<ButtonRelease-1>', lambda e: self.ShowMessageBox(3))self.btnMBQuestion.bind('<ButtonRelease-1>', lambda e: self.ShowMessageBox(4))self.btnMBYesNo.bind('<ButtonRelease-1>', lambda e: self.ShowMessageBox(5))

Ora aggiungete il codice per la funzione PlaceWidgets:

# Messagebox buttons and frameself.mbframe.grid(column = 0,row = 7, columnspan = 5, padx = 5, sticky = 'WE')l = Label(self.mbframe,text='Message Boxes |',width=15, anchor='e').grid(column=0,row=0)self.btnMBInfo.grid(column = 1, row = 0, padx= 3)self.btnMBWarning.grid(column = 2, row = 0, padx= 3)self.btnMBError.grid(column = 3, row = 0, padx= 3)self.btnMBQuestion.grid(column = 4, row = 0, padx= 3)self.btnMBYesNo.grid(column = 5, row = 0, padx= 3)

def ShowMessageBox(self,which):if which == 1:

tkMessageBox.showinfo('Demo','This is an INFO messagebox')elif which == 2:

tkMessageBox.showwarning('Demo','This is a WARNING messagebox')elif which == 3:

tkMessageBox.showerror('Demo','This is an ERROR messagebox')elif which == 4:

resp = tkMessageBox.askquestion('Demo','This is a QUESTION messagebox?')print('{0} was pressed...'.format(resp))

elif which == 5:resp = tkMessageBox.askyesno('Demo','This is a YES/NO messagebox')print('{0} was pressed...'.format(resp))