Я преподаватель. Используя 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()
Я думаю, что лучшим подходом было бы хранить каждый виджет в списке. Что-то типа:
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()
Обратите внимание, что это только добавляет, и вам потребуется другая логика для удаления студентов, которых вы отменяете, но я обычно подхожу к проблеме именно так!
Вам нужно взглянуть на сообщение здесь, потому что вы назначаете ссылку на переменную, а не на саму переменную, поэтому она всегда будет оценивать последнее значение цикла для всех кнопок. Мне нравится ответ 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()
.
Я просто копировал это из OP, но вы правы, теперь, когда вы указываете на это, это должно быть IntVar()
В вашем коде есть как минимум одна проблема:
value = IntVar
не делает то, что вы думаете. Кроме того, еслиlist_of_widgets
имеет два кортежа,item[2]
выдаст ошибку.