Tkinter: Radiobutton
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è…)
…
Commenti recenti