Creazione di un widget personalizzato e convalida dell'input

Introduzione

In questa lezione vedremo come preparare un proprio widget personalizzato mettendo insieme alcuni widget base. Approfitteremo dell'esempio preparato anche per vedere come effettuare una convalida dei dati.

Immaginiamo di voler creare un banalissimo programma per la semplificazione di frazioni, in cui però decidiamo di voler preparare un widget personalizzato per l'input del numeratore e del denominatore della frazione.

Il risultato che vogliamo ottenere è il seguente:

Frazioni

Programma principale

Vogliamo fare in modo di poter aggiungere la frazione nella finestra principale come se fosse un widget normale, scrivendo qualcosa tipo:

self.parent = parent

self.Fraction=FractionWidget(self.parent, background='red')
self.Fraction['borderwidth']=10
self.Fraction['relief']='ridge'
self.Fraction['numerator']=12
self.Fraction['denominator']=24

self.Fraction.pack({'side':'top', 'padx':10, 'pady':10})

Come si vede, il nostro widget, chiamato FractionWidget, si comporta a tutti gli effetti come un widget di base (il costruttore riceve dei parametri che vengono impostati, le proprietà possono essere modificate con il metodo dell'accesso tramite parentesi quadre. ecc.). Inoltre, è possibile impostare i valori per il numeratore e il denominatore.

La classe FractionWidget

Per il nostro widget personalizzato definiamo una classe, in cui il costruttore prepara un frame in cui sono presenti due widget Entry separati da un widget Label con i trattini (si può fare di meglio). Per il numeratore e denominatore impostiamo la proprietà justify a right.

def __init__(self, parent, height=10, width=10, background=None):
    self.parent=parent
    self.height=height
    self.width=width

    self.NumeratorValue=IntVar()
    self.NumeratorValue.set(1)
    self.DenominatorValue=IntVar()
    self.DenominatorValue.set(1)

    self.MainFrame=Frame(self.parent, width=self.width, height=self.height)
    self.Numerator=Entry(self.MainFrame,
                         textvariable=self.NumeratorValue,
                         width=self.width,
                         justify="right")
    self.FractionLine=Label(self.MainFrame, text="------")
    self.Denominator=Entry(self.MainFrame,
                           textvariable=self.DenominatorValue,
                           width=self.width,
                           justify="right")
    self.Numerator.pack(side="top", padx=5, pady=5)
    self.FractionLine.pack(side="top")
    self.Denominator.pack(side="top", padx=5, pady=5)

    if background:
        self.MainFrame['background']=background

Pack, grid e place

Per fare in modo che sia possibile usare i metodi pack, grid e place per il nostro widget, essi andranno ridefiniti nella classe, eseguendoli con la stessa lista di parametri sul frame che abbiamo creato:

def pack(self, *args, **kwargs):
    self.MainFrame.pack(*args, **kwargs)

def grid(self, *args, **kwargs):
    self.MainFrame.grid(*args, **kwargs)

def place(self, *args, **kwargs):
    self.MainFrame.place(*args, **kwargs)

Convalida dell'input

Se vogliamo effettuare una convalida dell'input durante la digitazione (per evitare che vengano inseriti caratteri non numerici, nel nostro caso) bisogna adottare una tecnica un po' particolare, che deriva dal fatto che si è costretti a richiamare funzionalità di basso livello di Tk.

Si inizia definendo una tupla contenente il comando di validazione, composto da una funzione e da una serie di parametri che si possono tenere in considerazione per la validazione stessa:

vcmd=(self.parent.register(self.doValidation), '%P')

Poi si impostano per il widget in cui fare la validazione i parametri validate (quando effettuare la validazione) e validatecommand (la tupla impostata prima):

self.Numerator=Entry(self.MainFrame,
    ....
    validate="key",
    validatecommand=vcmd)

Per validate i valori validi sono key (pressione di un tasto), focus, focusin, focusout e all.

I parametri che si possono impostare per la validazione sono i seguenti:

  • %d = tipo di azione (1=inserimento, 0=cancellazione, -1 altro)
  • %i = indice del carattere da inserire/cancellare, oppure -1
  • %P = valore della casella di input, se la modifica è consentita
  • %s = valore della casella prima della modifica
  • %S = stringa di testo che dovrebbe essere inserita o cancellata, se esiste
  • %v = tipo di validazione correntemente impostata
  • %V = tipo di validazione che ha fatto scattare la funzione
  • %W = nome Tk del widget

Ridefinizione metodi degli operatori per l'accesso agli attributi

Se si vuole fare in modo che sia possibile cambiare le proprietà del widget mediante la sintassi delle parentesi quadrate, si devono ridefinire gli operatori mediante le funzioni __setitem__ e __getitem__:

def __setitem__(self, key, value):
    if key in ('background', 'bg', 'borderwidth', 'bd', 'relief'):
        self.MainFrame[key]=value
    if key=='numerator':
        self.setNumerator(value)
    if key=='denominator':
        self.setDenominator(value)


def __getitem__(self, key):
    if key in ('background', 'bg', 'borderwidth', 'bd', 'relief'):
        return self.MainFrame[key]
    if key=='numerator':
        return self.getNumerator()
    if key=='denominator':
        return self.getDenominator()

Barra di progressione

Una barra di progressione può essere creata basandosi su un canvas in cui si disegna un rettangolo colorato, come nel seguente esempio:

class ProgressBar(object):
    def __init__(self, parent, height=10, width=300, background=None, foreground='black', progress=0):
        # adapted from "Python 2.1 Bible" (by Dave Brueck and Stephen Tanner)
        self._parent=parent
        self._height=height
        self._width=width
        self._background=background
        self._foreground=foreground

        self.barcanvas=Canvas(
            self._parent,
            width=self._width,
            heigh=self._height,
            background=self._background,
            borderwidth=1,
            relief=SUNKEN
            )
        self.barcanvas.pack(padx=5, pady=2)
        self.rectangleValue=self.barcanvas.create_rectangle(0,0,0, self._height)
        self.barcanvas.itemconfigure(
            self.rectangleValue,
            fill=self._foreground
            )

        self.setProgressPercent(progress)

    def getProgressPercent(self):
        return self._progress

    def setProgressPercent(self, level):
        self._progress = min(100, max(0, level))
        self.DrawProgress()

    def DrawProgress(self):
        pixels=round(self._width * self.getProgressPercent()/100.0)
        self.barcanvas.coords(
            self.rectangleValue,
            0,
            0,
            pixels,
            self._height
            )

    def pack(self, *args, **kwargs):
        self.barcanvas.pack(*args, **kwargs)

    def grid(self, *args, **kwargs):
        self.barcanvas.grid(*args, **kwargs)

    def place(self, *args, **kwargs):
        self.barcanvas.place(*args, **kwargs)

Barra di progressione

Esecuzione di un programma esterno

Può capitare di dover eseguire un programma esterno per verificare un valore. Ad esempio, si immagini di volere che facendo clic su un pulsante venga avviato un programma in cui l'utente gioca a qualcosa. Il programma chiamante deve venire informato dell'esito del programma chiamato.

Launcher

Per ottenere questo risultato, il programma chiamato deve uscire con un esito, che nel nostro esempio può essere 0 (il giocatore ha vinto), 1 (il giocatore ha perso) oppure 2 (il giocatore ha chiuso la finestra).

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

if __name__=='__main__':
    sys.exit(main())

Il programma chiamante, d'altro canto, deve essere in grado di avviare un altro programma e capire qual ne è stato l'esito. Per farlo, useremo il modulo subprocess:

try:
    subprocess.check_output([interpretername(), 'thegame.pyw'])
    self.statusbar['text']='You won!'
except:
    self.statusbar['text']='You lose!'

Il nome dell'interprete varia da sistema a sistema, per cui possiamo definire una funzione interpretername() che ce lo restituisca a seconda di alcune considerazioni (file di configurazione, sistema operativo usato, o altro).

Si noti che questo metodo consente di far interagire programmi completamente indipendenti (anche scritti in altri linguaggi di programmazione).

results matching ""

    No results matching ""