看完上个程序的执行结果, 我们肯定要问:
"这到底是咋了??!! 'buttonHandler'为啥在事件循环开始前就执行了!!"
原因是下面这句导致的:
self.button1 = Button(self.myContainer1,
command=self.buttonHandler(button_name, 1, 'Good stuff!'))
我们在这儿只是调用了buttonHandler这个函数, 而不是将它当作一个回调函数传给command. 这可不是我们想要的, 但我们确实是这么干了.
*注意一下* -->
* buttonHandler是一个函数对象, 可以作为回调函数来进行绑定.
* buttonHandler()(注意括号)却是一次实际的函数调用.
当下面这句:
self.button1 = Button(self.myContainer1,
command=self.buttonHandler(button_name, 1, 'Good stuff!'))
被执行时, 它实际是在发生一次'buttonHandler'的调用. buttonHandler被执行后, 打印出一条消息, 并返回调用的结果(在这种情况下是None对象). 这时按钮的'command'参数被绑定到调用返回的结果上. 简单的说就是command被绑定到了None对象上. 那就是为啥当你按按钮时, 啥也不发生的原因了.
*有解决办法么?*
这个...有啥解决方案没? 有啥办法可以参数化或重用一个事件处理器函数?
当然. 有很多技术可以解决. 其中一种是使用python内置的'lambda'函数, 另外还有一种叫做'currying'.
在本程序中, 我们主要讨论如何用lambda, 下个程序我们再来讲currying.
我不打算解决lambda和currying是如何工作的 -- 这太复杂了, 并且也跑题跑的太远了. 所以我打算将它们当成黑盒子来对待.
好了, 先来看看lambda.
*命令绑定*
最早, 我们认为下面的代码应该是可以工作的:
self.button1 = Button(self.myContainer1,
command=self.buttonHandler(button_name, 1, 'Good stuff!'))
... 但我们看到它并不能如我们所想的那样工作.
想让代码按我们说的做就得重写这行:
self.button1 = Button(self.myContainer1,
command=lambda
arg1=button_name, arg2=1, arg3='Good stuff!' :
self.buttonHandler(arg1, arg2, arg3)
)
*事件绑定*
嗯, 不错, lambda也可以用相同的办法来参数化事件绑定. 下面是原来的写法:
self.button1.bind('',
self.buttonHandler_a(event, button_name, 1, 'Good stuff!'))
(它不能工作, 因为没办法将event参数包含在参数列表中), 我们可以用lambda这样写:
self.button1.bind('',
lambda
event, arg1=button_name, arg2=1, arg3='Good stuff!' :
self.buttonHandler_a(event, arg1, arg2, arg3)
)
[注意这里'event'在这儿是一个参数 -- 它不是python的关键词或其他啥的. 这个例子使用'event'作为event参数, 也可以用'e'来表示event参数, 如果我们想的话, 也可以用'event_arg'来表示]
用lambda的一个好处是我们能(如果我们想的话)不传event参数. 如果我们不传入event参数, 可以直接调用self.buttonHandler, 而不是间接的通过调用self.buttonHandler_a函数来实现.
为了展示这项技术, 我们会将button2的事件绑定代码与button1的不写成一样的. 就像下面这样:
self.button2.bind('',
lambda
event, arg1=button_name, arg2=2, arg3='Bad stuff!' :
self.buttonHandler(arg1, arg2, arg3)
)
*代码示例*
当程序运行时, 它就会如我们想的那样正常工作了.
注意当你按TAB, 你可以将焦点在OK和CANCEL之间切换.
你应该实验一下通过按键来触发OK按钮的操作. 当你通过按触发OK按钮时, buttonHandler_a会被调用, 你会看到打印出传进来的事件的信息.
在任何情况, 无论你是用鼠标还是鼠标的键来触发按钮操作, 它都会很好的工作.
- from Tkinter import *
-
-
class MyApp:
-
def __init__(self, parent):
-
self.myParent = parent
-
self.myContainer1 = Frame(parent)
-
self.myContainer1.pack()
-
-
#------------------ BUTTON #1 ------------------------------------
-
button_name = "OK"
-
-
# command binding
-
self.button1 = Button(self.myContainer1,
-
command = lambda
-
arg1=button_name, arg2=1, arg3="Good stuff!" :
-
self.buttonHandler(arg1, arg2, arg3)
-
)
-
-
# event binding -- passing the event as an argument
-
self.button1.bind("",
-
lambda
-
event, arg1=button_name, arg2=1, arg3="Good stuff!" :
-
self.buttonHandler_a(event, arg1, arg2, arg3)
-
)
-
-
self.button1.configure(text=button_name, background="green")
-
self.button1.pack(side=LEFT)
-
self.button1.focus_force() # Put keyboard focus on button1
-
-
#------------------ BUTTON #2 ------------------------------------
-
button_name = "Cancel"
-
-
# command binding
-
self.button2 = Button(self.myContainer1,
-
command = lambda
-
arg1=button_name, arg2=2, arg3="Bad stuff!":
-
self.buttonHandler(arg1, arg2, arg3)
-
)
-
-
# event binding -- without passing the event as an argument
-
self.button2.bind("",
-
lambda
-
event, arg1=button_name, arg2=2, arg3="Bad stuff!" :
-
self.buttonHandler(arg1, arg2, arg3)
-
)
-
-
self.button2.configure(text=button_name, background="red")
-
self.button2.pack(side=LEFT)
-
-
-
def buttonHandler(self, argument1, argument2, argument3):
-
print " buttonHandler routine received arguments:" \
-
, argument1.ljust(8), argument2, argument3
-
-
def buttonHandler_a(self, event, argument1, argument2, argument3):
-
print "buttonHandler_a received event", event
-
self.buttonHandler(argument1, argument2, argument3)
-
-
-
print "\n"*100 # clear the screen
-
print "Starting program tt078."
-
root = Tk()
-
myapp = MyApp(root)
-
print "Ready to start executing the event loop."
-
root.mainloop()
-
print "Finished executing the event loop."