Ri-eccomi sull’argomento, prima di cominciare voglio ringraziare l’autore del framework in questione, Roberto Cavada, per aver commentato l’articolo precedente.
Nella prima puntata di questo tutorial avevo promesso di mostrarvi una piccola applicazione di esempio, Todoman, utile per comprendere in modo più approfondito il funzionamento di Pygtkmvc.
L’applicazione gestisce delle liste ToDo e consente all’utente di inserire, modificare e eliminare i task a proprio piacimento.
Ma basta chiacchiere e partiamo subito alla scoperta delle numerose funzionalità offerte dal framework. Innanzitutto analizziamo il modello di dominio e partiamo con una delle classi principali (come al solito, i commenti sono embedded):
import gtkmvc, gtkmvc.model class ToDoListModel(gtkmvc.model.TreeStoreModel): """ Rappresenta la lista dei task. La classe estende un modello di vista ad albero, in modo da velocizzare la sua rappresentazione. """ # Questo dizionario contiene gli attributi # "observable", ogni modifica apportata # alle proprietà verrà notificata in modo # automatico. Nel corpo dei metodi della # classe si potrà accedere a tali proprietà # come a qualsiasi altra variabile di istanza. __properties__ = { 'name' : '', 'author' : '' } def __init__(self): """ Costruttore della classe. Richiama il costruttore della classe base, passandogli come parametri il tipo di ognuna delle quattro colonne del widget TreeStore. """ gtkmvc.model.TreeStoreModel.__init__(self, int, str, str, 'gboolean') self.__setup_properties() # inizializza i widget def __setup_properties(self): """ Inizializza i widget grafici. """ self.name = _("Unnamed todo list") self.author = _("Unknown author") def get_todo_by_path(self, path): """ T.get_todo_by_path(path) -> todo -- inizializza un nuovo task con le informazioni presenti nella vista ad albero """ ta = ToDoItemModel() ta.name = self[path][columns['NAME']] ta.priority = self[path][columns['PRIORITY']] ta.description = self[path][columns['DESCRIPTION']] ta.done = self[path][columns['DONE']] return ta def has_item(self, item_name): """ T.has_item(item_name) -> bool -- restituisce True se nella vista ad albero è presente una riga con la stesso valore nella colonna 'NAME', False altrimenti """ found = False for i in self: if i[columns['NAME']] == item_name: found = True break return found def append_todo(self, todo): """ T.append(todo) -- inserisce un oggetto nella lista """ self.append(None, [ todo.priority, todo.name, todo.description, todo.done]) def clear(self): """ T.clear() -- cancella il contenuto della lista """ gtkmvc.TreeStoreModel.clear(self) self.__setup_properties()
Quelli che seguono sono gli indici delle colonne che desideriamo mostrare nel widget GtkTreeView:
# Contiene gli indici delle colonne della vista ad albero. columns = { 'PRIORITY' : 0, 'NAME' : 1, 'DESCRIPTION' : 2, 'DONE' : 3 }
Una volta definita la lista, possiamo occuparci degli elementi che dovrà contenere:
class ToDoItemModel(gtkmvc.model.Model): """ Rappresenta i task. La classe contiene delle proprietà testuali, numeriche e booleane, perciò estende un semplice modello generico. """ __properties__ = {'name' : '', 'priority' : 0, 'description' : '', 'done' : False} def __init__(self): """ Costruttore della classe. """ gtkmvc.model.Model.__init__(self) self.__setup_properties() self.treestore = None # relazione 1-a-1 con la lista che lo contiene def __setup_properties(self): """ Inizializza i widget grafici. """ self.name = "Unnamed todo item" # nome di default self.description = "" self.done = False self.priority = 5 def set_treestore(self, treestore): """ T.set_treestore(treestore) -- imposta la vista ad albero nella quale è elencato. """ self.treestore = treestore
Infine, poichè desideriamo salvare su file le nostre liste todo, definiamo una versione serializzabile del tipo lista:
class ToDoListModelSerializable(ToDoListModel): """ Questa è la versione serializzabile della classe ToDoListModel, fornisce i metodi necessari per la scrittura e la lettura della configurazione su file. """ SECTION_TODOLIST_DETAILS = 'Todolist details' OPTION_TODOLIST_NAME = 'Name' OPTION_TODOLIST_AUTHOR = 'Author' OPTION_TODOACTION_DESCRIPTION = 'Description' OPTION_TODOACTION_PRIORITY = 'Priority' OPTION_TODOACTION_DONE = 'Done' def __init__(self): """ Costruttore della classe. """ ToDoListModel.__init__(self) self.filename = '' self.changed = False # oggetto ConfigParser necessario per le operazioni su file self.config_parser = ConfigParser() def clear(self): """ T.clear() -- cancella il contenuto della lista Richiama il metodo della classe base per cancellare il contenuto della lista, quindi reimposta il nome del file e il flag sui cambiamenti. """ ToDoListModel.clear(self) self.filename = '' self.changed = False def save_to_file(self): """ T.save_to_file() -> bool -- salva la lista su disco Memorizza il contenuto su file. """ try: config_file = open(self.filename, 'w') # apre in scrittura except IOError, e: raise errors.FileUnknownException if self.config_parser.has_section(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS) == False: self.config_parser.add_section(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS) self.config_parser.set(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS, ToDoListModelSerializable.OPTION_TODOLIST_NAME, self.name) self.config_parser.set(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS, ToDoListModelSerializable.OPTION_TODOLIST_AUTHOR, self.author) for row in self: # memorizza tutti i task todo_name = row[columns['NAME']] if self.config_parser.has_section(todo_name) == False: self.config_parser.add_section(todo_name) self.config_parser.set(todo_name, ToDoListModelSerializable.OPTION_TODOACTION_DESCRIPTION, row[columns['DESCRIPTION']]) self.config_parser.set(todo_name, ToDoListModelSerializable.OPTION_TODOACTION_PRIORITY, str(row[columns['PRIORITY']])) self.config_parser.set(todo_name, ToDoListModelSerializable.OPTION_TODOACTION_DONE, str(row[columns['DONE']])) self.config_parser.write(config_file) # save to file config_file.close() return True def read_from_file(self): """ T.read_from_file() -> bool -- legge la lista da file Recupera le informazioni sulla lista da file. """ config_file = self.filename self.config_parser.read([config_file]) sections = self.config_parser.sections() if sections == []: raise errors.FileNotFoundException self.clear() # clears the treestore self.name = self.config_parser.get(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS, ToDoListModelSerializable.OPTION_TODOLIST_NAME) self.author = self.config_parser.get(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS, ToDoListModelSerializable.OPTION_TODOLIST_AUTHOR) self.changed = False sections.remove(ToDoListModelSerializable.SECTION_TODOLIST_DETAILS) for s in sections: # crea gli oggetti dei task # Si, lo so, si tratta di eager loading, ma questa è solo un'applicazione di esempio! todo = ToDoItemModel() todo.name = s todo.description = self.config_parser.get(s, ToDoListModelSerializable.OPTION_TODOACTION_DESCRIPTION) todo.priority = self.config_parser.getint(s, ToDoListModelSerializable.OPTION_TODOACTION_PRIORITY) todo.done = self.config_parser.getboolean(s, ToDoListModelSerializable.OPTION_TODOACTION_DONE) self.append_todo(todo) self.filename = config_file return True
Non credo siano necessari ulteriori spiegazioni, il codice illustrato è molto semplice e ben commentato.
Una volta sistemato il modello possiamo creare la vista, ma prima di scrivere altro codice apriamo Glade e progettiamo una semplice interfaccia grafica. Il mio consiglio è di creare un file di progetto per ogni finestra.
Ecco degli screenshot, per prima vi mostro la finestra principale del programma:
Questo è il dialogo delle proprietà della lista todo:
All’interno della seguente finestra di dialogo sono presenti le proprietà dei singoli task:
Carino, no?
Per quanto riguarda il codice, la questione è molto semplice; apriamo un nuovo file view.py e definiamo al suo interno le classi destinate a contenere le proprietà delle tre finestre mostrate sopra:
import gtkmvc, gtkmvc.view class ToDoListView(gtkmvc.view.View): def __init__(self, ctrl): gtkmvc.view.View.__init__(self, ctrl, GLADE_MAIN_WINDOW) class ToDoListPropertiesView(gtkmvc.view.View): def __init__(self, ctrl): gtkmvc.view.View.__init__(self, ctrl, GLADE_DIALOG_PROPERTIES) class ToDoItemView(gtkmvc.view.View): def __init__(self, ctrl): gtkmvc.view.View.__init__(self, ctrl, GLADE_DIALOG_EDIT)
I commenti qui sono inutili, basta solo assegnare alle tre costanti globali (quelle in MAIUSCOLO) i percorsi dei tre file Glade.
Ora il modello è pronto, la vista anche, manca solo il controller, che implementeremo nella prossima puntata.
Pingback: Alessio Treglia » Wordpress e il planet di Ubuntu Italia