簡體   English   中英

從 TKinter 小部件檢索/取回命令回調 function

[英]Retrieve/get back command callback function from TKinter widget

我(出於一些精心設置的原因)試圖從 tkinter 小部件中檢索實際command回調 function,例如為按鈕b設置回調

import tkinter as tk
root = tk.Tk()
b = tk.Button(root, text='btn', command=lambda:print('foo'))

兩個都

b['command']
b.cget('command')

我認為兩者都等同於

b.tk.call(b._w, 'cget', '-command')

只會返回類似"2277504761920<lambda\>"的字符串,而不是實際的命令 function。有沒有辦法獲得實際的回調 function?

看着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

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

我們得到 tkinter 將 function 包裝在CallWrapper class 中。 這意味着如果我們獲得所有CallWrapper對象,我們就可以恢復 function。 使用@hussic 的猴子建議使用更易於使用的 class 修補CallWrapper class,我們可以輕松獲取所有CallWrapper對象。

這是我根據@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()

正如@hussic 在評論中所說,存在列表( tk.call_wappers )僅被附加到的問題。 如果您有一個.after tkinter 循環,則問題將很明顯,因為每次.after被稱為 object 將被添加到列表中。 要解決此問題,您可能需要使用tk.call_wappers.clear()手動清除列表。 我將其更改為使用__slots__功能以確保它不會占用大量空間,但這並不能解決問題。

這是一個更復雜的解決方案。 它修補Misc._registerMisc.deletecommandMisc.destroy以從dict tkinterfuncs 中刪除值。 在這個例子中,有很多 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()

我無法想象任何情況,我完全不確定這是否能回答您的問題,但它可能等同於您正在尋找的內容:


按鈕的invoke方法似乎與我相當。 所以解決方案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()

如果這不是您要找的,您可以在 tcl 級別調用 function。 解決方案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()

解決方案 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. 該包裝器包含對 python function 的引用以及對小部件的引用。 要獲取小部件的回調,您可以遍歷包裝器的實例以取回原始 function。

例如,這樣的事情可能會起作用:

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

然后就可以直接調用這個function的返回值了。 考慮以下代碼塊:

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

當我單擊任一按鈕時,我會在控制台 output 中看到它,這證明get_callback方法返回一個可調用對象。

button1: <function do_something at 0x103386040> type: <class 'function'>
button2: <function <lambda> at 0x103419700> type: <class 'function'>

按鈕是一個 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

我一直在尋找同樣的問題,但我找不到任何好的答案然后我創建了我的實際上這很容易

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM