回调方法的工厂 - Python TKinter
我正在写一个测试应用程序,用来模拟PIO线路。我有一个非常简单的Python/Tk图形界面应用。通过按下数字键1到8来模拟PIO引脚1到8。按下键 = PIO高电平,松开键 = PIO低电平。我需要这个应用的目的不是问题所在。我在尝试使用工厂模式来创建按键回调函数时,走进了一个复杂的思路。
这里是一些简化的代码:
#!usr/bin/env python
"""
Python + Tk GUI interface to simulate a 8 Pio lines.
"""
from Tkinter import *
def cb_factory(numberic_key):
"""
Return a call back function for a specific keyboard numeric key (0-9)
"""
def cb( self, event, key=numberic_key ):
bit_val = 1<<numberic_key-1
if int(event.type) == 2 and not (bit_val & self.bitfield):
self.bitfield |= bit_val
self.message("Key %d Down" % key)
elif int(event.type) == 3 and (bit_val & self.bitfield):
self.bitfield &= (~bit_val & 0xFF)
self.message("Key %d Up" % key)
else:
# Key repeat
return
print hex(self.bitfield)
self.display_bitfield()
return cb
class App( Frame ):
"""
Main TK App class
"""
cb1 = cb_factory(1)
cb2 = cb_factory(2)
cb3 = cb_factory(3)
cb4 = cb_factory(4)
cb5 = cb_factory(5)
cb6 = cb_factory(6)
cb7 = cb_factory(7)
cb8 = cb_factory(8)
def __init__(self, parent):
"Init"
self.parent = parent
self.bitfield = 0x00
Frame.__init__(self, parent)
self.messages = StringVar()
self.messages.set("Initialised")
Label( parent, bd=1,
relief=SUNKEN,
anchor=W,
textvariable=self.messages,
text="Testing" ).pack(fill=X)
self.bf_label = StringVar()
self.bf_label.set("0 0 0 0 0 0 0 0")
Label( parent, bd=1,
relief=SUNKEN,
anchor=W,
textvariable=self.bf_label,
text="Testing" ).pack(fill=X)
# This Doesn't work! Get a traceback saying 'cb' expected 2 arguements
# but only got 1?
#
# for x in xrange(1,9):
# cb = self.cb_factory(x)
# self.parent.bind("<KeyPress-%d>" % x, cb)
# self.parent.bind("<KeyRelease-%d>" % x, cb)
self.parent.bind("<KeyPress-1>", self.cb1)
self.parent.bind("<KeyRelease-1>", self.cb1)
self.parent.bind("<KeyPress-2>", self.cb2)
self.parent.bind("<KeyRelease-2>", self.cb2)
self.parent.bind("<KeyPress-3>", self.cb3)
self.parent.bind("<KeyRelease-3>", self.cb3)
self.parent.bind("<KeyPress-4>", self.cb4)
self.parent.bind("<KeyRelease-4>", self.cb4)
self.parent.bind("<KeyPress-5>", self.cb5)
self.parent.bind("<KeyRelease-5>", self.cb5)
self.parent.bind("<KeyPress-6>", self.cb6)
self.parent.bind("<KeyRelease-6>", self.cb6)
self.parent.bind("<KeyPress-7>", self.cb7)
self.parent.bind("<KeyRelease-7>", self.cb7)
self.parent.bind("<KeyPress-8>", self.cb8)
self.parent.bind("<KeyRelease-8>", self.cb8)
def display_bitfield(self):
"""
Display the PIO lines (1 for on, 0 for off)
"""
bin_lst = []
for x in xrange(8):
bit = 1 << x
if bit & self.bitfield:
bin_lst.append("1")
else:
bin_lst.append("0")
bin_lst.reverse()
bin_str = " ".join( bin_lst )
self.bf_label.set( bin_str )
def message( self, msg_txt ):
"set"
self.messages.set( msg_txt )
def cb_factory(self, numberic_key ):
"""
Return a call back function for a specific keyboard numeric key (0-9)
"""
def cb( self, event, key=numberic_key ):
bit_val = 1<<numberic_key-1
if int(event.type) == 2:
self.bitfield |= bit_val
self.message("Key %d Down" % key)
else:
self.bitfield &= (~bit_val & 0xFF)
self.message("Key %d Up" % key)
print hex(self.bitfield)
self.display_bitfield()
return cb
##########################################################################
if __name__ == "__main__":
root = Tk()
root.title("PIO Test")
theApp = App( root )
root.mainloop()
我终于让某种方法工厂可以工作,用于回调,但我觉得效果不是很好。
所以我想问,能不能有一个类方法工厂,能够像我尝试的那样生成类方法(请看注释掉的代码和App类中的cb_factory()方法)?
备注:是的,我知道这个应用一次最多只能按下4个键,但这对我来说已经足够了。
3 个回答
这是修改过的代码,考虑到了SpliFF的回答。我觉得这个看起来好多了,但我还是不太明白它是怎么工作的。所以,想请教一下,有人能解释一下它是怎么运作的吗?
#!usr/bin/env python
"""
Python + Tk GUI interface to simulate a 8 Pio lines.
"""
from Tkinter import *
from pio_handler import *
class App( Frame ):
"""
Main TK App class
"""
def __init__(self, parent):
"Init"
self.parent = parent
self.bitfield = 0x00
Frame.__init__(self, parent)
self.messages = StringVar()
self.messages.set("Initialised")
Label( parent, bd=1,
relief=SUNKEN,
anchor=W,
textvariable=self.messages,
text="Testing" ).pack(fill=X)
self.bf_label = StringVar()
self.bf_label.set("0 0 0 0 0 0 0 0")
Label( parent, bd=1,
relief=SUNKEN,
anchor=W,
textvariable=self.bf_label,
text="Testing" ).pack(fill=X)
# This is the clever bit!
# Use a factory to assign a callback function for keys 1 to 8
for x in xrange(1,9):
cb = self.cb_factory(x)
self.parent.bind("<KeyPress-%d>" % x, cb)
self.parent.bind("<KeyRelease-%d>" % x, cb)
def display_bitfield(self):
"""
Display the PIO lines (1 for on, 0 for off)
"""
bin_lst = []
for x in xrange(8):
bit = 1 << x
if bit & self.bitfield:
bin_lst.append("1")
else:
bin_lst.append("0")
bin_lst.reverse()
bin_str = " ".join( bin_lst )
self.bf_label.set( bin_str )
def message( self, msg_txt ):
"set"
self.messages.set( msg_txt )
def cb_factory(self, numeric_key ):
"""
Return a call back function for a specific keyboard numeric key (0-9)
"""
def cb( event, key=numeric_key ):
bit_val = 1<<numeric_key-1
if int(event.type) == 2:
self.bitfield |= bit_val
self.message("Key %d Down" % key)
else:
self.bitfield &= (~bit_val & 0xFF)
self.message("Key %d Up" % key)
print hex(self.bitfield)
self.display_bitfield()
return cb
##########################################################################
if __name__ == "__main__":
root = Tk()
root.title("PIO Test")
theApp = App( root )
root.mainloop()
关于你后续的问题,我来解释一下。
我不太确定你不明白哪部分,但我猜可能是你对事件回调的工作原理还不太清楚?其实这很简单。Tk会在一个循环中运行,寻找各种事件,比如按键、鼠标点击等等。当你把一个回调函数绑定到某个事件上时,其实就是在告诉它,当这个事件发生时要调用你的函数,并把一个事件对象作为参数传给它。你可以通过这个事件对象获取更多关于发生了什么的细节。目前你是为18个按键事件(按下和释放1到9的键)分别创建了不同的回调函数。我觉得你可以把这个简化,把cb作为你类中的一个方法,因为事件对象几乎肯定会包含按键代码。
class:
def __init__(self):
for x in xrange(8):
self.parent.bind("<KeyPress-%d>" % x, self.keyaction)
self.parent.bind("<KeyRelease-%d>" % x, self.keyaction)
def keyaction(self, event):
key = event.keycode # attribute may have another name, I haven't checked tk docs
... do stuff ...
现在我们使用self.keyaction作为回调函数,它的第一个参数应该是self。它从事件对象中获取按键代码。这样,在创建函数时就不需要把这两个值“嵌入”到函数里了,因此不需要为每个按键实际创建不同的回调函数,这样代码也更容易理解。
cb函数需要两个东西,一个是'self',另一个是'event'。可能它只从绑定中得到了'event',而没有得到'self'。