Как создать набор виджетов tkinter (для цикла) и ссылаться на каждый из них в отдельности? - PullRequest
0 голосов
/ 09 апреля 2019

Я учитель.Используя 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()

Упрощенная рабочая таблица

Tkinter GUI

1 Ответ

0 голосов
/ 09 апреля 2019

Я думаю, что лучший подход - хранить каждый виджет в списке. Что-то вроде:

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().

...