簡體   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