2010-10-06 25 views
9

Tôi đang sử dụng Python và Tkinter và tôi muốn tương đương với onchange sự kiện từ các bộ công cụ/ngôn ngữ khác. Tôi muốn chạy mã bất cứ khi nào người dùng cập nhật trạng thái của một số tiện ích con.Làm thế nào để chạy một mã bất cứ khi nào một giá trị widget Tkinter thay đổi?

Trong trường hợp của tôi, tôi có nhiều tiện ích Entry, Checkbutton, SpinboxRadiobutton. Bất cứ khi nào một trong những thay đổi này, tôi muốn chạy mã của tôi (trong trường hợp này, hãy cập nhật một hộp văn bản trên bảng điều khiển khác).

(chỉ cần nhớ rằng người dùng có thể tương tác với những vật dụng bằng cách sử dụng chuột hoặc bàn phím, và thậm chí sử dụng tổ hợp phím Ctrl + V để dán văn bản)

Trả lời

0

Cho đến nay, tôi đã không gặp phải bất cứ điều gì tương đương với onChange trong Tkinter. Tiện ích có thể bị ràng buộc với các sự kiện khác nhau và tôi đã thực hiện điều đó một cách rõ ràng.

+2

Cách tiếp cận này đòi hỏi sự chú ý cẩn thận đến từng chi tiết, hoặc bạn có thể tạo ra các ràng buộc điều đó xảy ra * trước * widget thực sự thay đổi chứ không phải là ngay sau đó. –

7

Làm thế nào tôi có thể giải quyết việc này trong Tcl sẽ là để đảm bảo rằng các checkbutton, spinbox và radiobutton widget đều gắn liền với một biến mảng. Sau đó tôi sẽ đặt một dấu vết trên mảng mà sẽ gây ra một hàm được gọi mỗi khi biến đó được viết. Tcl làm điều này tầm thường.

Thật không may Tkinter không hỗ trợ làm việc với các mảng Tcl. May mắn thay, nó khá dễ hack. Nếu bạn thích mạo hiểm, hãy thử đoạn mã sau.

Từ bộ phận tiết lộ đầy đủ: Tôi đã ném điều này cùng nhau sáng nay trong khoảng nửa giờ. Tôi đã không thực sự sử dụng kỹ thuật này trong bất kỳ mã thực sự nào. Tuy nhiên, tôi không thể cưỡng lại thách thức, để tìm ra cách sử dụng các mảng với 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

Khá muộn, nhưng ai đó có thể thấy nó hữu ích.

Toàn bộ ý tưởng xuất phát từ @bryan Oakley's post

Nếu tôi hiểu tốt, vấn đề chính là để Detech nhập phụ tùng của. Để phát hiện nó trong spinbox, CheckbuttonRadiobutton bạn có thể sử dụng các tùy chọn command khi tạo tiện ích con.

Để bắt tiện ích <onChange> trong Entry, bạn có thể sử dụng phương pháp tiếp cận của Bryan bằng Tcl, tạo sự kiện này. Như tôi đã nói, đây không phải là giải pháp của tôi, tôi chỉ thay đổi nó một chút trong trường hợp này.

Vì vậy, cuối cùng bạn có thể sử dụng nó như thế này:

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() 

Tất nhiên sửa đổi nó để tạo ra gọi lại khác nhau cho nhiều trường hợp (như bạn đề cập trong câu hỏi) rất dễ dàng ngay bây giờ.

Tôi hy vọng ai đó sẽ thấy nó hữu ích.

+0

Thú vị. Chỉ cần một chút add-on, những widget hỗ trợ từ khóa 'command', điều này có thể được thiết lập không chỉ trong quá trình tạo, mà còn với 'widget.config (command = ...)' – norok2

5

Tôi nghĩ phương pháp đúng là sử dụng trace trên biến tkinter đã được gán cho tiện ích.

Ví dụ ...

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() 

Các w trong dấu vết nói Tkinter bất cứ khi nào ai đó viết (cập nhật) biến, mà sẽ xảy ra mỗi khi ai đó đã viết một cái gì đó trong widget Entry sau cùng, làm oddblue. Dấu vết luôn chuyển ba giá trị cho bất kỳ hàm nào bạn đã liệt kê, vì vậy bạn sẽ cần phải mong đợi chúng trong hàm của bạn, do đó a,b,c. Tôi thường không làm gì với họ vì mọi thứ tôi cần đều được định nghĩa cục bộ. Từ những gì tôi có thể nói a là đối tượng biến, b trống (không chắc chắn lý do) và c là chế độ theo dõi (ví dụ: w).

For more info on tkinter variables check this out.

Các vấn đề liên quan