[Supporto] Tutorial PyGTK e Glade (2 parte)

Linguaggi di programmazione: php, perl, python, C, bash, ecc.

[Supporto] Tutorial PyGTK e Glade (2 parte)

Messaggioda Alkatron » lunedì 25 ottobre 2010, 18:48

Tradotto da : http://www.learningpython.com/2006/09/0 ... plication/

In questo tutorial ci accingiamo a estendere la nostra applicazione PyWine per consentirci di modificare i vini che sono stati aggiunti alla lista e salvare/caricare un elenco di vini in modo che non si debbano reinserire ogni volta.

È possibile scaricare il codice completo per questo tutorial qui.
Se non avete familiarità con l'applicazione PyWine o con PyGTK e Glade vi consiglio di leggere i  primi due tutorial sull'argomento:

    * Tutorial PyGTK e Glade 1 parte
    * Creating a GUI using PyGTK and Glade

La GUI – Glade

La prima cosa che faremo è aprire il progetto PyWine e aggiungeremo un pulsante Edit alla nostra barra degli strumenti:

  • Fare spazio per il pulsante Edit, selezionando toolbar e impostando la proprietà size a 2.
  • Aggiungere un toolbar button nel nuovo spazio creato vuoto.
  • Chiamare il pulsante btnEditWine, impostare la sua etichetta a "Edit Wine", e la sua icona a stock Edit. Successivamente aggiungere un handler per l'evento clicked.
  • Cambieremo il menu per  dare anche la possibilita di modificare. Questo si puo fare proprio come abbiamo gia fatto nel precedente tutorial PyWine per la voce Add Wine e imposteremo l'handler alla stessa funzione dell'handler del pulsante btnEditWine.
.
Immagine

Il Codice

Renderemo ora operativo il pulsante Edit, la prima cosa è ottenere le informazioni del record su cui la gtk.TreeView è attualmente posizionata. Ci sono due modi per farlo, il primo è quello di leggere tutti i dati dalle quattro colonne e il secondo è quello di aggiungere il nostro oggetto wine al gtk.ListStore (il nostro modello), ma non visualizzarlo nella gtk.TreeView.

Dal momento che è piu' semplice e può risultare utile in futuro la nostra classe wine conterrà informazioni aggiuntive. Questo significa che abbiamo bisogno di cambiare un po' il nostro codice.

Per prima cosa dobbiamo aggiungere una ulteriore colonna alla nostra definizione delle colonne nella funzione __init__:

Codice: Seleziona tutto
self.cWineObject = 0
self.cWine = 1
self.cWinery = 2
self.cGrape = 3
self.cYear = 4

Si noti che abbiamo messo l'oggetto wine nella posizione 0 della lista, quindi dobbiamo modificare il nostro codice di creazione del gtk.ListStore nella stessa funzione come segue:

Codice: Seleziona tutto
#Create the listStore Model to use with the wineView
self.wineList = gtk.ListStore(gobject.TYPE_PYOBJECT
               , gobject.TYPE_STRING
               , gobject.TYPE_STRING
               , gobject.TYPE_STRING
               , gobject.TYPE_STRING)

Rimane tutto come prima, tranne che la prima voce del nostro gtk.ListStore sarà ora un oggetto python. Al fine di rendere il codice di cui sopra compilabile bisogna aggiungere il seguente import nella parte superiore del nostro file:

Codice: Seleziona tutto
import gobject


Cambieremo ora il modo in cui aggiungiamo il nostro vino alla gtk.ListStore cosi da includere l'oggetto wine. Per fortuna nel nostro precedente tutorial abbiamo aggiunto una funzione GetList() per la nostra classe wine che restituisce l'elenco da aggiungere al gtk.ListStore(), quindi tutto quello che dobbiamo fare è modificare quanto segue:

Codice: Seleziona tutto
def getList(self):
   """This function returns a list made up of the
   wine information.  It is used to add a wine to the
   wineList easily"""
   return [self, self.wine, self.winery, self.grape, self.year]

