Archivio

Archivio per la categoria ‘Tkinter’

Tkinter: progressbar

7 Aprile 2014 Commenti chiusi
import ttk
import time

class ProgressBar(object):
    def __init__(self, master=None):
        self.var = 0
        if master is None:
            self.root = tk.Tk()
        else:
            self.root = master
        self.root.title('wait...')

    def build_gui(self):
        self.frame = tk.Frame(self.root)  
        self.frame.pack()  
        self.pb = ttk.Progressbar(self.frame, mode="determinate",
                                  variable=self.var)
        self.pb.pack()

    def start(self):
        self.root.mainloop()  

class Controller(object):
    def __init__(self):
#        self.app = ProgressBar(master=tk.Toplevel()) # CoreFrame as parent
        self.app = ProgressBar(master=None) # progress bar on the fly
        self.app.build_gui()

    ## Do stuff ##

    def call_progressbar(self, newlimit=100):
        '''Call progressbar using newlimit as maximum argument'''
        self.app.pb.configure(maximum=newlimit)
        for i in range(newlimit):
            time.sleep(.005)
            self.app.pb.step()
            self.app.pb.update()
        self.app.pb.stop()
        self.app.root.destroy()

    def run(self):
        self.app.start()

if __name__ == '__main__':
    c = Controller()
    c.call_progressbar(200)
    c.run() # Only if master is None and we need a mainloop
    


Categorie:python, Tkinter Tag:

Tkinter: Radiobutton

6 Marzo 2014 Commenti chiusi

Ecco come utilizzare i radiobutton con Tkinter.
Il modo più semplice è quando si creano i radiobutton e
si fa il bind alla callback direttamente all’interno della stessa classe:

import Tkinter as tk

class View:
    def __init__(self):
        self.root = tk.Tk()
        self.v = tk.IntVar()
        self.v.set(1)  # initializing to the 1st choice

        tk.Label(self.root,
              text="""Choose your number:""",
              justify=tk.LEFT, padx=20).pack()

        tk.Radiobutton(self.root, text='One', padx=20,
                        variable=self.v, command=self.ShowChoice,
                        value=1).pack(anchor=tk.W)
        tk.Radiobutton(self.root, text='Two', padx=20,
                        variable=self.v, command=self.ShowChoice,
                        value=2).pack(anchor=tk.W)

    def ShowChoice(self):
        print 'click %s' % self.v.get()

if __name__ == '__main__':
    v = View()
    v.root.mainloop()

Con un po’ di refactoring si può rendere la cosa più elegante,
usando un forloop su una lista di tuple numero-testo da associare ai radiobutton:

import Tkinter as tk

class View:
    def __init__(self):
        self.root = tk.Tk()
        self.v = tk.IntVar()
        self.v.set(1)  # initializing to the 1st choice

        tk.Label(self.root,
              text="""Choose your number:""",
              justify=tk.LEFT, padx=20).pack()

        for num, text in enumerate(['One', 'Two', 'Three']):
            tk.Radiobutton(self.root, text=text,
                        padx=20,
                        variable=self.v,
                        command=self.ShowChoice,
                        value=num+1).pack(anchor=tk.W)

    def ShowChoice(self):
        print 'click %s' % self.v.get()

if __name__ == '__main__':
    v = View()
    v.root.mainloop()

Fin qui tutto bene.
Bisogna prestare un po’ di attenzione, se si utilizzano dei pattern
MVC-like, o semplicemente se si deve ottenere un aggangio ai radiobutton,
all’esterno della classe che li contiene.
Vediamo prima la versione senza forloop:

import Tkinter as tk

class View:
    def __init__(self):
        self.root = tk.Tk()
        self.v = tk.IntVar()
        self.v.set(1)  # initializing to the first choice

        tk.Label(self.root, text="""Choose number:""",
                 justify = tk.LEFT, padx=20).pack()

        self.rba = tk.Radiobutton(self.root, text='one',
                        padx=20, variable=self.v, value=1)
        self.rba.pack(anchor=tk.W)

        self.rbb = tk.Radiobutton(self.root, text='Two',
                        padx=20, variable=self.v, value=2)
        self.rbb.pack(anchor=tk.W)

