In questo ultima parte illustrerò come sono stati utilizzati i vari widgets per realizzare l’editor e l’emulatore. Come vedrete, una volta capito il funzionamento della libreria, sarà molto semplice implemtentare soluzioni per le vostre necessità. Spero che gli esempi siano abbastanza chiari, così da potervi permettere di sperimentare autonomamente la creazione di programmi multipiattaforma con tkinter.
Per prima cosa vediamo il codice di Editor:
def __init__(self,master, calcolatore): """ Inizializza i frame della finestra dell'Editor """ self.master = master self.CD = calcolatore ## Codice Assembly self.codice = LabelFrame(self.master, text = 'Codice Assembly', relief = RIDGE, borderwidth = 5, labelanchor = 'n', pady = 5) self.codice.rowconfigure(0, weight=1) self.codice.columnconfigure(0, weight=1) self.codice.grid(row = 1, column = 0,rowspan = 3, columnspan = 5, sticky = W+E+N+S) self.menubar = Menu(self.master) self.create_widgets(self.menubar) def create_widgets(self,menubar): """ Crea il layout del programma, finestra dell'Editor """ ### Menu self.filemenu = Menu(menubar,tearoff = 0) self.filemenu.add_command(label = 'Apri', command = self.aprifile) self.filemenu.add_command(label = 'Salva', command = self.salvafile) self.filemenu.add_command(label = 'Cancella', command = self.cancella) self.filemenu.add_separator() self.filemenu.add_command(label = 'Esci', command = self.exit) menubar.add_cascade(label = 'Opzioni', menu = self.filemenu) self.master.config(menu = self.menubar) self.helpmenu = Menu(menubar,tearoff = 0) self.helpmenu.add_command(label = 'Informazioni', command = self.infor) self.helpmenu.add_command(label = 'Legenda', command = self.leg) self.helpmenu.add_command(label = 'Guida', command = self.guida) menubar.add_cascade(label = 'Aiuto', menu = self.helpmenu) ## Codice Assembly self.Inserisci = Text(self.codice, width = 50,height = 30,wrap = WORD) self.Inserisciscrollbar = Scrollbar(self.codice) self.Inserisciscrollbar.config(command = self.Inserisci.yview) self.Inserisci.config(yscrollcommand = self.Inserisciscrollbar.set) self.Inserisciscrollbar.grid(row = 0, column = 1, sticky = N+S) self.Inserisci.grid(row = 0, column = 0, sticky = W)
Come potete vedere dal costruttore, oltre alla finestra principale viene passato anche calcolatore (oggetto creato con la classe pdp8 dei precendeit articoli). Questa finestra non è molto complessa, infatti è composta solo dal LabelFrame codice e dal menu in alto “menubar”. Non dimenticate che ogni frame ha la sua griglia (grid).
In create_widgets() possiamo vedere più dettagliatamente come menubar venga arricchito delle varie voci previste dal programma e di come si inserisce un oggetto Text al frame codice.
C’è da notare che all’oggetto Text abbiamo subito associato la barra di scorrimento laterale e che ogni opzione del menù è stata associata ad un comando specifico, ovvero una funzione interna alla classe che ha determinati compiti (non posto il codice delle varie funzioni per non rendere troppo dispersivo l’articolo, si possono comunque cosultare i sorgenti online, visto che ho aggiornato il repository con mercurial).
La finestra dell’emulatore è un pò più complessa, come potete notare dal costruttore:
def __init__(self,master,codice,calcolatore,emulatore): """ Inizializza i frame per l'interfaccia dell'emulatore """ self.CD = calcolatore self.codice = codice self.delay = 100 self.master = Frame(master) self.root = emulatore ## Memoria Ram self.ram = LabelFrame(self.master, text = 'Memoria RAM', relief = RIDGE, borderwidth = 5, labelanchor = 'n', pady = 5) self.ram.rowconfigure(0, weight=1) self.ram.columnconfigure(0, weight=1) self.ram.grid(row = 0, column = 0,rowspan = 3, columnspan = 5, sticky = W+E+N+S) ## Controlli self.controlli = Frame(self.master, padx = 10, pady = 10) self.controlli.grid(row = 0, column = 5,rowspan = 1) ## Status CD self.registri = LabelFrame(self.master, text = 'REGISTRI', relief = RIDGE, borderwidth = 5, labelanchor = 'n',padx = 25,pady = 10) self.registri.grid(row = 0, column = 6,rowspan = 1, sticky = W+E+N+S) self.unita = LabelFrame(self.master, text = 'UC', relief = RIDGE, borderwidth = 5, labelanchor = 'n', padx = 10, pady = 10) self.unita.grid(row = 2, column = 6, rowspan = 1, sticky = N) ## Var self.variabili = Frame(self.master) self.variabili.grid(row = 2, column = 5) self.nstep = LabelFrame(self.variabili, text = 'Num. Step', relief = RIDGE, borderwidth = 5, labelanchor = 'n') self.nstep.grid(row = 0, column = 5,sticky = W+E) self.delays = LabelFrame(self.variabili, text = 'Delay', relief = RIDGE, borderwidth = 5, labelanchor = 'n') self.delays.grid(row = 1, column = 5,sticky = W+E) self.tempo = LabelFrame(self.variabili, text = 'Tempo', relief = RIDGE, borderwidth = 5, labelanchor = 'n') self.tempo.grid(row = 1, column = 6,sticky = W+E) ### Unita' di controllo self.unitas = LabelFrame(self.unita, text = 'S', labelanchor = 's', padx = 10) self.unitas.grid(row = 0, column = 0, sticky = N) self.unitaf = LabelFrame(self.unita, text = 'F', labelanchor = 's', padx = 10) self.unitaf.grid(row = 0, column = 1, sticky = N) self.unitar = LabelFrame(self.unita, text = 'R', labelanchor = 's', padx = 10) self.unitar.grid(row = 0, column = 2, sticky = N) self.unitaint = LabelFrame(self.unita, text = 'Int.', labelanchor = 's', padx = 10) self.unitaint.grid(row = 0, column = 3, sticky = N) ### Registri self.programc = LabelFrame(self.registri, text = 'PC',relief = FLAT, labelanchor = 'e', padx = 5) self.programc.grid(row = 0, column = 0, sticky = W+E) self.mar = LabelFrame(self.registri, text = 'MAR',relief = FLAT, labelanchor = 'e', padx = 5) self.mar.grid(row = 1, column = 0, sticky = W+E) self.mbr = LabelFrame(self.registri, text = 'MBR',relief = FLAT, labelanchor = 'e', padx = 5) self.mbr.grid(row = 2, column = 0, sticky = W+E) self.lac = LabelFrame(self.registri, text = 'AC',relief = FLAT, labelanchor = 'e', padx = 5) self.lac.grid(row = 3, column = 0, sticky = W+E) self.vare = LabelFrame(self.registri, text = 'E',relief = FLAT, labelanchor = 'e', padx = 5) self.vare.grid(row = 4, column = 0, sticky = W+E) self.lopr = LabelFrame(self.registri, text = 'OPR',relief = FLAT, labelanchor = 'e', padx = 5) self.lopr.grid(row = 5, column = 0, sticky = W+E) self.vari = LabelFrame(self.registri, text = 'I',relief = FLAT, labelanchor = 'e', padx = 5) self.vari.grid(row = 6, column = 0, sticky = W+E) ## Microistruzioni self.micro = LabelFrame(self.master, text = 'Microistruzioni eseguite', relief = RIDGE, borderwidth = 5, labelanchor = 'n', pady = 5) self.micro.rowconfigure(0, weight=1) self.micro.columnconfigure(0, weight=1) self.micro.grid(row = 3, column = 4,rowspan = 5, columnspan = 5, sticky = W+E+N+S) ## Inout self.inout = LabelFrame(self.master, text = 'Input & Output', relief = RIDGE, borderwidth = 5, labelanchor = 'n', pady = 5) self.inout.rowconfigure(0, weight=1) self.inout.columnconfigure(0, weight=1) self.inout.grid(row = 3, column = 0, columnspan = 4, sticky = W+E+N+S) self.create_widgets() def create_widgets(self): """ Crea il layout del programma, finestra dell'emulatore """ ## Memoria RAM self.Visualizza = Text(self.ram, width = 80) self.Visualizzascrollbar = Scrollbar(self.ram) self.Visualizzascrollbar.config(command = self.Visualizza.yview) self.Visualizza.config(yscrollcommand = self.Visualizzascrollbar.set) self.Visualizzascrollbar.grid(row = 0, column = 1, sticky = N+S) self.Visualizza.grid(row = 0, column = 0, sticky = W) ## INOUT self.Visualizzainout = Text(self.inout, width = 62, height = 7, fg = 'green', bg = 'black') self.Visualizzascrollbar_inout = Scrollbar(self.inout) self.Visualizzascrollbar_inout.config(command = self.Visualizzainout.yview) self.Visualizzainout.config(yscrollcommand = self.Visualizzascrollbar_inout.set) self.Visualizzascrollbar_inout.grid(row = 0, column = 1, sticky = N+S) self.Visualizzainout.grid(row = 0, column = 0, sticky = W) ## Mircroistruzioni self.Visualizzamicro = Text(self.micro, width = 55, height = 7) self.Visualizzascrollbar_m = Scrollbar(self.micro) self.Visualizzascrollbar_m.config(command = self.Visualizzamicro.yview) self.Visualizzamicro.config(yscrollcommand = self.Visualizzascrollbar_m.set) self.Visualizzascrollbar_m.grid(row = 0, column = 1, sticky = N+S) self.Visualizzamicro.grid(row = 0, column = 0, sticky = W) ### Pulsanti self.butload = Button(self.controlli, text = 'LOAD', anchor = CENTER, width = 15, command = self.loading, bg = 'SkyBlue') self.butload.grid(row = 0, column = 0) self.butstep = Button(self.controlli, text = 'Step', anchor = CENTER, width = 15, command = self.step, bg = 'linen') self.butstep.grid(row = 1, column = 0) self.butminstep = Button(self.controlli, text = 'miniStep', anchor = CENTER, width = 15, command = self.mini_step, bg = 'linen') self.butminstep.grid(row = 2, column = 0) self.butstep = Button(self.controlli, text = 'microStep', anchor = CENTER, width = 15, command = self.micro_step, bg = 'linen') self.butstep.grid(row = 3, column = 0) self.butsetstep = Button(self.controlli, text = 'Set n Step', anchor = CENTER, width = 15, command = self.setnstep, bg = 'linen') self.butsetstep.grid(row = 4, column = 0) self.butsetdelay = Button(self.controlli, text = 'Set Delay', anchor = CENTER, width = 15, command = self.setdelay, bg = 'linen') self.butsetdelay.grid(row = 5, column = 0) self.butstart = Button(self.controlli, text = 'START', anchor = CENTER, width = 15, command = self.start, bg = 'DarkOliveGreen3') self.butstart.grid(row = 6, column = 0) self.butreset = Button(self.controlli, text = 'RESET', anchor = CENTER, width = 15, command = self.resetCD, bg = 'Orange3') self.butreset.grid(row = 7, column = 0) self.butstop = Button(self.controlli, text = 'STOP', anchor = CENTER, width = 15, command = self.stop, bg = 'IndianRed') self.butstop.grid(row = 8, column = 0) self.butbreak = Button(self.controlli, text = 'BREAK', anchor = CENTER, width = 15, command = self.breakpoint, bg = 'Magenta2') self.butbreak.grid(row = 9, column = 0) self.butcontinue = Button(self.controlli, text = 'CONTINUA', anchor = CENTER, width = 15, command = self.continua, bg = 'Magenta2') self.butcontinue.grid(row = 10, column = 0) self.butesegui = Button(self.controlli, text = 'ESEGUI', anchor = CENTER, width = 15, command = self.esegui, bg = 'Yellow') self.butesegui.grid(row = 11, column = 0) ### Labels self.labelprogramc = Label(self.programc, text = '00000000000', relief = SUNKEN, bg = 'red') self.labelprogramc.grid() self.labelmar = Label(self.mar, text = '00000000000', relief = SUNKEN, bg = 'yellow') self.labelmar.grid() self.labelmbr = Label(self.mbr, text = '000000000000000', relief = SUNKEN) self.labelmbr.grid() self.labelac = Label(self.lac, text = '000000000000000', relief = SUNKEN) self.labelac.grid() self.labelvari = Label(self.vari, text = '0', relief = SUNKEN) self.labelvari.grid() self.labelvare = Label(self.vare, text = '0', relief = SUNKEN) self.labelvare.grid() self.labelopr = Label(self.lopr, text = '000', relief = SUNKEN) self.labelopr.grid() self.labelucs = Label(self.unitas, text = '0') self.labelucs.grid() self.labelucf = Label(self.unitaf, text = '0') self.labelucf.grid() self.labelucr = Label(self.unitar, text = '0') self.labelucr.grid() self.labelucint = Label(self.unitaint, text = '0') self.labelucint.grid() self.labelnstep = Label(self.nstep, text = '1') self.labelnstep.grid() self.labeldelay = Label(self.delays, text = str(self.delay)) self.labeldelay.grid() self.labeltempo = Label(self.tempo, text = str(self.CD.tempo)) self.labeltempo.grid()
Apparentemente sembra difficile da interpretare, ma questo schemino riassuntivo dei frame principali dovrebbe rendere meglio l’idea della sua implementazione.
Una volta localizzati i frame principali è quasi banale capire l’inserimento dei vari widgets. Molti sotto-frame sono stati inseriti solo per raggiungere un buon compromesso visivo, ma sostanzialmente se ne poteva anche fare a meno.
Da notare che ci siamo portati dietro l’oggetto codice della precedente finestra, così da poter estrapolare il testo al suo interno e darlo in pasto all’Assembler del pdp8. L’oggetto pdp8 è lo stesso creato nel main, quindi ne esiste uno per tutta la sessione del programma ed è raggiungibile sia da Editor (la finestra principale) che da Emulatore (finestra toplevel, secondaria ma di uguale importanza).
Come prima, non metterò il codice di tutte le funzioni presenti perché sono consultabili online; piuttosto vorrei parlarvi della risoluzione di un problema affrontato con la scrittura dell’interfaccia.
Se avete già provato il programma avrete notato che facendo partire l’emulazione è possibile premere altri pulsati ed interagire con l’interfaccia (come in qualsiasi altro programma) mentre è in esecuzione il codice. Questo risultato è stato raggiunto grazie ad un metodo basilare dei widgets per “alarm handlers and other non-event callbacks”, ovverro per gestire eventi che non sono semplicemente associati ad un oggetto.
In questo modo ho potuto ricostruire l’esecuzione temporale del processore secondo un certo delay (che è possobile settare sempre tramite interfaccia grafica) che non tocca minimamente l’implementazione interna del pdp8.
Nei sorgenti troverete per esempio “self.butesegui.after(self.delay, self.esegui)” in Emulatore nel metodo esegui: premuto il tasto ESEGUI (il quale è collegato al medesimo metodo), dopo un certo delay il widget richiamerà la funzione esegui della classe Emulatore, così da permettere la continua esecuzione del codice.
Per il resto, gli unici widgets utilizzati sono Text e Button, mentre i LabelFrame vengono utilizzati per organizzare visivamente l’emulatore, visto che è possibile scrivere un testo di contorno al frame creato. Per evidenziare il testo si è utilizzato un particolare vincolo di Text, ovvero i “tag” che possono essere personalizzati dall’utente.
Conclusioni
Spero che i codici sorgeti disponibili e queste mini-guide esplicative del programma siano utili per la realizzazione di piccoli progetti. Tkinter è molto semplice da utilizzare e per questo non ho speso molto tempo nell’analisi riga per riga del codice visto poiché, a parte l’organizzazione dei frame, l’utilizzo dei widgets è banale e basta consultare bene i manuali online per capire il funzionamento dei vari metodi. C’è da tenere poi conto che 3/4 del tempo è stato speso per il testing, potete quindi immaginare quanto semplice sia il codice.
Con questo concludo la serie di articoli sull’emulatore del pdp8 in python, curioso di ascoltare le vostre critiche, opinioni, suggerimenti e dubbi. In futuro mi piacerebbe convertire l’applicazione in wxPython, visto che quest’ultima ha suscitato di recente il mio interesse.
bello, molto semplice come Toolkit :-)
Per fare una GUI al volo è perfetto. Chiaramente se serve qualcosa di più sofisticato mi butterei sempre e comunque sulle Qt :-)
@ Antonio
Se devi rimanere in python consiglierei l’utilizzo di wxPython (come ho già scritto, mi sta interessando molto), porting di wxWidgets. Magari è un pò più complessa di Qt, ma in python è tutto più semplice, come hai già potuto notare.
In C++ credo che Qt sia imbattibile come facilità di utilizzo e gli editor grafici funzionano molto bene. :D
Io non vedo l’ora di mettere le mani su Silverlight con (Iron)Python. 8-)
@Mirco: il framework Qt in C++ addirittura “amplia il linguaggio”, aggiungendo delle keyword che vengono processate da un preprocessor fatto ad hoc (il MOC) e che aggiunge al C++ la possibilità di programmare “ad eventi” in modo molto semplice :-)
il meccanismo signal/slot consente non solo di scrivere del codice molto pulito per GUI moderne e sofisticate, ma ti consente anche di semplificare la struttura stessa dell’applicazione, soprattutto utile per applicazioni basate sull’interazione tra vari moduli tra loro semi-indipendenti.
@ Antonio
Come sospettavo, c’era qualche motivo per il quale Qt in C++ è il migliore :D. Ora per colpa tua sono indeciso se provare Qt in c++ o Wxwidgets in python :P .
@ Cesare
Silverlight ammette l’utilizzo di python?? Forse è la volta buona che lo provo!
Beh in realtà il meccanismo signal/slot si può implementare anche alla vecchia maniera, tramite il pattern Publisher/Subscriber. La comodità di Qt è che tutta questa parte viene fatta da un preprocessore e quindi ti da l’impressione di essere parte integrante del linguaggio.
Ovviamente devi usare un IDE che sia Qt-aware, ad esempio Nokia Qt Creator, altrimenti non potrai usufruire degli strumenti adatti tipo l’autocomplete supplementare delle macro SIGNAL e SLOT :-)
Infatti credo che sia indispensabile l’utilizzo del Nokia Qt Creator, se non altro per tutte le agevolazioni che offre. Credo non ci siano strumenti equivalenti nelle altre librerie.
Perché non hai provato Silverlight: http://www.silverlight.net/ :D
Intanto ti lascio un link su un portale che raccoglie parecchio materiale su Silverlight + IronPython: http://www.voidspace.org.uk/ironpython/silverlight/index.shtml
Per cominciare ti puoi leggere l’introduzione.
Un altro link (ma dallo stesso sito) col quale puoi smanettare in tempo reale con IronPython & Silverlight: http://www.voidspace.org.uk/ironpython/webide/webide.html
La cosa spettacolare rimane Silverlight, col quale puoi realizzare applicazioni (anche giochi!) standalone, web, o per Windows Phone 7.
Il tutto in maniera molto comoda e produttiva. Tirare fuori l’interfaccia grafica è una goduria, persino lavorando direttamente con XAML (è il markup che si usa per definire la GUI).
In ogni caso c’è sempre Expression Blend per realizzarla tramite un ambiente RAD/IDE che è veramente impressionante: http://www.microsoft.com/expression/products/blend_overview.aspx
Sto usando Silverlight (+ .NET) per sviluppare su Windows Phone 7 e, nonostante debba usare C#, è una meraviglia. Immagina quant’è bello lavorare usando IronPython. ;)
Provalo, ma non per 5 minuti (facci un progettino, anche piccolo), e vedrai che non ne potrai fare a meno. :P
Ah, dimenticavo: non ci sono slot/signal. Ci sono i delegate se ti servono, ma in generale tramite i Dependency Object puoi avere delle classi che “pilotano” la tua interfaccia grafica cambiando proprietà ad oggetti, come pure stili degli oggetti, e persino animazioni (anche complesse); in base ad alcuni eventi, ovviamente.
E’ una cosa difficile da spiegare in poche righe, perché bisogna sperimentare per capirne bene le potenzialità, trattandosi di un modo diverso da come siamo abituati. Insomma, non è il classico sistema evento -> richiamo la callback col mio codice che esegue business logica modificando eventualmente le proprietà di uno o più oggetti.
Qui sei tu che “metti in comunicazione” gli oggetti e le proprietà, descrivendo cosa deve succedere. E lo può fare anche un grafico, eventualmente con un minimo supporto da parte di un programmatore.
Ad esempio per una banale classe che si occupa di “convertire” un booleano in due immagini; questa classe verrà usata dal grafico per dire: “quando il checkbox cambia valore, trasforma il suo stato booleano tramite questa classe in un’immagine da utilizzare come sfondo in quest’altro controllo”.
Comunque è tutto da sperimentare. Solo così si potrà apprezzare il salto di qualità offerto da questo strumento (che poi è un sottoinsieme di Windows Presentation Foundation aka WPF).
Beh, Microsoft ci sa fare con i devkit, non c’è che dire :-)
@ Cesare
Intanto ti ringrazio per i link. Volevo chiederti se ti sei interessato anche a moonlight, ovvero la rivisitazione open (da parte di novell) di silverligh per il mondo linux.
Visto che se ne occupa sempre il progetto mono (che dovrebbe essere al passo con le librerie .Net), volevo sapere se era possibile sviluppare un’applicazione crossplatform senza problemi.
A detta di Jacopo, è pure più veloce di Silverlight. Miguel de Icaza sembra abbia fatto un ottimo lavoro.
Non credo ci siano problemi a sviluppare cross-platform, limitandosi alla parte comune di .NET e Silverlight ovviamente.
Spero che Moonlight venga portato presto su MonoTouch/iPhone e poi su MonoDroid/Android: si potrebbero sviluppare applicazioni mobile cross-platform (incluso Windows Phone 7 ovviamente) MOLTO velocemente e comodamente. Il miracolo sarebbe poter usare IronPython anche su WP7 (attualmente su Mono gira e dovrebbe funzionare anche su MonoTouch/Droid).
@Antonio: già. SLURP! :)