Non è un grande cambiamento, serve solo a  fare in modo che GetList() includa la classe wine, all'inizio della lista.

Il passo successivo è quello di consentire di fatto all'utente di modificare un vino, ma prima di farlo c'è un altra cosa da fare. Nel tutorial precedente la funzione __init__ della finestra di dialogo Wine accettava tutti gli elementi che compongono la nostra classe wine come singoli parametri di inizializzazione.

Codice: Seleziona tutto
def __init__(self, wine="", winery="", grape="", year=""):

Questo funziona bene se si dispone di un numero ridotto o parametri, ma se la nostra classe wine è destinata a crescere, l'inizializzazione della classe wineDialog potrebbe diventare un problema. Così modifichiamo la funzione __init__ per farle accettare un oggetto di classe wine, piuttosto che tutte le sue parti:

Codice: Seleziona tutto
def __init__(self, wine=None):
   """Initialize the class.
   wine - a Wine object"""

   #setup the glade file
   self.gladefile = "pywine.glade"
   #setup the wine that we will return
   if (wine):
      #They have passed a wine object
      self.wine = wine
   else:
      #Just use a blank wine
      self.wine = Wine()


Il passo successivo è modificare finalmente un vino, lo faremo nella funzione on_EditWine(), collegata all'evento click del pulsante Edit:

Codice: Seleziona tutto
def on_EditWine(self, widget):
   """Called when the user wants to edit a wine entry"""

   # Get the selection in the gtk.TreeView
   selection = self.wineView.get_selection()
   # Get the selection iter
   model, selection_iter = selection.get_selected()

   if (selection_iter):
      """There is a selection, so now get the the value at column
      self.cWineObject, the Wine Object"""
      wine = self.wineList.get_value(selection_iter, self.cWineObject)
      # Create the wine dialog, based off of the current selection
      wineDlg = wineDialog(wine);
      result,newWine = wineDlg.run()

      if (result == gtk.RESPONSE_OK):
         """The user clicked Ok, so let's save the changes back
         into the gtk.ListStore"""
         self.wineList.set(selection_iter
               , self.cWineObject, newWine
               , self.cWine, newWine.wine
               , self.cWinery, newWine.winery
               , self.cGrape, newWine.grape
               , self.cYear, newWine.year)

La prima cosa da fare è chiamare gtk.TreeView.get_selection() per ottenere l'oggetto gtk.TreeSelection che viene associato al gtk.TreeView. Chiameremo poi gtk.TreeSelection.get_selected() che restituira il nostro gtk.TreeModel  (che non ci interessa) e un gtk.TreeIter che punta al nodo selezionato nella nostra gtk.TreeView (che invece ci interessa).

Il gtk.TreeIter restituito dalla funzione get_selected() sarà None se non vi è alcuna selezione, usiamo gtk.TreeIter per ottenere l'oggetto wine del record su cui è attualmente la selezione nel nostro gtk.TreeView chiamando il gtk.TreeModel.get_value(). Una volta che abbiamo l'oggetto wine il resto è piuttosto semplice, creiamo il nostro oggetto wineDialog, lo visualizziamo (run), e cliccando su Ok si aggiorna l'elemento selezionato nel gtk.TreeView utilizzando la funzione gtk.ListStore.set().

La funzione gtk.ListStore.set() in realtà è abbastanza interessante perché vuole un gtk.TreeIter come primo parametro (la riga su cui impostare i valori) e il resto dei suoi parametri possono essere una o più coppie numero_colonna, nuovo_valore! La mia delusione è stata solo non aver trovato una funzione che utilizza una list  nello stesso modo di come fa gtk.ListStore.append().

Dal momento che non si vuole reinserire i vini che ci piacciono ogni volta che si avvia l'applicazione è ora che cominciamo a salvare e caricare la nostra carta dei vini.

Salvataggio e caricamento dell'elenco dei Vini.

La prima cosa che faremo è prendere in prestito due funzioni di supporto dal tutorial WordPy blogging offline tool:

