Обработка и хранение данных / подсказка к 4 задачке

4

Добавить поле под отчество, при выводе списка показывать только имя фамилию. В общем чтобы как-то так работало:

Если лень зполнять список вручную можно использовать вот такой список: students.json

Кстати, чтобы сделать такой скроллбар как у меня надо добавить специальную компоненту в __init__

scrollbar = Scrollbar(gui)
scrollbar.place(x=220 + 160, y=10, height=130) # расположить ее справа от списка
scrollbar.config(command=self.studentsList.yview) # привязать скроллинг списка как реакцию на движение ползунка
self.studentsList.config(yscrollcommand=scrollbar.set) # привязать смещение ползунка, когда скролят список

Теперь, когда у нас есть файлик с несколькими записями попробуем вывести их сразу все на форму.

Для этого в tkinter встроена такая компонента как ListBox то бишь “коробочка под список”, выглядит он вот так:

Добавим ее на форму:

class UI():
    def __init__(self, gui):    
        gui.geometry("400x150")
        
        self.name = create_entry(10, 10, "Имя", 120)
        self.last_name = create_entry(10, 40, "Фамилия", 120)
        self.group = create_entry(10, 70, "Группа", 120)
        
        self.button = Button(text="Сохранить в файл", command=self.save_to_file)
        self.button.place(x=10, y=100)
        
        # добавим список на форму
        self.studentsList = Listbox(exportselection=False)
        self.studentsList.place(x=220, y=10, height=130, width=170)
          
    def save_to_file(self):
        # ...

получится так:

Попробуем чего-нибудь добавить в этот список:

class UI():
    def __init__(self, gui):    
        #...
        
        self.studentsList = Listbox(exportselection=False)
        self.studentsList.place(x=220, y=10, height=130, width=170)
        
        # добавил три планеты
        self.studentsList.insert(END, "Меркурий")
        self.studentsList.insert(END, "Венера")
        self.studentsList.insert(END, "Земля")
          
    def save_to_file(self):
        # ...

Попробуем теперь заполнить данными из файла:

class UI():
    def __init__(self, gui):    
        
        
        self.studentsList = Listbox(exportselection=False)
        self.studentsList.place(x=220, y=10, height=130, width=170)
        
        """
        это убрали
        self.studentsList.insert(END, "Меркурий")
        self.studentsList.insert(END, "Венера")
        self.studentsList.insert(END, "Земля")
        """
        
        # открываем файл со студентами
        with open("user.json", encoding="utf8") as f:
            students = json.load(f)
        
        # добавляем студентов в список
        for s in students:
            self.studentsList.insert(END, s)
            
    # ...

смотрим что получится

ну в принципе вывелось, правда смотреть не удобно. Было бы лучше если выводил как-нибудь в виде имя фамилия.

class UI():
    def __init__(self, gui):    
        # ...

        with open("user.json", encoding="utf8") as f:
            students = json.load(f)

        for s in students:
            # буду вместо s добавлять в список студента в виде "фамилия имя группа"
            self.studentsList.insert(END, f"{s['last_name']} {s['name']} {s['group']}")

во, так уже лучше

Теперь попробуем сделать так, чтобы можно было кликнуть на студента в списке и подкорректировать запись.

Для этого есть возможность подцеплять так называемую реакцию на события к компоненте на форме.

В tkinter целая куча встроенных событий, например, мы можем добавить реакцию на клик мышкой. Делается это так:

class UI():
    def __init__(self, gui):    
        # ...
        
        self.studentsList = Listbox(exportselection=False)
        self.studentsList.place(x=220, y=10, height=130, width=170)
        
        # добавляем реакцию на клик мышки, чтобы вызывалась функция on_student_list_click
        self.studentsList.bind("<Button>", self.on_student_list_click)
        
        # ...
     
    # собственно функция on_student_list_click
    # обрати внимание на второй аргумент функции event, там информация о событии
    def on_student_list_click(self, event):
        print(event) # выведем информацию о событии

тестируем:

как мы видим он показывает координаты клика внутри списка. Еще есть там какой-то num, это номер кнопки мыши которую кликнули:

можно, например, подкорректировать функцию:

def on_student_list_click(self, event):
    if event.num == 1:
        print(f"Вы кликнули левой кнопкой в точку ({event.x}, {event.y})")
    elif event.num == 2:
        print(f"Вы кликнули средней кнопкой в точку ({event.x}, {event.y})")
    elif event.num == 3:
        print(f"Вы кликнули правой кнопкой в точку ({event.x}, {event.y})")

и потыкать разными кнопками мыши:

а как например понять, что кликнули именно на конкретный элемент, тут есть два способа, первый это узнать по y координате порядковый номер строки в списке, а потом по номеру получить текст в этой строке

def on_student_list_click(self, event):
    item_index = self.studentsList.nearest(event.y) # по координате event.y получаем номер строки
    item_text = self.studentsList.get(item_index) # по номеру строки берем текст в строке
    print(item_text) # выводим его в консольку

кстати если в консольку не нравится, можно показать информационное сообщение, делается это так:

def on_student_list_click(self, event):
    item_index = self.studentsList.nearest(event.y) # по координате event.y получаем номер строки
    item_text = self.studentsList.get(item_index) # по номеру строки берем текст в строке
    messagebox.showinfo("Сообщение! =О", item_text) # выводим информационное сообщение, первый параметр -- это заголовок окна

больше видов можно подсмотреть тут https://docs.python.org/3/library/tkinter.messagebox.html

тестируем

прекрасно! =)

И альтернативный способ, если брать выбранный элемент по координате y выглядит странно. Можно вместо события клика мышью использовать специальное событие выбора строки. Подключается это дело так:

class UI():
    def __init__(self, gui):    
        #...
        
        self.studentsList = Listbox(exportselection=False)
        self.studentsList.place(x=220, y=10, height=130, width=170)
        self.studentsList.bind('<<ListboxSelect>>', self.on_student_list_click)
        # эту строчку убираем self.studentsList.bind("<Button>", self.on_student_list_click)
        
        # ...
        
    def on_student_list_click(self, event):
        # тут вместо nearest используем метод curselection(), 
        # возвращает список выбранных строк (теоретически их может быть несколько),
        # так как нам нужна только одна то поэтому пишем curselection()[0]
        item_index = self.studentsList.curselection()[0]
        
        # а дальше как обычно
        item_text = self.studentsList.get(item_index)
        messagebox.showinfo("", item_text)

работает так же

Ну пока мы играемся, а как сделать чтобы при клике на элемент в списке, заполнялась форма. И можно было бы обновить запись.

На самом деле сделать это не так просто, потому что то что выводится на форме к исходному списку уже никак не привязано, это по сути отдельный список.

И как быть?

А надо просто исходный список который из файла грузим тоже сохранять привязанным к self

Делаем это так:

class UI():
    def __init__(self, gui):    
        # ...
        
        self.studentsList = Listbox(exportselection=False)
        self.studentsList.place(x=220, y=10, height=130, width=170)
        self.studentsList.bind('<<ListboxSelect>>', self.on_student_list_click)
        
        # как обычно открываем файл со студентами
        with open("user.json", encoding="utf8") as f:
            # и теперь привязываем список из файла к self.students
            self.students = json.load(f)
        
        # ну итерируем по списку self.students
        for s in self.students:
            self.studentsList.insert(END, f"{s['last_name']} {s['name']} {s['group']}")
        
    
    def on_student_list_click(self, event):
        # в реакции на клик берем как обычно выбранный номер строки с формы 
        item_index = self.studentsList.curselection()[0]
        # а значение берем не из списка на форме, а из self.students
        item = self.students[item_index]
        
        # и заполняем значения на форме
        self.name.delete(0, END)
        self.name.insert(0, item['name'])
        
        self.last_name.delete(0, END)
        self.last_name.insert(0, item['last_name'])
        
        self.group.delete(0, END)
        self.group.insert(0, item['group'])
    
    def save_to_file(self):
        # ...
     

тыкаем:

в принципе можно теперь задание пилить