Programmazione GUI con Tkinter

Introduzione

I programmi che vogliono mettere a disposizione una GUI (graphical user interface) possono essere sviluppati in diversi modi usando Python.

Essendo un linguaggio multipiattaforma, i programmi Python devono tendenzialmente essere eseguibili su diversi tipi di calcolatore e con diversi sistemi operativi, garantendo funzionalità e, se possibile, look&feel analoghi (anche se adattati all'interfaccia utente standard delle diverse piattaforme).

Di seguito presenteremo qualche esempio che sfrutta la libreria Tkinter, che probabilmente non è la più semplice da usare né quella che garantisce i risultati migliori, ma ha due indubbi vantaggi:

  • è fornita insieme a Python;
  • funziona anche con Python versione 3 e successive.

Documentazione (e guida alla sua lettura)

La documentazione su Tkinter c'è ed è sufficientemente accurata da poter essere utilizzata per scrivere almeno qualche programma di base, anche se a volte gli esempi forniti riguardano Python 2.x, e quindi non sono perfettamente compatibili con Python 3.x. Piccole modifiche al codice sorgente e qualche ricerca su web possono però risolvere i problemi.

Si può iniziare sicuramente da Tkinter per sopravvivere, di Stephen Ferg (con traduzione di Massimo Piai). La documentazione ufficiale ha una sezione su Tkinter che può essere d'aiuto. Altro documento interessante può essere An Introduction to Tkinter di Fredrik Lundh.

Nello studiare gli esempi che si trovano su Internet, bisognerà considerare che con Python 3.x:

  • il modulo da importare si chiama tkinter (minuscolo)
  • la funzione print richiede le parentesi
  • alcuni componenti, come filedialog, messagebox, ecc. vanno importati in maniera leggermente diversa

Altre cose degne di nota sono che:

  • IDLE (l'editor spesso usato per gestire il codice python) è esso stesso un'applicazione tkinter, e ciò può comportare dei problemi durante l'esecuzione. Si consiglia di avviare le applicazioni GUI direttamente e non premendo F5 dall'interno di IDLE;

  • è bene che i file abbiano estensione .pyw, in modo che le finestre vengano aperte direttamente;

  • per essere eseguiti direttamente in ambiente linux, la prima riga dovrebbe contenere la scritta #!/usr/bin/env python3.1 e il file dovrebbe essere reso eseguibile (chmod +x)

Esempi di base

Hello, world!

Un primo esempio fa comparire una finestra di dialogo (senza nulla dentro).

import tkinter 
root = tkinter.Tk()  
root.mainloop()

GUI

Avremmo potuto ottenere lo stesso risultato importando direttamente nel namespace corrente le componenti di tkinter:

from tkinter import * 
root = Tk()  
root.mainloop() 

Quando il ciclo principale si esegue esso attende gli eventi che accadono nell'oggetto radice.

Se avviene un evento allora esso viene gestito e il ciclo prosegue, continuando la sua attesa per il successivo evento. L'esecuzione del ciclo continua sinché nella finestra radice non si verifica un evento «destroy» (ad esempio, quando viene chiusa la finestra). Quando la radice è distrutta, il ciclo di gestione degli eventi termina.

Se vogliamo aggiungere un'etichetta nella finestra, dobbiamo prima definirla e poi sistemarla (con il metodo pack o con altri) nella finestra principale o in un altro contenitore. Il processo serve a stabilire una relazione visuale tra il widget e il suo genitore. In sua assenza, il widget esiste ma non viene visualizzato.

from tkinter import * 
root = Tk()
MyLabel = Label(root, text="Hello, world!")
MyLabel.pack()
root.mainloop() 

GUI

Gli oggetti GUI (widget, da windows gadget) vengono organizzati in gerarchie. Nell'esempio, l'etichetta MyLabel è figlia dell'oggetto principale, root.

Per i widget possono essere definiti i valori di alcuni attributi in diversi modi. Il più semplice è di impostarli come se fossero valori di un dizionario:

from tkinter import * 
root = Tk()
MyLabel = Label(root, text="The quick brown fox jumps over the lazy dog.")
MyLabel['background']="#FFFFFF"
MyLabel['foreground']="red"
MyLabel.pack()
root.mainloop() 

GUI

Avremmo potuto impostare anche il testo scrivendo un codice simile al seguente:

MyLabel['text']="The quick brown fox jumps over the lazy dog."

Si può avere un po' di controllo su come i widget vengono sistemati all'interno della finestra usando l'attributo "side", come nell'esempio che segue:

from tkinter import * 
root = Tk()
MyLabel1 = Label(root, text="Prima etichetta.")
MyLabel1['background']="#FFFFFF"
MyLabel1['foreground']="red"
MyLabel1.pack({"side":"right"})
MyLabel1.pack()
MyLabel2 = Label(root, text="Seconda etichetta.")
MyLabel2['background']="#FFFFFF"
MyLabel2['foreground']="blue"
MyLabel2.pack({"side":"left"})
root.mainloop() 

GUI

Il metodo pack() serve ad accatastare i widget uno dopo l'altro a partire dalla posizione indicata. Vedremo ulteriori esempi più avanti.

Gestione degli eventi

Per i pulsanti è possibile impostare un gestore di eventi, in modo da richiamare una determinata funzione al clic del mouse.

from tkinter import * 

def MyButton_Click():
    print("Mi hai cliccato... (standard output)")
    StatusBar["text"]="mi ha cliccato... (status bar)"

root = Tk()

MyButton = Button(root, text="Fai clic qui")
MyButton['background']="#FFFFFF"
MyButton['foreground']="red"
MyButton['command']=MyButton_Click
MyButton.pack({"side":"top", "padx": 10, "pady": 20})

StatusBar = Label(root, text="...")
StatusBar['background']="#FFFFFF"
StatusBar['foreground']="blue"
StatusBar.pack({"side":"bottom", "expand":"yes", "fill":"x"})

root.mainloop() 

GUI
prima…

GUI
dopo…

Input di testo

Per poter chiedere in input un testo all'utente, si può usare un widget di tipo Entry, che viene associato ad una variabile.

from tkinter import * 
def MyButton_Click():
    StatusBar["text"]=name.get()

root = Tk()
name = StringVar()
MyInputBox = Entry(root, textvariable=name)
MyInputBox.pack()
MyButton = Button(root, text="Fai clic qui")
MyButton['background']="#FFFFFF"
MyButton['foreground']="red"
MyButton['command']=MyButton_Click
MyButton.pack({"side":"top", "padx": 10, "pady": 20})
StatusBar = Label(root, text="...")
StatusBar['background']="#FFFFFF"
StatusBar['foreground']="blue"
StatusBar.pack({"side":"bottom", "expand":"yes", "fill":"x"})

root.mainloop() 

GUI

Implementazione Object-Oriented

Per applicazioni con un minimo di complessità, conviene definire una propria classe, come nell'esempio seguente:

from tkinter import * 

class Application(object):
    def __init__(self, parent):
        self.name = StringVar()
        self.MyInputBox = Entry(parent, textvariable=self.name)
        self.MyInputBox.pack()
        self.MyButton = Button(parent, text="Fai clic qui")
        self.MyButton['background']="#FFFFFF"
        self.MyButton['foreground']="red"
        self.MyButton['command']=self.MyButton_Click
        self.MyButton.pack({"side":"top", "padx": 10, "pady": 20})
        self.StatusBar = Label(parent, text="...")
        self.StatusBar['background']="#FFFFFF"
        self.StatusBar['foreground']="blue"
        self.StatusBar.pack({"side":"bottom", "expand":"yes", "fill":"x"})

    def MyButton_Click(self):
        self.StatusBar["text"]=self.name.get()

def main():
    root = Tk()
    myapp = Application(root)
    root.mainloop() 

if __name__=='__main__':
    main()

Creazione e gestione dinamica dei widget

Può essere utile creare dinamicamente dei widget durante l'esecuzione del programma (ad esempio, a seconda dell'input dell'utente). Se si vogliono creare dei pulsanti, si pone il problema di come associare una funzione che gestisce gli eventi associati ai vari pulsanti (visto che il gestore di eventi è una funzione che non ha parametri). La soluzione, come nell'esempio seguente, è di creare una funzione dinamicamente (tramite la parola chiave lambda). Approfondiremo l'uso delle espressioni lambda più avanti, nella lezione 22. Vedremo anche, nella lezione 23, che attraverso functools.partial() si può ottenere lo stesso risultato in maniera leggermente più semplice.

from tkinter import *

def str2int(s):
    try:
        k=int(s)
        return k
    except:
        return 0

class Application(object):
    def __init__(self, parent):

        self.name = StringVar()
        self.buttons=[]
        self.parent = parent

        self.MyInputBox = Entry(parent, textvariable=self.name)
        self.MyInputBox.pack()

        self.MyButton = Button(parent, text="Fai clic qui")
        self.MyButton['background']="#FFFFFF"
        self.MyButton['foreground']="red"
        self.MyButton['command']=self.MyButton_Click
        self.MyButton.pack({"side":"top", "padx": 10, "pady": 20})

        self.StatusBar = Label(parent, text="...")
        self.StatusBar['background']="#FFFFFF"
        self.StatusBar['foreground']="blue"
        self.StatusBar.pack({"side":"bottom", "expand":"yes", "fill":"x"})

    def MyButton_Click(self):
        self.StatusBar["text"]=self.name.get()
        n=str2int(self.name.get())
        for i in range(n):
            b=Button(self.parent, text="pulsante " + str(i))
            b['background']='yellow'
            b['command']=self.BuildButtonAction(i)
            b.pack()
            self.buttons.append(b)

    def BuildButtonAction(self, number):
        return lambda : self.ChangeButtonColor(number)

    def ChangeButtonColor(self, number):
        self._SwitchColor(self.buttons[number])

    def _SwitchColor(self, button):
        button['background']='yellow' if button['background']=='red' else 'red'

def main():
    root = Tk()
    myapp = Application(root)
    root.mainloop() 

if __name__=='__main__':
    main()

GUI

results matching ""

    No results matching ""