class Controller:
    def __init__(self):
        self.v = View()
        self.v.rba.configure(command=self.on_click)
        self.v.rbb.configure(command=self.on_click)

        self.v.root.mainloop()

    def on_click(self):
        print 'click %s' % self.v.v.get()

if __name__ == '__main__':
    Controller()

La prima cosa fondamentale è rendere i radiobutton attributi di self.
Seconda cosa IMPORTANTISSIMA è NON utilizzare la creazione dei radiobutton
su una riga di codice soltanto.
Se utilizzassimo la sintassi:

        self.rba = tk.Radiobutton(self.root, text='one',
                        padx=20, variable=self.v,
                        value=1).pack(anchor=tk.W)

avremmo dei grossi problemi. In python a comandare è sempre l’ultima chiamata a
metodo, in questo caso pack(). pack() per come è strutturata, ma anche grid(),
dopo aver svolto i suoi compiti, ritorna None.
Quindi il Radiobutton viene creato con l’instanziamento

tk.Radiobutton(self.root, text='one', padx=20, variable=self.v, value=1)

ma a self.rba, viene successivamente assegnato un bel None, da

....pack(anchor=tk.W)

Quando nel controller faremo il binding alla callback tramite il metodo
configure(), saremo fregati.
Questo non succede invece utilizzando le “due linee di codice”

        self.rba = tk.Radiobutton(self.root, text='one',
                        padx=20, variable=self.v, value=1)
        self.rba.pack(anchor=tk.W)

Detto questo, con un po’ di refactoring:

import Tkinter as tk

class View:
    def __init__(self):
        self.root = tk.Tk()
        self.v = tk.IntVar()
        self.v.set(1)  # initializing to 'One' choice

        tk.Label(self.root,
              text="""Choose your number:""",
              justify=tk.LEFT, padx=20).pack()

        for num, text in enumerate(['One', 'Two', 'Three']):
            tk.Radiobutton(self.root, text=text, padx=20,
                        variable=self.v, value=num+1).pack(anchor=tk.W)

class Controller:
    def __init__(self):
        self.v = View()
        d = self.v.root.children
        for ch in d.values():
            if isinstance(ch, tk.Radiobutton):
                ch.configure(command=self.on_click)

        self.v.root.mainloop()

    def on_click(self):
        print 'click %s' % self.v.v.get()

if __name__ == '__main__':
    Controller()

Qui ci sono alcuni accorgimenti.
Se usiamo il forloop, non ha più senso utilizzare le due linee separate
di definizione del radiobutton, poichè comunque, al termine del ciclo,
varrebbero sempre e solo gli ultimi assegnamenti al radiobutton.
Quindi, anche volendolo aggangiare dall’esterno (controller), avrei
il binding alla callback, valido solo per l’ultimo radiobutton creato.
Per non avere problemi dovrei creare i singoli radiobutton senza forloop,
come nel primo esempio, rendendoli attributi di ‘self’ e poi bindarli
singolarmente all’interno del controller, ma così ripeteremmo il codice…
Visto che puntiamo al refactoring e al DRY, ho utilizzato il dizionario children
di root.

        d = self.v.root.children
        for ch in d.values():
            if isinstance(ch, tk.Radiobutton):
                ch.configure(command=self.on_click)

root è il parent di tutti i widget della classe View:
se il widget è una istanza di tk.Radiobutton, allora
con il metodo configure, lo bindiamo alla callback
on_click, del controller.
In questo modo abbiamo mantenuto anche una notevole
separazione tra le classi (anche se ho creato i valori dei radiobuttons
dentro View e bisognerebbe passarglieli tramite controller, ma vabbè…)

Categorie:python, Tkinter Tag:

Tkinter: catturare testo Entry senza bottone

27 Novembre 2013 Commenti chiusi

Voglio catturare il testo all’interno di una Entry, mentre lo scrivo.
Ovviamente non voglio fare come su tutte le guide, dove viene utilizzato
un bottone per la cattura.
Voglio fare il bind a e lavorare sul testo in real-time.
Probabilmente esiste un metodo più elegante di questo,
ma il codice seguente fa il suo lavoro.

import Tkinter as tk
import ttk

