【问题标题】:How to create a set of tkinter widgets (for loop) and refer to every one of them individually?如何创建一组 tkinter 小部件(for 循环)并单独引用它们中的每一个?
【发布时间】:2019-08-30 03:30:11
【问题描述】:

我是老师。使用 Tkinter 和 Openpyxl,我需要编写一个图形用户界面,我可以在其中单击“.xlsx”文件中的学生列表。我想用一个复选按钮“链接”每个学生的名字,并在他/她的复选按钮处于活动状态时显示他/她的分数(保存在“.xlsx”文件中)。如何创建一组我以后可以单独引用的小部件/检查按钮?在下面的代码中,每个检查按钮都具有相同的名称!!!

# -*- coding: utf-8 -*-
from tkinter import *
import openpyxl

wb = openpyxl.load_workbook('marks.xlsx', data_only=True)
sheet = wb.active

names_and_rows = {}

for i in range(2, sheet.max_row + 1):
    name = sheet.cell(row=i, column=1).value
    names_and_rows[name] = i

root = Tk()
root.title("Student's marks")

students_names = Frame(root, bd=1, relief="solid")
students_names.pack(side="left")

student_marks = Frame(root, bd=1, relief="solid")
student_marks.pack(side="right")

message = Label(student_marks, text="You still haven't checked on any student's name")
message.pack()


def get_marks(v):
    marks = ""
    for i in range(2, sheet.max_column + 1):
        information = str(sheet.cell(row=1, column=i).value) + ": " + str(sheet.cell(row=v, column=i).value) + "\n"
        marks = marks + information
    if (v.get() == 1):
        message.config(text=marks)
    else:
        message.config(text="You still haven't checked on any student's name")


list_of_widgets = []

for k, v in names_and_rows.items():
    square = Checkbutton(students_names, variable=v, onvalue=1, offvalue=0, text=k, command=lambda: get_marks(v))
    list_of_widgets.append(square)
    square.pack()

root.mainloop()

Simplified Worksheet

Tkinter GUI

【问题讨论】:

    标签: python tkinter widget openpyxl


    【解决方案1】:

    我认为最好的方法是将每个小部件存储在一个列表中。比如:

    list_of_widgets = []
    for student in students:
        square = Checkbutton(root, variable=value, text=student)
        list_of_widgets.append(square)
        square.pack()
    

    虽然我也很确定您需要为每个变量分配一个新变量,并且您可能希望将其存储在列表中的元组中。也许这样的事情会更合适:

    list_of_widgets = []
    for student in students:
        value = IntVar()
        square = Checkbutton(root, variable=value, text=student)
        list_of_widgets.append((square, value)) # appending a tuple ()
        square.pack()
    

    您甚至可以包含其他信息,以便更轻松地将按钮与特定学生关联:

    list_of_widgets.append((student, square, value)) # appending a tuple ()
    

    然后你可以让一个线程永远循环,如果发生状态变化更新你的程序:

    import threading
    
    def check_for_changes():
        global list_of_widgets
        students_selected = []
        while True: # loop forever
            for item in list_of_widgets:
                if item[2].get() != 0 and item[0] not in students_selected:
                    students_selected.append(item)
                    # or some other code
    
    t = threading.Thread(target=check_for_changes)
    t.start()
    

    请注意,这只会添加,您需要其他逻辑来删除您取消选择的学生,但我通常是这样解决问题的!

    编辑

    您需要查看here 的帖子,因为您正在分配对变量的引用而不是变量本身,因此它总是会计算所有按钮的循环的最后一个值。我喜欢lambda i=i: self.open_this(i) 的答案,修改你的代码来实现它看起来更像:

    from tkinter import *
    import openpyxl
    from collections import OrderedDict
    
    wb = openpyxl.load_workbook('test.xlsx', data_only=True)
    sheet = wb.active
    
    names_and_rows = OrderedDict()
    
    for i in range(2, sheet.max_row + 1):
        name = sheet.cell(row=i, column=1).value
        names_and_rows[name] = i
    
    root = Tk()
    root.title("Student's marks")
    
    students_names = Frame(root, bd=1, relief="solid")
    students_names.pack(side="left")
    
    student_marks = Frame(root, bd=1, relief="solid")
    student_marks.pack(side="right")
    
    message = Label(student_marks, text="You still haven't checked on any student's name")
    message.pack()
    
    
    def get_marks(v):
        button_status = list_of_widgets[v][1].get()
        if button_status:
            row = list_of_widgets[v][2] + 2
            marks = ""
            for col in range(2, sheet.max_column + 1):
                information = str(sheet.cell(row=1, column=col).value) + ": " + str(sheet.cell(row=row, column=col).value) + "\n"
                marks = marks + information
            message.config(text=marks)
        else:
            message.config(text="You unselected a student")
    
    
    list_of_widgets = []
    i = 0
    for k, v in names_and_rows.items():
        new_variable = IntVar()
        square = Checkbutton(students_names, variable=new_variable, onvalue=1, offvalue=0, text=k, command=lambda i=i: get_marks(i))
        list_of_widgets.append((square, new_variable, i))
        square.pack()
        i += 1
    
    root.mainloop()
    

    需要特别注意的是,您需要使用collections.OrderedDict() 来维护您的姓名列表的顺序。这不仅会使它们从您的 Excel 工作表中以正确的顺序出现(想象一下,尝试在每次程序运行时随机打乱的 100 个名称的列表中找到一个名称……这就是您要去的地方),而且还会允许您确定需要在 get_marks() 函数中引用的行。

    【讨论】:

    • 您的代码中至少存在一个问题:value = IntVar 没有按照您的想法执行。另外,如果list_of_widgets 有两个元组,item[2] 会抛出错误。
    • 我只是从 OP 复制的,但你说得对,现在你指出它应该是 IntVar()
    猜你喜欢
    • 1970-01-01
    • 2017-11-04
    • 2020-11-19
    • 1970-01-01
    • 1970-01-01
    • 2020-03-18
    • 2019-08-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多