将OptionMenu传递给回调(或检索对已使用窗口小部件的引用)

时间:2017-08-04 20:24:53

标签: python-3.x tkinter optionmenu

我正在处理一个(顶层)GUI,它由一个包含8个OptionMenus的数组组成,每个OptionMenus包含相同的选项列表。目前,我使用for循环构建这些小部件,我save references in a dictionary。所有OptionMenus都链接到相同的(lambda)回调函数。

保持实用:选项列表中的项目表示一系列处理步骤,用户可以更改进程的顺序。

其中一个列表的更改将导致一个进程执行两次,而一个进程根本不执行。但是,我希望每个项目只出现一次。因此,每个用户输入应该伴随第二个OptionMenu更改。

例如:初始订单1-2-3 - >用户更改第二个进程:1-3-3,自动更正为:1-3-2,其中每个进程仅再次执行一次。

根据我的理解,如果我引用了刚刚改变的OptionMenu(来自回调函数),我只能让它工作。我正在调查passing the widget into the callback。示例代码是尝试实现第二个建议的方法,但结果不是我所期望的。

事实是,OptionMenu小部件的行为似乎与其他小部件有所不同。 OptionMenu does not allow用于重新定义命令功能。无论我使用命令函数输入什么输入,回调似乎只检索OptionMenu选择,这对我来说不足以确定我的流程订单。

建议会更加渺茫!

import tkinter as tk

class Application(tk.Frame):

    def __init__(self, master=None):
        super().__init__(master)
        self.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
        self.create_widgets()


    def create_widgets(self):
        self.active_procs = ['proc 1','proc 2','proc 3','proc 4',
                             'proc 5','proc 6','proc 7','proc 8']
        itemnr, widgets = dict(), dict()
        for index in range(8):
            name_construct = 'nr' + str(index)
            itemnr[name_construct] = tk.StringVar(root)
            itemnr[name_construct].set(self.active_procs[index])
            widgets[name_construct] = tk.OptionMenu(self, itemnr[name_construct], *self.active_procs,
                                                    command=lambda widget=name_construct:
                                                    self.order_change(widget))
            widgets[name_construct].grid(row=index+2, column=2, columnspan=2,
                                         sticky="nwse", padx=10, pady=10)


    def order_change(self,widget):
        print(widget)


root = tk.Tk()
root.title("OptionMenu test")

app = Application(master=root)

root.mainloop()

2 个答案:

答案 0 :(得分:1)

OptionMenu会将新值传递给回调,因此您无需执行任何操作即可获取新值。这就是为什么你的widget值不是name_construct的值 - 传入的值会覆盖你在lambda中提供的默认值。

要解决此问题,您只需添加另一个参数,以便可以将name_construct的值传递给回调以与自动发送的值一起使用。

它看起来像这样:

widgets[name_construct] = tk.OptionMenu(..., command=lambda value, widget=name_construct: self.order_change(value, widget))
...
def order_change(self, value, widget):
    print(value, widget)

注意:OptionMenu实际上不是tkinter小部件。它只是一个便利功能,可以创建一个带有关联Menubutton的标准Menu。然后,它会在菜单上为每个选项创建一个项目,并将其与StringVar

联系在一起

您可以相当轻松地获得完全相同的行为。这样做可以在选择时更改菜单中每个项目的功能。

答案 1 :(得分:0)

对于那些感兴趣的人,您可以在下面找到我如何获得我想要的小部件行为的示例代码。我采用了Bryan的建议来替换Menubutton / Menu组合的OptionMenu。我还使用this post在流程订单列表中查找重复的条目。

有关如何以更简洁的方式实现此功能的任何想法或建议,或如何使用不同的界面(例如拖放)获得相同的功能,都是值得欢迎的!

import tkinter as tk

class Application(tk.Frame):


    def __init__(self, master=None):
        super().__init__(master)
        self.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
        self.create_widgets()


    def create_widgets(self):
        # Assisting text
        l1 = tk.Label(self, text = "Data in", font=(None, 15))
        l1.grid(row=0, column=2)
        l2 = tk.Label(self, text = u'\N{BLACK DOWN-POINTING TRIANGLE}', font=(None, 15))
        l2.grid(row=1, column=2)
        l3 = tk.Label(self, text = "Data out", font=(None, 15))
        l3.grid(row=11, column=2)
        l4 = tk.Label(self, text = u'\N{BLACK DOWN-POINTING TRIANGLE}', font=(None, 15))
        l4.grid(row=10, column=2)

        # Process list
        self.active_procs = ['proc a','proc b','proc c','proc d',
                             'proc e','proc f','proc g','proc h']

        self.the_value, self.widgets, self.topmenu = dict(), dict(), dict()
        for index in range(8):
            name_construct = 'nr' + str(index)
            self.the_value[name_construct] = tk.StringVar(root)
            self.the_value[name_construct].set(self.active_procs[index])
            self.widgets[name_construct] = tk.Menubutton(self, textvariable=
                                            self.the_value[name_construct],
                                            indicatoron=True)
            self.topmenu[name_construct] = tk.Menu(self.widgets[name_construct],
                                            tearoff=False)
            self.widgets[name_construct].configure(menu=self.topmenu[name_construct])

            for proc in self.active_procs:
                self.topmenu[name_construct].add_radiobutton(label=proc, variable=
                                              self.the_value[name_construct],
                                              command=lambda proc=proc,
                                              widget=name_construct:
                                              self.order_change(proc,widget))
            self.widgets[name_construct].grid(row=index+2, column=2, columnspan=2,
                                              sticky="nwse", padx=10, pady=10)


    def order_change(self,proc,widget):

        # Get the index of the last changed Menubutton
        index_user_change = list(self.widgets.keys()).index(widget) 

        procs_order = [] # Current order from widgets
        for index in range(8):
            name_construct = 'nr' + str(index)
            procs_order.append(self.widgets[name_construct].cget("text"))

        # 1 change may lead to 1 double and 1 missing process
        doubles = self.list_duplicates_of(procs_order,proc) 

        if len(doubles) == 2: # If double processes are present... 
            doubles.remove(index_user_change) # ...remove user input, change the other
            missing_proc = str(set(self.active_procs)^set(procs_order)).strip('{"\'}')
            index_change_along = int(doubles[0])

            # Update references
            self.active_procs[index_user_change] = proc 
            self.active_procs[index_change_along] = missing_proc 

            # Update widgets
            name_c2 = 'nr'+str(index_change_along)
            self.the_value[name_c2].set(self.active_procs[index_change_along])
            self.widgets[name_c2].configure(text=missing_proc)


    def list_duplicates_of(self,seq,item):
        start_at = -1
        locs = []
        while True:
            try:
                loc = seq.index(item,start_at+1)
            except ValueError:
                break
            else:
                locs.append(loc)
                start_at = loc
        return locs    


root = tk.Tk()
root.title("OptionMenu test")

app = Application(master=root)

root.mainloop()