CHARS = 'abcdefghijklmnopqrstuvwxyz1234567890'
CENTER = tk.N + tk.S + tk.W + tk.E


class ViewTH:
    def __init__(self):
        self.root = tk.Tk()
        self.frame = tk.Frame(self.root)
        self.frame.pack(fill=tk.BOTH)
        self.en_cod = tk.Entry(self.frame)
        self.en_cod.grid(sticky=CENTER, row=0, column=0, padx=2, pady=2)
        btn_quit = tk.Button(self.frame, text='Quit',
                      command=self.root.destroy)
        btn_quit.grid(sticky=CENTER, row=1, column=0, padx=2, pady=2)
        self.en_cod.bind('<KeyPress>', func=self.on_entry)
        self.en_cod.focus_set()

    def on_entry(self, event):
        text = self.en_cod.get()
        char = event.keysym
        index = self.en_cod.index(tk.INSERT)
        if not text:
            string = char
        else:
            if char in CHARS + CHARS.upper():
                string = text + char
            elif char == 'BackSpace':
                if index == 0:
                    string = text
                else:
                    string = text[ :index - 1] + text[index: ]
            elif char == 'Delete':
                string = text[ :index] + text[index + 1: ]
            else:
                 string = text
        print string

    def start(self):
        self.root.mainloop()


if __name__ == '__main__':
    vth = ViewTH()
    vth.start()

In pratica nella callback on_entry, richiamata ad ogni pressione di un tasto all’interno della
Entry, effettuo questi controlli:

…e se il tasto premuto, è il primo della sequenza, lo utilizzo per ottenere il primo
carattere, che altrimenti verrebbe saltato dal metodo get().

...
    def on_entry(self, event):
        text = self.en_cod.get()
        char = event.keysym
        index = self.en_cod.index(tk.INSERT)
        if not text:
            string = char

poi controllo che il carattere premuto (event.symkey), faccia parte di quelli consentiti
(CHARS)

...
        else:
            if char in CHARS + CHARS.upper():
                string = text + char
	    ...
            else:
                 string = text

In più nelle condizioni elif gestisco i casi di ‘BackSpace’ e ‘Delete’,
richiamando l’indice posizionale del cursore all’interno della Entry,
per ottenere il testo corretto in caso di cancellazione caratteri
(soprattutto nel mezzo della stringa).

index = self.en_cod.index(tk.INSERT)

Tutti gli altri tasti non alfanumerici non li considero.
Nel caso comunque premessi ‘Home’ e di seguito ‘BackSpace’, gestirei la stringa all’interno di
questo if/else:

            elif char == 'BackSpace':
                if index == 0:
                    string = text
                else:
                    string = text[ :index - 1] + text[index: ]

…poi è chiaro che con un Button ed una callback che al proprio interno richiami il
metodo get() della Entry ed il testo al suo interno, sarebbe tutto molto più facile,
ma più brutto.

Categorie:python, Tkinter Tag:

Tkinter callbacks

5 Novembre 2013 Commenti chiusi

Partiamo dalla situazione classica dove abbiamo
un widget collegato ad una callback.
Quindi creiamo una semplicissima window con frame,
un bottone generico ed un bottone di chiusura.

Di solito tengo completamente separata la logica
del programma dalla “grafica” e quindi anche i vari bindings ai
widgets, che lascio gestire ad una classe estranea, eccezion fatta
per il bottone di chiusura che lego chiaramente al metodo destroy.

Perchè questo?
Perchè questa GUI e questo bottone, potrei volerli usare per altri
scopi, senza dover quindi mettere mano al codice della grafica stessa.
In questo caso dovrei modificare solo le callback dei vari handlers.

import Tkinter as tk

CENTER = tk.N + tk.S + tk.E + tk.W

class MyApp:
    def __init__(self):
        self.root = tk.Tk()
        self.frame = tk.Frame(master=self.root)
        self.frame.pack(fill=tk.BOTH, expand=1)
        self.btn_a = tk.Button(master=self.frame, text='Button 1', width=10)
        self.btn_a.grid(sticky=CENTER, row=0, column=0)
        btn_quit = tk.Button(master=self.frame, text='Quit', width=10,
                             command=self.root.destroy)
        btn_quit.grid(sticky=CENTER, row=0, column=1)

