[英]Retrieve/get back command callback function from TKinter widget
I am (for some elaborate setup reasons) trying to retrieve the actual command
callback function from tkinter widgets, for example setting up a callback for a button b
我(出于一些精心设置的原因)试图从 tkinter 小部件中检索实际
command
回调 function,例如为按钮b
设置回调
import tkinter as tk
root = tk.Tk()
b = tk.Button(root, text='btn', command=lambda:print('foo'))
both两个都
b['command']
b.cget('command')
which I think both are equivalent to我认为两者都等同于
b.tk.call(b._w, 'cget', '-command')
will only return a string like "2277504761920<lambda\>"
and not the actual command function. Is there a way to get the actual callback function?只会返回类似
"2277504761920<lambda\>"
的字符串,而不是实际的命令 function。有没有办法获得实际的回调 function?
Looking at tkinter.__init__.py
:看着
tkinter.__init__.py
:
class BaseWidget:
...
def _register(self, func, subst=None, needcleanup=1):
"""Return a newly created Tcl function. If this
function is called, the Python function FUNC will
be executed. An optional function SUBST can
be given which will be executed before FUNC."""
f = CallWrapper(func, subst, self).__call__
name = repr(id(f))
try:
func = func.__func__
except AttributeError:
pass
try:
name = name + func.__name__
except AttributeError:
pass
self.tk.createcommand(name, f)
if needcleanup:
if self._tclCommands is None:
self._tclCommands = []
self._tclCommands.append(name)
return name
and和
class CallWrapper:
"""Internal class. Stores function to call when some user
defined Tcl function is called e.g. after an event occurred."""
def __init__(self, func, subst, widget):
"""Store FUNC, SUBST and WIDGET as members."""
self.func = func
self.subst = subst
self.widget = widget
def __call__(self, *args):
"""Apply first function SUBST to arguments, than FUNC."""
try:
if self.subst:
args = self.subst(*args)
return self.func(*args)
except SystemExit:
raise
except:
self.widget._report_exception()
We get that tkinter wraps the function in the CallWrapper
class.我们得到 tkinter 将 function 包装在
CallWrapper
class 中。 That means that if we get all of the CallWrapper
objects we can recover the function.这意味着如果我们获得所有
CallWrapper
对象,我们就可以恢复 function。 Using @hussic's suggestion of monkey patching the CallWrapper
class with a class that is easier to work with, we can easily get all of the CallWrapper
objects.使用@hussic 的猴子建议使用更易于使用的 class 修补
CallWrapper
class,我们可以轻松获取所有CallWrapper
对象。
This is my solution implemented with @hussic's suggestion:这是我根据@hussic 的建议实施的解决方案:
import tkinter as tk
tk.call_wappers = [] # A list of all of the `MyCallWrapper` objects
class MyCallWrapper:
__slots__ = ("func", "subst", "__call__")
def __init__(self, func, subst, widget):
# We aren't going to use `widget` because that can take space
# and we have a memory leak problem
self.func = func
self.subst = subst
# These are the 2 lines I added:
# First one appends this object to the list defined up there
# the second one uses lambda because python can be tricky if you
# use `id(<object>.<function>)`.
tk.call_wappers.append(self)
self.__call__ = lambda *args: self.call(*args)
def call(self, *args):
"""Apply first function SUBST to arguments, than FUNC."""
try:
if self.subst:
args = self.subst(*args)
return self.func(*args)
except SystemExit:
raise
except:
if tk._default_root is None:
raise
else:
tk._default_root._report_exception()
tk.CallWrapper = MyCallWrapper # Monkey patch tkinter
# If we are going to monkey patch `tk.CallWrapper` why not also `tk.getcommand`?
def getcommand(name):
for call_wapper in tk.call_wappers:
candidate_name = repr(id(call_wapper.__call__))
if name.startswith(candidate_name):
return call_wapper.func
return None
tk.getcommand = getcommand
# This is the testing code:
def myfunction():
print("Hi")
root = tk.Tk()
button = tk.Button(root, text="Click me", command=myfunction)
button.pack()
commandname = button.cget("command")
# This is how we are going to get the function into our variable:
myfunction_from_button = tk.getcommand(commandname)
print(myfunction_from_button)
root.mainloop()
As @hussic said in the comments there is a problem that the list ( tk.call_wappers
) is only being appended to.正如@hussic 在评论中所说,存在列表(
tk.call_wappers
)仅被附加到的问题。 THe problem will be apparent if you have a .after
tkinter loop as each time .after
is called an object will be added to the list.如果您有一个
.after
tkinter 循环,则问题将很明显,因为每次.after
被称为 object 将被添加到列表中。 To fix this you might want to manually clear the list using tk.call_wappers.clear()
.要解决此问题,您可能需要使用
tk.call_wappers.clear()
手动清除列表。 I changed it to use the __slots__
feature to make sure that it doesn't take a lot of space but that doesn't solve the problem.我将其更改为使用
__slots__
功能以确保它不会占用大量空间,但这并不能解决问题。
This is a more complex solution.这是一个更复杂的解决方案。 It patches
Misc._register
, Misc.deletecommand
and Misc.destroy
to delete values from dict
tkinterfuncs.它修补
Misc._register
、 Misc.deletecommand
和Misc.destroy
以从dict
tkinterfuncs 中删除值。 In this example there are many print to check that values are added and removed from the dict.在这个例子中,有很多 print 来检查是否在字典中添加和删除了值。
import tkinter as tk
tk.tkinterfuncs = {} # name: func
def registertkinterfunc(name, func):
"""Register name in tkinterfuncs."""
# print('registered', name, func)
tk.tkinterfuncs[name] = func
return name
def deletetkinterfunc(name):
"""Delete a registered func from tkinterfuncs."""
# some funcs ('tkerror', 'exit') are registered outside Misc._register
if name in tk.tkinterfuncs:
del tk.tkinterfuncs[name]
# print('delete', name, 'tkinterfuncs len:', len(tkinterfuncs))
def _register(self, func, subst=None, needcleanup=1):
"""Return a newly created Tcl function. If this
function is called, the Python function FUNC will
be executed. An optional function SUBST can
be given which will be executed before FUNC."""
name = original_register(self, func, subst, needcleanup)
return registertkinterfunc(name, func)
def deletecommand(self, name):
"""Internal function.
Delete the Tcl command provided in NAME."""
original_deletecommand(self, name)
deletetkinterfunc(name)
def destroy(self):
"""
Delete all Tcl commands created for
this widget in the Tcl interpreter.
"""
if self._tclCommands is not None:
for name in self._tclCommands:
# print('- Tkinter: deleted command', name)
self.tk.deletecommand(name)
deletetkinterfunc(name)
self._tclCommands = None
def getcommand(self, name):
"""
Gets the command from the name.
"""
return tk.tkinterfuncs[name]
original_register = tk.Misc.register
tk.Misc._register = tk.Misc.register = _register
original_deletecommand = tk.Misc.deletecommand
tk.Misc.deletecommand = deletecommand
tk.Misc.destroy = destroy
tk.Misc.getcommand = getcommand
if __name__ == '__main__':
def f():
root.after(500, f)
root = tk.Tk()
root.after(500, f)
but1 = tk.Button(root, text='button1', command=f)
but1.pack()
but2 = tk.Button(root, text='button2', command=f)
but2.pack()
but3 = tk.Button(root, text='button3', command=lambda: print(3))
but3.pack()
print(root.getcommand(but1['command']))
print(root.getcommand(but2['command']))
print(root.getcommand(but3['command']))
but3['command'] = f
print(root.getcommand(but3['command']))
root.mainloop()
I cannot imagine any case and Im not sure at all if this answers your question but it maybe equivalent for what you are looking for:我无法想象任何情况,我完全不确定这是否能回答您的问题,但它可能等同于您正在寻找的内容:
The invoke
method of the button seems pretty equivalent to me.按钮的
invoke
方法似乎与我相当。 So solution-1 would be:所以解决方案1将是:
import tkinter as tk
def hi():
print('hello')
root = tk.Tk()
b = tk.Button(root, text='test', command=hi)
b.pack()
cmd = b.invoke
#cmd = lambda :b._do('invoke')
root.mainloop()
If this isnt what you looking for you could call the function in tcl level.如果这不是您要找的,您可以在 tcl 级别调用 function。 Solution-2 :
解决方案2 :
import tkinter as tk
def hi():
print('hello')
root = tk.Tk()
b = tk.Button(root, text='test', command=hi)
b.pack()
cmd = lambda :root.tk.call(b['command'])
#cmd= lambda :root.tk.eval(b['command'])
cmd()
root.mainloop()
Solution 3 , would be to return
your function by invoke
:解决方案 3 ,将通过
invoke
return
您的 function:
import tkinter as tk
def hi():
print('hello')
return hi
root = tk.Tk()
b = tk.Button(root, text='test', command=hi)
b.pack()
cmd = b.invoke()
print(cmd) #still a string but comparable
root.mainloop()
When you assign a command to a widget, or bind a function to an event, the python function is wrapped in a tkinter.CallWrapper
object. When you assign a command to a widget, or bind a function to an event, the python function is wrapped in a
tkinter.CallWrapper
object. That wrapper contains a reference to the python function along with a reference to the widget.该包装器包含对 python function 的引用以及对小部件的引用。 To get a callback for a widget you can iterate over the instances of the wrapper in order to get back the original function.
要获取小部件的回调,您可以遍历包装器的实例以取回原始 function。
For example, something like this might work:例如,这样的事情可能会起作用:
import tkinter as tk
import gc
def get_callback(widget):
for obj in gc.get_objects():
if isinstance(obj, tk.CallWrapper) and obj.widget == widget:
return obj.func
return None
You can then directly call the return value of this function.然后就可以直接调用这个function的返回值了。 Consider the following block of code:
考虑以下代码块:
import tkinter as tk
import gc
def get_callback(widget):
for obj in gc.get_objects():
if isinstance(obj, tk.CallWrapper) and obj.widget == widget:
return obj.func
def do_something():
print(f"button1: {get_callback(button1)} type: {type(get_callback(button1))}")
print(f"button2: {get_callback(button2)} type: {type(get_callback(button2))}")
root = tk.Tk()
button1 = tk.Button(root, text="do_something", command=do_something)
button2 = tk.Button(root, text="lambda", command=lambda: do_something())
button1.pack(padx=20, pady=20)
button2.pack(padx=20, pady=20)
root.mainloop()
When I click either button, I see this in the console output which proves that the get_callback
method returns a callable.当我单击任一按钮时,我会在控制台 output 中看到它,这证明
get_callback
方法返回一个可调用对象。
button1: <function do_something at 0x103386040> type: <class 'function'>
button2: <function <lambda> at 0x103419700> type: <class 'function'>
Button is a object you can assign attributes just define your function outside the button and assign the function ass a attribute按钮是一个 object 你可以分配属性只需在按钮外定义你的 function 并为 function 分配一个属性
func_print = lambda: print("nice")
x = Button(..., command=func_print)
x.my_func = func_print
def something():
x.my_func()
something()
>>> nice
I was looking same problem but I could not find any nice answer then I created mine actually it is very easy我一直在寻找同样的问题,但我找不到任何好的答案然后我创建了我的实际上这很容易
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.