2010-10-06 19 views
9

Sto usando Python e Tkinter e voglio l'equivalente dell'evento onchange da altri toolkit/lingue. Voglio eseguire il codice ogni volta che l'utente aggiorna lo stato di alcuni widget.Come eseguire un codice ogni volta che cambia un valore del widget Tkinter?

Nel mio caso, ho molti Entry, Checkbutton, Spinbox e Radiobutton widget. Ogni volta che qualcuno di questi cambiamenti, voglio eseguire il mio codice (in questo caso, aggiornare una casella di testo sull'altro pannello).

(basta ricordare che l'utente può interagire con questi widget utilizzando il mouse o la tastiera, e anche utilizzando Ctrl + V per incollare il testo)

risposta

0

Finora, non ho incontrato alcuna cosa equivalente di onChange in Tkinter. I widget possono essere associati ai vari eventi e l'ho fatto esplicitamente.

+2

questo approccio richiede attenzione per i dettagli, oppure si possono creare associazioni che accadono * prima * il widget in realtà cambia, piuttosto che immediatamente dopo. –

7

Come faccio a risolvere questo in Tcl sarebbe quello di fare in modo che i widget checkbutton, casella numerica e RadioButton sono tutti associati con una variabile array. Metterei quindi una traccia sull'array che causerebbe il richiamo di una funzione ogni volta che viene scritta la variabile. Tcl lo rende banale.

Sfortunatamente Tkinter non supporta il funzionamento con gli array Tcl. Fortunatamente, è abbastanza facile hackerare. Se sei avventuroso, prova il seguente codice.

Dal dipartimento di divulgazione completo: ho buttato questo insieme questa mattina in circa mezz'ora. In realtà non ho usato questa tecnica in alcun codice reale. Non ho potuto resistere alla sfida, però, per capire come usare gli array con Tkinter.

import Tkinter as tk 

class MyApp(tk.Tk): 
    '''Example app that uses Tcl arrays''' 

    def __init__(self): 

     tk.Tk.__init__(self) 

     self.arrayvar = ArrayVar() 
     self.labelvar = tk.StringVar() 

     rb1 = tk.Radiobutton(text="one", variable=self.arrayvar("radiobutton"), value=1) 
     rb2 = tk.Radiobutton(text="two", variable=self.arrayvar("radiobutton"), value=2) 
     cb = tk.Checkbutton(text="checked?", variable=self.arrayvar("checkbutton"), 
          onvalue="on", offvalue="off") 
     entry = tk.Entry(textvariable=self.arrayvar("entry")) 
     label = tk.Label(textvariable=self.labelvar) 
     spinbox = tk.Spinbox(from_=1, to=11, textvariable=self.arrayvar("spinbox")) 
     button = tk.Button(text="click to print contents of array", command=self.OnDump) 

     for widget in (cb, rb1, rb2, spinbox, entry, button, label): 
      widget.pack(anchor="w", padx=10) 

     self.labelvar.set("Click on a widget to see this message change") 
     self.arrayvar["entry"] = "something witty" 
     self.arrayvar["radiobutton"] = 2 
     self.arrayvar["checkbutton"] = "on" 
     self.arrayvar["spinbox"] = 11 

     self.arrayvar.trace(mode="w", callback=self.OnTrace) 

    def OnDump(self): 
     '''Print the contents of the array''' 
     print self.arrayvar.get() 

    def OnTrace(self, varname, elementname, mode): 
     '''Show the new value in a label''' 
     self.labelvar.set("%s changed; new value='%s'" % (elementname, self.arrayvar[elementname])) 

class ArrayVar(tk.Variable): 
    '''A variable that works as a Tcl array variable''' 

    _default = {} 
    _elementvars = {} 

    def __del__(self): 
     self._tk.globalunsetvar(self._name) 
     for elementvar in self._elementvars: 
      del elementvar 


    def __setitem__(self, elementname, value): 
     if elementname not in self._elementvars: 
      v = ArrayElementVar(varname=self._name, elementname=elementname, master=self._master) 
      self._elementvars[elementname] = v 
     self._elementvars[elementname].set(value) 

    def __getitem__(self, name): 
     if name in self._elementvars: 
      return self._elementvars[name].get() 
     return None 

    def __call__(self, elementname): 
     '''Create a new StringVar as an element in the array''' 
     if elementname not in self._elementvars: 
      v = ArrayElementVar(varname=self._name, elementname=elementname, master=self._master) 
      self._elementvars[elementname] = v 
     return self._elementvars[elementname] 

    def set(self, dictvalue): 
     # this establishes the variable as an array 
     # as far as the Tcl interpreter is concerned 
     self._master.eval("array set {%s} {}" % self._name) 

     for (k, v) in dictvalue.iteritems(): 
      self._tk.call("array","set",self._name, k, v) 

    def get(self): 
     '''Return a dictionary that represents the Tcl array''' 
     value = {} 
     for (elementname, elementvar) in self._elementvars.iteritems(): 
      value[elementname] = elementvar.get() 
     return value 


class ArrayElementVar(tk.StringVar): 
    '''A StringVar that represents an element of an array''' 
    _default = "" 

    def __init__(self, varname, elementname, master): 
     self._master = master 
     self._tk = master.tk 
     self._name = "%s(%s)" % (varname, elementname) 
     self.set(self._default) 

    def __del__(self): 
     """Unset the variable in Tcl.""" 
     self._tk.globalunsetvar(self._name) 


if __name__ == "__main__": 
    app=MyApp() 
    app.wm_geometry("400x200") 
    app.mainloop() 
1

È tardi, ma ancora, qualcuno potrebbe trovare utile.

L'idea viene da @bryan Oakley's post

Se ho capito bene, il problema principale è quello di DeTech widget di entrata di. Per rilevarlo in spinbox, Checkbutton e Radiobutton è possibile utilizzare le opzioni command durante la creazione di widget.

Per catturare lo <onChange> nel widget Entry è possibile utilizzare l'approccio di Bryan utilizzando Tcl, che genera questo evento. Come ho detto, questa non è la mia soluzione, l'ho modificata solo leggermente per questo caso.

Così alla fine si potrebbe usare in questo modo:

import tkinter as tk 
from tkinter import ttk 

def generateOnChange(obj): 
     obj.tk.eval(''' 
      proc widget_proxy {widget widget_command args} { 

       # call the real tk widget command with the real args 
       set result [uplevel [linsert $args 0 $widget_command]] 

       # generate the event for certain types of commands 
       if {([lindex $args 0] in {insert replace delete}) || 
        ([lrange $args 0 2] == {mark set insert}) || 
        ([lrange $args 0 1] == {xview moveto}) || 
        ([lrange $args 0 1] == {xview scroll}) || 
        ([lrange $args 0 1] == {yview moveto}) || 
        ([lrange $args 0 1] == {yview scroll})} { 

        event generate $widget <<Change>> -when tail 
       } 

       # return the result from the real widget command 
       return $result 
      } 
      ''') 
     obj.tk.eval(''' 
      rename {widget} _{widget} 
      interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget} 
     '''.format(widget=str(obj))) 

def onEntryChanged(event = None): 
    print("Entry changed") 

def onCheckChanged(event = None): 
    print("Check button changed") 

def onSpinboxChanged(event = None): 
    print("Spinbox changed") 

def onRadioChanged(event = None): 
    print("Radio changed") 

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

    frame = tk.Frame(root, width=400, height=400) 

    entry = tk.Entry(frame, width=30) 
    entry.grid(row=0, column=0) 
    generateOnChange(entry) 
    entry.bind('<<Change>>', onEntryChanged) 

    checkbutton = tk.Checkbutton(frame, command=onCheckChanged) 
    checkbutton.grid(row=1, column=0) 

    spinbox = tk.Spinbox(frame, width=100, from_=1.0, to=100.0, command=onSpinboxChanged) 
    spinbox.grid(row=2, column=0) 


    phone = tk.StringVar() 
    home = ttk.Radiobutton(frame, text='Home', variable=phone, value='home', command=onRadioChanged) 
    home.grid(row=3, column=0, sticky=tk.W) 
    office = ttk.Radiobutton(frame, text='Office', variable=phone, value='office', command=onRadioChanged) 
    office.grid(row=3, column=0, sticky=tk.E) 

    frame.pack()  
    root.mainloop() 

Naturalmente modificarlo per creare callback diverso per un sacco di istanze (come lei ha ricordato nella questione) è facile ora.

Spero che qualcuno lo trovi utile.

+0

Interessante. Solo un piccolo add-on, quei widget che supportano la parola chiave 'comando', questo può essere impostato non solo durante la creazione, ma anche dopo con 'widget.config (comando = ...)' – norok2

5

Penso che il metodo corretto sia quello di utilizzare trace su una variabile tkinter che è stata assegnata a un widget.

Ad esempio ...

import tkinter 

root = tkinter.Tk() 
myvar = tkinter.StringVar() 
myvar.set('') 
mywidget = tkinter.Entry(root,textvariable=myvar,width=10) 
mywidget.pack() 

def oddblue(a,b,c): 
    if len(myvar.get())%2 == 0: 
     mywidget.config(bg='red') 
    else: 
     mywidget.config(bg-'blue') 
    mywidget.update_idletasks() 

myvar.trace('w',oddblue) 

root.mainloop() 

Il w in traccia racconta tkinter ogni volta che qualcuno scrive (aggiornamenti) La variabile, che sarebbe accaduto ogni volta che qualcuno ha scritto qualcosa nel widget Entry, fare oddblue. La traccia passa sempre tre valori a qualsiasi funzione che hai elencato, quindi dovrai aspettarli nella tua funzione, quindi a,b,c. Di solito non faccio nulla con loro, dato che tutto ciò di cui ho bisogno è definito localmente comunque. Da quello che posso dire a è l'oggetto variabile, b è vuoto (non è sicuro il motivo) e c è la modalità traccia (ad esempio w).

For more info on tkinter variables check this out.

Problemi correlati