Codice: Seleziona tutto
def show_error_dlg(self, error_string):
   """This Function is used to show an error dialog when
   an error occurs.
   error_string - The error string that will be displayed
   on the dialog.
   """
   error_dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR
            , message_format=error_string
            , buttons=gtk.BUTTONS_OK)
   error_dlg.run()
   error_dlg.destroy()

Questa funzione ci fornisce un modo semplice per consentire all'utente di sapere che un errore si è verificato, mostrandogli una finestra-dialogo di errore. La aggiungeremo alla classe pyWine. Per ulteriori informazioni su come funziona vedere il tutorial WordPy blogging offline tool.

Prenderemo anche la funzione browse_for_image():

Codice: Seleziona tutto
def browse_for_image(self):
   """This function is used to browse for an image.
   The path to the image will be returned if the user
   selects one, however a blank string will be returned
   if they cancel or do not select one."""
   
   file_open = gtk.FileChooserDialog(title="Select Image"
            , action=gtk.FILE_CHOOSER_ACTION_OPEN
            , buttons=(gtk.STOCK_CANCEL
                     , gtk.RESPONSE_CANCEL
                     , gtk.STOCK_OPEN
                     , gtk.RESPONSE_OK))
   """Create and add the Images filter"""      
   filter = gtk.FileFilter()
   filter.set_name("Images")
   filter.add_mime_type("image/png")
   filter.add_mime_type("image/jpeg")
   filter.add_mime_type("image/gif")
   filter.add_pattern("*.png")
   filter.add_pattern("*.jpg")
   filter.add_pattern("*.gif")
   file_open.add_filter(filter)
   """Create and add the 'all files' filter"""
   filter = gtk.FileFilter()
   filter.set_name("All files")
   filter.add_pattern("*")
   file_open.add_filter(filter)
   
   """Init the return value"""
   result = ""
   if file_open.run() == gtk.RESPONSE_OK:
      result = file_open.get_filename()
   file_open.destroy()
   
   return result

La modificheremo in modo che funzioni come una finestra di dialogo File Open/Save per scorrere i file pyWine (*. PWI) al posto delle immagini. Controllare se si tratta di open o save passando un parametro aggiuntivo chiamato dialog_action, che sarà l'azione che usiamo per impostare la proprietà azione del gtk.FileChooserDialog:

Codice: Seleziona tutto
def file_browse(self, dialog_action, file_name=""):
   """This function is used to browse for a pyWine file.
   It can be either a save or open dialog depending on
   what dialog_action is.
   The path to the file will be returned if the user
   selects one, however a blank string will be returned
   if they cancel or do not select one.
   dialog_action - The open or save mode for the dialog either
   gtk.FILE_CHOOSER_ACTION_OPEN, gtk.FILE_CHOOSER_ACTION_SAVE
        file_name - Default name when doing a save"""

   if (dialog_action==gtk.FILE_CHOOSER_ACTION_OPEN):
      dialog_buttons = (gtk.STOCK_CANCEL
                     , gtk.RESPONSE_CANCEL
                     , gtk.STOCK_OPEN
                     , gtk.RESPONSE_OK)
   else:
      dialog_buttons = (gtk.STOCK_CANCEL
                     , gtk.RESPONSE_CANCEL
                     , gtk.STOCK_SAVE
                     , gtk.RESPONSE_OK)

   file_dialog = gtk.FileChooserDialog(title="Select Project"
            , action=dialog_action
            , buttons=dialog_buttons)
   """set the filename if we are saving"""
   if (dialog_action==gtk.FILE_CHOOSER_ACTION_SAVE):
      file_dialog.set_current_name(file_name)
   """Create and add the pywine filter"""
   filter = gtk.FileFilter()
   filter.set_name("pyWine database")
   filter.add_pattern("*." + FILE_EXT)
   file_dialog.add_filter(filter)
   """Create and add the 'all files' filter"""
   filter = gtk.FileFilter()
   filter.set_name("All files")
   filter.add_pattern("*")
   file_dialog.add_filter(filter)

   """Init the return value"""
   result = ""
   if file_dialog.run() == gtk.RESPONSE_OK:
      result = file_dialog.get_filename()
   file_dialog.destroy()

   return result


File_ext è semplicemente definito come segue:

Codice: Seleziona tutto
FILE_EXT = "pwi"


Aggiungeremo anche degli handlers per le voci di menu File | Open e File | Save nel nostro progetto glade utilizzando lo stesso metodo che abbiamo usato per le voci di menu Wine | Add e Wine | Edit. Chiamandoli on_file_open e on_file_save:

Codice: Seleziona tutto
#Create our dictionay and connect it
dic = {"on_mainWindow_destroy" : self.on_Quit
      , "on_AddWine" : self.OnAddWine
      , "on_EditWine" : self.on_EditWine
      , "on_file_open" : self.on_file_open
      , "on_file_save" : self.on_file_save}
self.wTree.signal_autoconnect(dic)


Per fare il salvataggio e il caricamento dei nostri vini utilizzeremo il modulo python shelve. Che è un modulo Python standard che può contenere la maggior parte (se non tutti) gli oggetti Python. Ci sono naturalmente altri modi in cui avremmo potuto fare questo, ma ho pensato che usare lo shelve in questa situazione è la soluzione più semplice.

Dalla documentazione:
A “shelf” is a persistent, dictionary-like object. The difference with “dbm” databases is that the values (not the keys!) in a shelf can be essentially arbitrary Python objects — anything that the pickle module can handle. This includes most class instances, recursive data types, and objects containing lots of shared sub-objects. The keys are ordinary strings.

   
Salvataggio

Iniziamo con il creare la funzione on_file_save(). Per iniziare permetteremo all'utente di decidere dove salvare il suo file, e con che nome. Poi faremo in modo che che il file abbia l'estensione decisa da noi e a quel punto  con un ciclo su tutti gli elementi della gtk.TreeView salvaremo ogni oggetto wine utilizzando il modulo shelve:

Codice: Seleziona tutto
def on_file_save(self, widget):
   """Called when the user wants to save a wine list"""

   # Get the File Save path
   save_file = self.file_browse(gtk.FILE_CHOOSER_ACTION_SAVE, self.project_file)
   if (save_file != ""):
      # We have a path, ensure the proper extension
      save_file, extension = os.path.splitext(save_file)
      save_file = save_file + "." + FILE_EXT
      """ Now we have the "real" file save loction create
      the shelve file, use "n" to create a new file"""
      db = shelve.open(save_file,"n")
      """Get the first item in the gtk.ListStore, and while it is not
      None, move forward through the list saving each item"""
      # Get the first item in the list
      iter = self.wineList.get_iter_root()
      while (iter):
         # Get the wine at the current gtk.TreeIter
         wine = self.wineList.get_value(iter, self.cWineObject)
         # Use the iters position in the list as the key name
         db[self.wineList.get_string_from_iter(iter)] = wine
         # Get the next iter
         iter = self.wineList.iter_next(iter)
      #close the database and write changes to disk, we are done
      db.close();
      #set the project file
      root, self.project_file = os.path.split(save_file)

Dopo aver lavorato con il gtk.TreeIter poco fa questo codice non dovrebbe essere così difficile da capire. In realtà l'unica vera parte difficile del codice è al seguente, il resto dovrebbe essere comprensibile con i commenti in-line:

Codice: Seleziona tutto
while (iter):
   # Get the wine at the current gtk.TreeIter
   wine = self.wineList.get_value(iter, self.cWineObject)
   # Use the iters position in the list as the key name
   db[self.wineList.get_string_from_iter(iter)] = wine
   # Get the next iter
   iter = self.wineList.iter_next(iter)

Fondamentalmente quello che stiamo facendo è scorrere ogni elemento nella gtk.ListStore e poi salvare nel file shelve l'oggetto wine nella posizione gtk.TreeIter corrente.

Codice: Seleziona tutto
db[self.wineList.get_string_from_iter(iter)] = wine

Il gtk.TreeModel.get_string_from_iter() "restituisce una rappresentazione di stringa del percorso a cui punta iter. Questa stringa è una lista separata da ':' di numeri. Ad esempio, "4:10:0:3" sarebbe un valore accettabile di ritorno per questa stringa. "(PyGTK Docs). Dal momento che stiamo usando un gtk.ListStore i valori restituiti saranno sempre i singoli valori che aumentano man mano che andiamo verso il basso nell'elenco.

Quindi il primo elemento sarà "0", il secondo "1", il terzo "2", e così via ciò ci sarà utile quando aperiremo i file, dal momento che le chiavi nei file shelve non sono in alcun ordine particolare.

Quando si chiude il file shelve, i dati saranno scritti sul disco.

Noterete anche l'inclusione della voce self.project_file come nome file di default, questa è una nuova aggiunta alla classe E il nome del file del progetto corrente, ci permette di dare il nome predefinito nella gtk.FileChooserDialog quando stiamo facendo un salvataggio. E' definita nella funzione __init__ come segue:

self.project_file = ""

Avremo cosi una finestra di dialogo che si apre in questo modo:

Immagine

Caricamento

Ora creiamo la funzione on_file_open() che, avendo capito la funzione on_file_save, non dovrebbe essere così difficile:

Codice: Seleziona tutto
# Get the file to open
   open_file = self.file_browse(gtk.FILE_CHOOSER_ACTION_OPEN)
   if (open_file != ""):
      # We have a path, open it for reading
      try:
         db = shelve.open(open_file,"r")
         if (db):
            # We have opened the file, so empty out our gtk.TreeView
            self.wineList.clear()
            """ Since the shelve file is not gaurenteed to be in order we
            move through the file starting at iter 0 and moving our
            way up"""
            count = 0;
            while db.has_key(str(count)):
               newwine = db[str(count)]
               self.wineList.append(newwine.getList())
               count = count +1
            db.close();
            #set the project file
            root, self.project_file = os.path.split(open_file)
         else:
            self.show_error_dlg("Error opening file")
      except:
         self.show_error_dlg("Error opening file")

Noterete che quando carichiamo gli elementi dalla lista si usa un contatore (count) e la funzione has_key(). Come spiegato in precedenza salviamo ogni oggetto wine, utilizzando il path gtk.TreeIter, che è un numero unico dato che stiamo usando un gtk.ListStore. Ma dal momento i records nel file non sono ordinati utilizziamo il nostro contatore per recuperare in ordine ogni record a partire da zero fino a quando il numero nel nostro contatore non è più presente nel file. (Nota: convertire l'intero in una stringa in quanto le chiavi nel file shelve devono essere stringhe.)

Per caricare un oggetto wine dal file chiediamo semplicemente l'oggetto alla posizione con chiave il nostro contatore:

Codice: Seleziona tutto
newwine = db[str(count)]

Poi aggiungiamo  il vino alla lista, e abbiamo caricato un file. PWI. Il try....except , in pratica cattura ogni errore che potrebbe verificarsi se l'utente tenta di aprire un file che non è un vero file di progetto pyWine.

Conclusione

Questo è tutto per questo tutorial, se lo avete capito si può facilmente aggiungere un menu File | New o aggiungere un pulsante Delete sulla barra degli strumenti, o anche impostare il titolo della finestra da il file di vini caricato.

Si potrebbe anche provare a giocare con diversi tipi di file di dati e provare un tipo di file XML, oppure usare un database e consentire all'utente di decidere quale tipo di salvataggio utilizzare per i propri dati.

È possibile scaricare il codice completo per questo tutorial qui.
Avatar utente
Alkatron
Entusiasta Emergente
Entusiasta Emergente
 
Messaggi: 1187
Iscrizione: aprile 2009
Distribuzione: debian jessie 64

Torna a Programmazione

Chi c’è in linea

Visualizzano questa sezione: Google [Bot], xubuntunew e 4 ospiti