Обзор проекта
Приложение WordMatch включает в себя три модуля, которые могут работать и вместе, и по отдельности:
Скрипт для создания пользовательского словаря.
GUI интерфейс и набор CRUD операций для добавления, редактирования и удаления записей в словаре.
GUI интерфейс и скрипт для проверки правильности сопоставления иностранных слов и значений, выведенных в случайном порядке.
WordMatch состоит из трех независимых скриптов
Что мы изучим
Как создавать базы данных, выполнять CRUD операции и запросы на языке SQL.
Как обрабатывать события в элементах Listbox.
Как назначить действия основным кнопкам программы и кнопке закрытия окна.
Скрипт для создания словаря
Словарь представляет собой базу данных SQLite, которая поставляется с Python по умолчанию. Для создания новой базы не придется устанавливать никакие дополнительные модули. Однако при желании можно установить набор дополнительных инструментов для работы с SQLite и один из визуальных браузеров-редакторов:
SQLiteStudio
DBeaver
DB Browser for SQLite
Структура таблицы словаря dictionary задается в sql_create_dictionary_table скрипта create_new_db.py:
id – порядковый номер записи (целое число);
word – иностранное слово (текстовое поле);
meaning – значение слова на русском языке (текстовое поле).
При желании в этом описании можно задать поле для фонетической транскрипции. Для создания базы в определенной директории надо сначала убедиться, что необходимый путь к папке уже имеется на диске. Если нужную директорию не создать заранее, но указать путь к ней в коде, то выполнение скрипта завершится так:
unable to open database file Ошибка: не удалось подключиться к базе.
Если же путь вообще не указан, файл базы данных будет создан в текущей рабочей директории – в Windows это C:UsersUser .
При подключении к несуществующей базе SQLite создает файл базы автоматически, но только при условии, что указанный путь существует. Ниже приведен полный код скрипта для создания базы данных словаря. При этом папка Dictionary в поддиректории Users была создана заранее, а файл dictionary.db в ней был сгенерирован скриптом автоматически:
create_new_db.py
import sqlite3 from sqlite3 import Error def create_connection(db_file): conn = None try: conn = sqlite3.connect(db_file) return conn except Error as e: print(e) return conn def create_table(conn, create_table_sql): try: c = conn.cursor() c.execute(create_table_sql) except Error as e: print(e) def main(): database = r"dictionary_my.db" # описание столбцов словаря - id номер, слово и значение sql_create_dictionary_table = """ CREATE TABLE IF NOT EXISTS dictionary ( id integer PRIMARY KEY, word text, meaning text ); """ # подключение к базе conn = create_connection(database) # создание таблицы dictionary if conn is not None: create_table(conn, sql_create_dictionary_table) else: print("Ошибка: не удалось подключиться к базе.") if __name__ == '__main__': main()
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста» Интересно, перейти к каналу
GUI интерфейс и скрипт для набора CRUD операций
Графический интерфейс программы включает стандартные элементы Tkinter и несколько виджетов модуля Ttk . Для позиционирования элементов на поверхности окна в Tkinter есть целых три метода – pack() , place() и grid() . Мы воспользуемся последним, поскольку он предусматривает максимальную точность размещения. При использовании grid() все пространство окна делится на ряды row и столбцы column . Для каждого элемента нужно указать ряд и столбец, на пересечении которых он размещается:
(row = 2, column = 0)
Еще можно указать ширину элемента, если нужно, чтобы он соответствовал ширине нескольких столбцов:
columnspan = 2
Визуальный интерфейс для CRUD
SQL-запросы и команды
Первый запрос, который нам потребуется для извлечения из базы всех слов, выглядит так:
'SELECT * FROM dictionary ORDER BY word DESC'
В этом случае результаты будут упорядочены по алфавитному порядку английских слов. Если заменить word
на meaning
, слова в таблице окажутся упорядоченными по русскоязычному значению.
В функции add_word() используется команда для вставки новой записи:
'INSERT INTO dictionary VALUES(NULL, ?, ?)'
Вопросительными знаками обозначаются параметры, которые передаются (из соответствующих полей формы) на следующей строке:
parameters = (self.word.get(), self.meaning.get())
Сообщение об успешном добавлении нового слова
Для удаления слова необходимо выделить соответствующую строку. Слово извлекается из выделенной строки:
word = self.tree.item(self.tree.selection())['text']
И передается в качестве параметра с командой удаления:
query = 'DELETE FROM dictionary WHERE word = ?' self.run_query(query, (word, ))
В функции редактирования существующей записи мы реализуем предварительное заполнение поля формы старыми значениями – оригиналом слова и его переводом:
value = word value = old_meaning
Предварительное заполнение полей в окне редактирования
Это нужно для того, чтобы при сохранении записи не сохранилось пустое поле вместо предыдущего слова или значения, если одно из них не нужно было редактировать и пользователь не ввел слово (значение) вручную. А еще это упрощает исправление опечаток.
Фактическое обновление существующей записи производится командой со следующими параметрами:
query = 'UPDATE dictionary SET word = ?, meaning = ? WHERE word = ? AND meaning = ?' parameters = (new_word, new_meaning, word, old_meaning)
Вот полный код для CRUD скрипта и его интерфейса:
from tkinter import ttk from tkinter import * import sqlite3 class Dictionary: db_name = 'dictionary.db' def __init__(self, window): self.wind = window self.wind.title('Редактирование словаря') # создание элементов для ввода слов и значений frame = LabelFrame(self.wind, text = 'Введите новое слово') frame.grid(row = 0, column = 0, columnspan = 3, pady = 20) Label(frame, text = 'Слово: ').grid(row = 1, column = 0) self.word = Entry(frame) self.word.focus() self.word.grid(row = 1, column = 1) Label(frame, text = 'Значение: ').grid(row = 2, column = 0) self.meaning = Entry(frame) self.meaning.grid(row = 2, column = 1) ttk.Button(frame, text = 'Сохранить', command = self.add_word).grid(row = 3, columnspan = 2, sticky = W + E) self.message = Label(text = '', fg = 'green') self.message.grid(row = 3, column = 0, columnspan = 2, sticky = W + E) # таблица слов и значений self.tree = ttk.Treeview(height = 10, columns = 2) self.tree.grid(row = 4, column = 0, columnspan = 2) self.tree.heading('#0', text = 'Слово', anchor = CENTER) self.tree.heading('#1', text = 'Значение', anchor = CENTER) # кнопки редактирования записей ttk.Button(text = 'Удалить', command = self.delete_word).grid(row = 5, column = 0, sticky = W + E) ttk.Button(text = 'Изменить', command = self.edit_word).grid(row = 5, column = 1, sticky = W + E) # заполнение таблицы self.get_words() # подключение и запрос к базе def run_query(self, query, parameters = ()): with sqlite3.connect(self.db_name) as conn: cursor = conn.cursor() result = cursor.execute(query, parameters) conn.commit() return result # заполнение таблицы словами и их значениями def get_words(self): records = self.tree.get_children() for element in records: self.tree.delete(element) query = 'SELECT * FROM dictionary ORDER BY word DESC' db_rows = self.run_query(query) for row in db_rows: self.tree.insert('', 0, text = row[1], values = row[2]) # валидация ввода def validation(self): return len(self.word.get()) != 0 and len(self.meaning.get()) != 0 # добавление нового слова def add_word(self): if self.validation(): query = 'INSERT INTO dictionary VALUES(NULL, ?, ?)' parameters = (self.word.get(), self.meaning.get()) self.run_query(query, parameters) self.message['text'] = 'слово {} добавлено в словарь'.format(self.word.get()) self.word.delete(0, END) self.meaning.delete(0, END) else: self.message['text'] = 'введите слово и значение' self.get_words() # удаление слова def delete_word(self): self.message['text'] = '' try: self.tree.item(self.tree.selection())['text'][0] except IndexError as e: self.message['text'] = 'Выберите слово, которое нужно удалить' return self.message['text'] = '' word = self.tree.item(self.tree.selection())['text'] query = 'DELETE FROM dictionary WHERE word = ?' self.run_query(query, (word, )) self.message['text'] = 'Слово {} успешно удалено'.format(word) self.get_words() # рeдактирование слова и/или значения def edit_word(self): self.message['text'] = '' try: self.tree.item(self.tree.selection())['values'][0] except IndexError as e: self.message['text'] = 'Выберите слово для изменения' return word = self.tree.item(self.tree.selection())['text'] old_meaning = self.tree.item(self.tree.selection())['values'][0] self.edit_wind = Toplevel() self.edit_wind.title = 'Изменить слово' Label(self.edit_wind, text = 'Прежнее слово:').grid(row = 0, column = 1) Entry(self.edit_wind, textvariable = StringVar(self.edit_wind, value = word), state = 'readonly').grid(row = 0, column = 2) Label(self.edit_wind, text = 'Новое слово:').grid(row = 1, column = 1) # предзаполнение поля new_word = Entry(self.edit_wind, textvariable = StringVar(self.edit_wind, value = word)) new_word.grid(row = 1, column = 2) Label(self.edit_wind, text = 'Прежнее значение:').grid(row = 2, column = 1) Entry(self.edit_wind, textvariable = StringVar(self.edit_wind, value = old_meaning), state = 'readonly').grid(row = 2, column = 2) Label(self.edit_wind, text = 'Новое значение:').grid(row = 3, column = 1) # предзаполнение поля new_meaning= Entry(self.edit_wind, textvariable = StringVar(self.edit_wind, value = old_meaning)) new_meaning.grid(row = 3, column = 2) Button(self.edit_wind, text = 'Изменить', command = lambda: self.edit_records(new_word.get(), word, new_meaning.get(), old_meaning)).grid(row = 4, column = 2, sticky = W) self.edit_wind.mainloop() # внесение изменений в базу def edit_records(self, new_word, word, new_meaning, old_meaning): query = 'UPDATE dictionary SET word = ?, meaning = ? WHERE word = ? AND meaning = ?' parameters = (new_word, new_meaning, word, old_meaning) self.run_query(query, parameters) self.edit_wind.destroy() self.message['text'] = 'слово {} успешно изменено'.format(word) self.get_words() if __name__ == '__main__': window = Tk() application = Dictionary(window) window.mainloop()
Модуль для запоминания слов и проверки значений
Английские слова и их значения загружаются в два элемента Listbox . Для перемешивания слов и значений в случайном порядке используется метод shuffle из модуля random . Для обработки событий (кликов) по спискам Listbox мы напишем две функции – callback_left и callback_right . Чтобы связать функции с Listbox, нужно воспользоваться методом bind :
self.right.bind("<<ListboxSelect>>", self.callback_right) self.left.bind("<<ListboxSelect>>", self.callback_left)
Слова и значения выводятся в случайном порядке
Функция callback_left отслеживает клики по английским словам в левом элементе Listbox . Когда пользователь кликает по слову, функция посылает запрос в базу:
'SELECT * from dictionary WHERE word = ?'
Результат запроса – отдельная запись:
record = cursor.fetchone()
Второй элемент записи record[2]
является значением слова, которое передается в функцию callback_right .
Функция callback_right обрабатывает клики по значениям слов в правом списке Listbox . Когда пользователь кликает по значению, функция сравнивает его со значением, полученным из callback_left . Если они совпадают – ответ является верным, и английское слово вместе с соответствующим значением удаляются из левого и правого списков:
if click == self.trans: self.right.delete(ANCHOR) self.left.delete(ANCHOR)
В противном случае выводится сообщение о неверном ответе, а выделение с ошибочного значения снимается.
В GUI модуля для заучивания слов доступны две кнопки: Редактировать для вызова скрипта и визуального интерфейса редактирования словаря, а также Начать сначала для перезагрузки слов и значений. Чтобы вызвать скрипт редактирования словаря, достаточно написать простейшую функцию:
def run_edit(self): os.system('edit_dictionary.py')
Назначение команд для кнопок выглядит так:
ttk.Button(text="Начать сначала", command=self.restart_program).grid(row = 4, column = 1, sticky = W + E) ttk.Button(text="Редактировать", command=self.run_edit).grid(row = 4, column = 0, sticky = W + E)
Редактировать словарь можно прямо из модуля запоминания слов
Действие для кнопки х , которая по умолчанию просто закрывает окно и останавливает программу, можно переопределить таким образом, чтобы выводилось окно подтверждения:
Подтверждение выхода
Для этого нужно задать новый протокол:
self.wind.protocol("WM_DELETE_WINDOW", self.on_exit)
И добавить функцию:
def on_exit(self): if messagebox.askyesno("Выйти", "Закрыть программу?"): self.wind.destroy()
Это полный код модуля word_match.py для запоминания и проверки значений слов:
word_match.py
from tkinter import ttk from tkinter import * import random, os import sqlite3 class Match: db_name = 'dictionary.db' def __init__(self, window): self.wind = window self.wind.title('Учим слова') self.eng, self.trans = str(), str() self.message = Label(text = '', fg = 'red') self.message.grid(row = 1, column = 0, columnspan = 2, sticky = W + E) # правая и левая колонки self.left = Listbox(height = 12, exportselection=False, activestyle='none') self.left.grid(row = 2, column = 0) self.right = Listbox(height = 12, activestyle='none') self.right.grid(row = 2, column = 1) self.right.bind("<<ListboxSelect>>", self.callback_right) self.left.bind("<<ListboxSelect>>", self.callback_left) # назначение команд кнопкам программы и х-кнопке окна ttk.Button(text="Начать сначала", command=self.restart_program).grid(row = 4, column = 1, sticky = W + E) ttk.Button(text="Редактировать", command=self.run_edit).grid(row = 4, column = 0, sticky = W + E) self.wind.protocol("WM_DELETE_WINDOW", self.on_exit) # заполняем колонки словами self.get_words() # закрытие программы по клику на кнопке х def on_exit(self): if messagebox.askyesno("Выйти", "Закрыть программу?"): self.wind.destroy() # подключение к базе и передача запроса def run_query(self, query, parameters = ()): with sqlite3.connect(self.db_name) as conn: cursor = conn.cursor() result = cursor.execute(query, parameters) conn.commit() return result # запрос на извлечение всех существующих записей из базы в алфавитном порядке def get_words(self): query = 'SELECT * FROM dictionary ORDER BY word DESC' db_rows = self.run_query(query) # формирование словаря из перемешанных в случайном порядке слов и их значений lst_left, lst_right = [], [] for row in db_rows: lst_left.append(row[1]) lst_right.append(row[2]) random.shuffle(lst_left) random.shuffle(lst_right) dic = dict(zip(lst_left, lst_right)) # заполнение правой и левой колонок for k, v in dic.items(): self.left.insert(END, k) self.right.insert(END, v) # обработка клика по словам в левой колонке def callback_left(self, event): self.message['text'] = '' if not event.widget.curselection(): return # извлечение из базы значения выделенного слова w = event.widget idx = int(w.curselection()[0]) self.eng = w.get(idx) with sqlite3.connect(self.db_name) as conn: cursor = conn.cursor() sqlite_select_query = 'SELECT * from dictionary WHERE word = ?' cursor.execute(sqlite_select_query, (self.eng,)) record = cursor.fetchone() self.trans = record[2] # обработка клика в правой колонке def callback_right(self, event1): self.message['text'] = '' if not event1.widget.curselection(): return w = event1.widget idx = int(w.curselection()[0]) click = w.get(idx) # если выбранное слово является правильным переводом, удаляем и оригинал, и значение if click == self.trans: self.right.delete(ANCHOR) self.left.delete(ANCHOR) # сообщаем о неверном значении else: self.message['text'] = 'Неправильно' self.right.selection_clear(0, END) # загружаем окно и скрипт редактирования словаря def run_edit(self): os.system('edit_dictionary.py') # перезапуск программы def restart_program(self): self.message['text'] = '' self.left.delete(0, END) self.right.delete(0, END) self.get_words() if __name__ == '__main__': window = Tk() window.geometry('250x245+350+200') application = Match(window) window.mainloop()
Готовый проект доступен в репозитории.
Материалы по теме
Как подружить Python и базы данных SQL. Подробное руководство
SQL за 20 минут
🐘 Руководство по SQL для начинающих. Часть 1: создание базы данных, таблиц и установка связей между таблицами