class GUIHandler:
    def __init__(self):
        self.app = MyApp()
        self.bind_widgets()

    def bind_widgets(self):
        self.app.btn_a.configure(command=self.on_button)

    def on_button(self):
        print 'You have clicked on Button-1'

    def start(self):
        self.app.root.mainloop()


if __name__ == '__main__':
    h = GUIHandler()
    h.start()

Come dicevo, il btn_quit, l’ho lasciato interno alla GUI e non collegato
dall’esterno, poichè il suo scopo è di chiudere la app, quindi anche gestendo
la gui da un altro handler, questa funzione rimarrebbe la stessa.
Il btn_a invece (con self e accessibile dall’esterno!), lo bindo direttamente dall’handler.
Questa cosa la si può fare sia con il metodo configure(command=func), o con il
metodo bind(event, func, **kwargs):

...
class GUIHandler:
    def __init__(self):
        self.app = MyApp()
        self.bind_widgets()

    def bind_widgets(self):
        self.app.btn_a.bind('<Button-1>', func=self.on_button)

    def on_button(self, event):
        print 'You have clicked on Button-1'

    def start(self):
        self.app.root.mainloop()


if __name__ == '__main__':
    h = GUIHandler()
    h.start()

in questo caso al metodo bind, bisogna passare come argomento, il tipo di evento
(button-1 sta per il bottone 1 del mouse cliccato) ed ovviamente la callback.
La stessa callback, deve avere anche l’argomento event.

Se volessi passare degli argomenti alla callback, dovrei utilizzare una lambda:

import Tkinter as tk

CENTER = tk.N + tk.S + tk.E + tk.W

class MyApp:
    def __init__(self):
        self.root = tk.Tk()
        self.frame = tk.Frame(master=self.root)
        self.frame.pack(fill=tk.BOTH, expand=1)
        self.btn_a = tk.Button(master=self.frame, text='Button 1', width=10)
        self.btn_a.grid(sticky=CENTER, row=0, column=0)
        btn_quit = tk.Button(master=self.frame, text='Quit', width=10,
                             command=self.root.destroy)
        btn_quit.grid(sticky=CENTER, row=0, column=1)

class GUIHandler:
    def __init__(self):
        self.app = MyApp()
        self.bind_widgets()

    def bind_widgets(self):
        self.app.btn_a.configure(command=lambda: self.on_button(100))

    def on_button(self, num):
        print 'You have clicked Button-%s' % num

    def start(self):
        self.app.root.mainloop()


if __name__ == '__main__':
    h = GUIHandler()
    h.start()
Categorie:Tkinter Tag:

tkFileDialog: Nascondere la root window

16 Ottobre 2013 Commenti chiusi

Non sempre si utilizza il tkFileDialog, messo a disposizione da Tkinter,
all’interno di una root window.
Capita a volta di scrivere semplicissime righe di codice senza GUI,
ma di avere bisogno di un FileBrowser per la selezione (o salvataggio)
di un file.

Nessun problema,
ad es. il codice seguente fa quello che serve:

import Tkinter as tk
import tkFileDialog as tkfd


def get_file(root):
    ch_file = tkfd.askopenfilename(master=root,
                filetypes=[('Text file','*.txt'),
                           ('All files','*.*')])

    root.destroy()
    return ch_file

if __name__ == '__main__':
    root = tk.Tk()
    f = get_file(root)
    root.mainloop()

C’è una cosa però che non voglio vedere:
la finestra di root generica, che appare dietro
all’tkFileDialog.

Per ovviare a questo problema, basta utilizzare il
metodo withdraw di root, inserendolo subito prima
della chiamata al metodo askopenfilename.

import Tkinter as tk
import tkFileDialog as tkfd


def get_file(root):
    root.withdraw()
    ch_file = tkfd.askopenfilename(master=root,
                filetypes=[('Text file','*.txt'),
                           ('All files','*.*')])

    root.destroy()
    return ch_file

if __name__ == '__main__':
    root = tk.Tk()
    f = get_file(root)
    root.mainloop()
Categorie:Tkinter Tag: