繁体   English   中英

Tkinter框架内的迭代

[英]Iteration within tkinter Frame

我想使用tkinter GUI遍历字典(例如),并允许用户对其值进行操作。

例如,我的老板可能想遍历部门并选择要解雇的员工。 以下代码(大多数情况下)适用于第一个部门,但我不了解如何升级到下一个部门(下面的self.advance )。

这个问题是相关的,但是只是更新现有小部件的值。 每个部门的员工人数各不相同,因此我不仅可以更新姓名,还必须允许垂直滚动。

迭代发生在框架( innerFrame )内,UI的其余部分大部分是静态的。 我应该销毁并重新创建该innerFrame还是其中的所有小部件? 无论哪种方式,我如何前进到下一个迭代?

# Example data
emp = {'Sales':['Alice','Bryan','Cathy','Dave'],
       'Product':['Elizabeth','Frank','Gordon','Heather',
                  'Irene','John','Kristof','Lauren'],
       'Marketing':['Marvin'],
       'Accounting':['Nancy','Oscar','Peter','Quentin',
                     'Rebecca','Sally','Trevor','Umberto',
                     'Victoria','Wally','Xavier','Yolanda',
                     'Zeus']}

import tkinter as tk
from tkinter import messagebox

class bossWidget(tk.Frame):
    def __init__(self, root):
        """
        Scrollbar code credit to Bryan Oakley:
        https://stackoverflow.com/a/3092341/2573061
        """
        super().__init__()     
        self.canvas = tk.Canvas(root, borderwidth=0)
        self.frame  = tk.Frame(self.canvas)
        self.scroll = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.scroll.set)
        self.scroll.pack(side="right", fill="y")
        self.canvas.pack(side="left", fill="both", expand=True)
        self.canvas.create_window((4,4), window=self.frame, anchor="nw", 
                                  tags="self.frame")
        self.frame.bind("<Configure>", self.onFrameConfigure)
        self.initUI()        

    def initUI(self):
        """
        Creates the static UI content and the innerFrame that will hold the
        dynamic UI content (i.e., the Checkbuttons for the copies)
        """
        self.master.title("Boss Interface")
        self.instructLabel = tk.Label( self.frame, justify='left',
                                      text = "Select the employees you wish to FIRE")
        self.skipButton   = tk.Button( self.frame, text="Skip Department", 
                                      command = self.advance)
        self.deleteButton = tk.Button( self.frame, text="Fire employees", fg = 'red',
                                       command = self.executeSelection )
        self.quitButton   = tk.Button( self.frame, text="Exit", command=self.frame.quit)
        self.innerFrame   = tk.Frame( self.frame)
        self.instructLabel.pack(anchor = 'nw', padx=5,pady=5)
        self.innerFrame.pack(anchor='nw', padx=5, pady=20, expand=True)
        self.deleteButton.pack(side='left', padx=5,pady=5)
        self.skipButton.pack(side='left', padx=5,pady=5)
        self.quitButton.pack(side='left', padx=5,pady=5)

    def populateUI(self, title, labelList):
        """
        Creates and packs a list of Checkbuttons (cbList) into the innerFrame
        By default, the first Checkbutton will be unchecked, all others checked.
        You should help the boss out by passing the best employee at the head of the list
        """
        self.instructLabel.config(text = title + ' department:\nSelect the employees you wish to FIRE')
        self.cbList = [None] * len(labelList)
        self.cbValues = [tk.BooleanVar() for i in range(len(labelList))]
        for i in range(len(labelList)):
            self.cbList[i] = tk.Checkbutton( self.innerFrame, 
                                        text=labelList[i], 
                                        variable = self.cbValues[i])
            if i: self.cbList[i].select() # Check subsequent buttons by default
            self.cbList[i].pack(anchor = 'w', padx=5,pady=5) 

    def advance(self):
        # -------------> this is what I don't understand how to do <-------------
        self.innerFrame.destroy()  # this destroys everything! 
        # how to advance to next iteration?

    def querySelection(self):
        return [x.get() for x in self.cbValues]

    def executeSelection(self):
        fired = self.querySelection()

        if ( not all(x for x in fired) or 
             messagebox.askokcancel(message='Fire ALL the employees in the department?') 
           ):       
            for i in range(len(self.cbList)):
                empName = self.cbList[i].cget('text') 
                if fired[i]:
                    print('Sorry, '+ empName + ', but we have to let you go.', flush=True)
                else:    
                    print('See you Monday, '+ empName, flush=True)    
            self.advance()   

    def onFrameConfigure(self, event):
        """Reset the scroll region to encompass the inner frame"""
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

def main(): 
    root = tk.Tk()   
    root.geometry("400x250+250+100") # width x height + xOffset + yOffset 
    app = bossWidget(root)
    while emp:    
        department, employees = emp.popitem()
        app.pack(side='top',fill='both',expand=True)
        app.populateUI(title = department, labelList = employees)
        root.mainloop()
        try:
            root.destroy()
        except tk.TclError:
            pass # if run in my IDE, the root already is destroyed

if __name__ == '__main__':
    main()   

基本模式是每个帧都有一个类或一个函数。 这些类或函数中的每一个都创建一个框架,并将其所有小部件放置在该框架中。

然后,切换帧所需要做的就是删除当前帧,然后调用函数或对象来创建新帧。 就这么简单。

该站点上的一些示例:

这是对代码的简短修改,以处理更新解雇雇员的复选框和切换框架以显示部门中的新雇员的情况。 如果所有员工都被解雇了,我将无法推进工作。 还有一个小错误,但是我留给您找出。

这可能是很多清洁。 我只是不想重写您的所有代码。

   # Example data
    emp = [['Sales', ['Alice','Bryan','Cathy','Dave']],
           ['Product', ['Elizabeth','Frank','Gordon','Heather',
                      'Irene','John','Kristof','Lauren']],
           ['Marketing', ['Marvin']],
           ['Accounting', ['Nancy','Oscar','Peter','Quentin',
                         'Rebecca','Sally','Trevor','Umberto',
                         'Victoria','Wally','Xavier','Yolanda',
                         'Zeus']]]

    import tkinter as tk
    from tkinter import messagebox

    class bossWidget(tk.Frame):
        def __init__(self, root):
            """
            Scrollbar code credit to Bryan Oakley:
            https://stackoverflow.com/a/3092341/2573061
            """
            super().__init__()    

            self.cursor = 0

            self.canvas = tk.Canvas(root, borderwidth=0)
            self.frame  = tk.Frame(self.canvas)
            self.scroll = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
            self.canvas.configure(yscrollcommand=self.scroll.set)
            self.scroll.pack(side="right", fill="y")
            self.canvas.pack(side="left", fill="both", expand=True)
            self.canvas.create_window((4,4), window=self.frame, anchor="nw", 
                                      tags="self.frame")
            self.frame.bind("<Configure>", self.onFrameConfigure)
            self.initUI()        

        def initUI(self):
            """
            Creates the static UI content and the innerFrame that will hold the
            dynamic UI content (i.e., the Checkbuttons for the copies)
            """
            self.master.title("Boss Interface")
            self.instructLabel = tk.Label( self.frame, justify='left',
                                          text = "Select the employees you wish to FIRE")
            self.skipButton   = tk.Button( self.frame, text="Skip Department", 
                                          command = self.advance)
            self.deleteButton = tk.Button( self.frame, text="Fire employees", fg = 'red',
                                           command = self.executeSelection )
            self.quitButton   = tk.Button( self.frame, text="Exit", command=self.frame.quit)
            self.innerFrame = tk.Frame(self.frame)
            self.instructLabel.pack(anchor = 'nw', padx=5,pady=5)
            self.innerFrame.pack(anchor = 'nw', padx=5,pady=5)
            self.deleteButton.pack(side='left', padx=5,pady=5)
            self.skipButton.pack(side='left', padx=5,pady=5)
            self.quitButton.pack(side='left', padx=5,pady=5)

            self.populateUI(*self.get_populate_items())

        def get_populate_items(self):

            return (emp[self.cursor][0], emp[self.cursor][1])

        def populateUI(self, title, labelList):
            """
            Creates and packs a list of Checkbuttons (cbList) into the innerFrame
            By default, the first Checkbutton will be unchecked, all others checked.
            You should help the boss out by passing the best employee at the head of the list
            """
            for child in self.innerFrame.winfo_children():
                child.destroy()
            self.instructLabel.config(text = title + ' department:\nSelect the employees you wish to FIRE')
            self.cbList = [None] * len(labelList)
            self.cbValues = [tk.BooleanVar() for i in range(len(labelList))]
            for i in range(len(labelList)):
                self.cbList[i] = tk.Checkbutton( self.innerFrame, 
                                            text=labelList[i], 
                                            variable = self.cbValues[i])
                if i: self.cbList[i].select() # Check subsequent buttons by default
                self.cbList[i].pack(anchor = 'w', padx=5,pady=5) 

        def advance(self):

            if (self.cursor < len(emp) - 1):
                self.cursor += 1
            else:
                self.cursor  = 0
            self.populateUI(*self.get_populate_items())

        def querySelection(self):
            return [x.get() for x in self.cbValues]

        def executeSelection(self):
            fired = self.querySelection()

            if ( not all(x for x in fired) or 
                 messagebox.askokcancel(message='Fire ALL the employees in the department?') 
               ):       
                for i in range(len(self.cbList)):
                    empName = self.cbList[i].cget('text') 
                    if fired[i]:
                        emp[self.cursor][1].remove(empName)
                        print('Sorry, '+ empName + ', but we have to let you go.', flush=True)
                    else:    
                        print('See you Monday, '+ empName, flush=True) 
                self.populateUI(*self.get_populate_items())
                # self.advance()   

        def onFrameConfigure(self, event):
            """Reset the scroll region to encompass the inner frame"""
            self.canvas.configure(scrollregion=self.canvas.bbox("all"))

    def main(): 
        root = tk.Tk()   
        root.geometry("400x250+250+100") # width x height + xOffset + yOffset 
        app = bossWidget(root)
        root.mainloop()
        # while emp:    
        #     department, employees = emp.popitem()
        #     app.pack(side='top',fill='both',expand=True)
        #     app.populateUI(title = department, labelList = employees)
        #     root.mainloop()
        #     try:
        #         root.destroy()
        #     except tk.TclError:
        #         pass # if run in my IDE, the root already is destroyed

    if __name__ == '__main__':
        main()   

我接受了Pythonista的回答,但最终决定做以下事情:

  • UI构造函数将数据作为参数获取(也许比全局数据变量更好的做法)
  • UI填充器会首先删除任何现有标签(请参见接受的答案)
  • 然后,UI填充器弹出一条记录(如果剩余,则终止)
  • 执行按钮在执行其他任务后将调用UI填充器
  • 跳过按钮仅调用UI填充器(因此可以完全删除高级功能)

这就是我最后使用的。 正如Pythonista所说,这很混乱,但是我们所有人都必须从某个地方开始。

# Example data
emp = {'Sales':['Alice','Bryan','Cathy','Dave'],
       'Product':['Elizabeth','Frank','Gordon','Heather',
                  'Irene','John','Kristof','Lauren'],
       'Marketing':['Marvin'],
       'Accounting':['Nancy','Oscar','Peter','Quentin',
                     'Rebecca','Sally','Trevor','Umberto',
                     'Victoria','Wally','Xavier','Yolanda',
                     'Zeus']}

import tkinter as tk
from tkinter import messagebox

class bossWidget(tk.Frame):
    def __init__(self, root, data):
        """
        Scrollbar code credit to Bryan Oakley:
        https://stackoverflow.com/a/3092341/2573061
        """
        super().__init__()     
        self.canvas = tk.Canvas(root, borderwidth=0)
        self.frame  = tk.Frame(self.canvas)
        self.scroll = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.scroll.set)
        self.scroll.pack(side="right", fill="y")
        self.canvas.pack(side="left", fill="both", expand=True)
        self.canvas.create_window((4,4), window=self.frame, anchor="nw", 
                                  tags="self.frame")
        self.frame.bind("<Configure>", self.onFrameConfigure)
        self.data = data
        self.initUI()        

    def initUI(self):
        """
        Creates the static UI content and the innerFrame that will hold the
        dynamic UI content (i.e., the Checkbuttons for the copies)
        """
        self.master.title("Boss Interface")
        self.instructLabel = tk.Label( self.frame, justify='left',
                                      text = "Select the employees you wish to FIRE")
        self.skipButton   = tk.Button( self.frame, text="Skip Department", 
                                      command = self.populateUI)
        self.deleteButton = tk.Button( self.frame, text="Fire employees", fg = 'red',
                                       command = self.executeSelection )
        self.quitButton   = tk.Button( self.frame, text="Exit", command=self.frame.quit)
        self.innerFrame   = tk.Frame( self.frame)
        self.instructLabel.pack(anchor = 'nw', padx=5,pady=5)
        self.innerFrame.pack(anchor='nw', padx=5, pady=20, expand=True)
        self.deleteButton.pack(side='left', padx=5,pady=5)
        self.skipButton.pack(side='left', padx=5,pady=5)
        self.quitButton.pack(side='left', padx=5,pady=5)
        self.populateUI()

    def populateUI(self):
        """
        Creates and packs a list of Checkbuttons (cbList) into the innerFrame
        By default, the first Checkbutton will be unchecked, all others checked.
        You should help the boss out by passing the best employee at the head of the list
        """
        for child in self.innerFrame.winfo_children():
            child.destroy()
        try:
            title, labelList = self.data.popitem()
            self.instructLabel.config(text = title + ' department:\nSelect the employees you wish to FIRE')
            self.cbList = [None] * len(labelList)
            self.cbValues = [tk.BooleanVar() for i in range(len(labelList))]
            for i in range(len(labelList)):
                self.cbList[i] = tk.Checkbutton( self.innerFrame, 
                                            text=labelList[i], 
                                            variable = self.cbValues[i])
                if i: self.cbList[i].select() # Check subsequent buttons by default
                self.cbList[i].pack(anchor = 'w', padx=5,pady=5) 
        except KeyError:
            messagebox.showinfo("All done", "You've purged all the departments.  Good job, boss.")
            self.frame.quit()

    def querySelection(self):
        return [x.get() for x in self.cbValues]

    def executeSelection(self):
        fired = self.querySelection()

        if ( not all(x for x in fired) or 
             messagebox.askokcancel(message='Fire ALL the employees in the department?') 
           ):       
            for i in range(len(self.cbList)):
                empName = self.cbList[i].cget('text') 
                if fired[i]:
                    print('Sorry, '+ empName + ', but we have to let you go.', flush=True)
                else:    
                    print('See you Monday, '+ empName, flush=True)    
            self.populateUI()   

    def onFrameConfigure(self, event):
        """Reset the scroll region to encompass the inner frame"""
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

def main(): 
    root = tk.Tk()   
    root.geometry("400x250+250+100") # width x height + xOffset + yOffset 
    app = bossWidget(root, data=emp)
    app.mainloop()
    try:
        root.destroy()
    except tk.TclError:
        pass # if run in my IDE, the root already is destroyed

if __name__ == '__main__':
    main()   

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM