Home > python, Tkinter > Tkinter: Radiobutton

Tkinter: Radiobutton

6 Marzo 2014

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:
I commenti sono chiusi.