ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 05.12.2023
Просмотров: 860
Скачиваний: 3
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Глава 32. Приложение «Судоку»
771
Здесь мы создаем экземпляр класса
QApplication
, представляющий приложение, указываем для него значок, подготовленный ранее, создаем экземпляр только что написанного класса
MainWindow
, представляющего основное окно, выводим окно на экран и запускаем прило- жение.
Выполним запуск приложения, запустив модуль start.pyw любым знакомым нам способом: щелчком мышью на самом файле или нажатием клавиши
32.3.6. Копирование и вставка головоломок
Итак, базовые функции приложения мы реализовали. Настало время заняться дополнитель- ными.
Начнем мы с реализации копирования головоломок в буфер обмена и их вставки оттуда.
Код, который мы напишем для этого, будет впоследствии использован также для сохране- ния головоломок в файлы и их последующей загрузки.
32.3.6.1. Форматы данных
Но сначала следует определиться, в каких форматах головоломки будут копироваться в бу- фер обмена. В разд. 32.2 мы решили, что таковых будет три: полный, компактный и предна- значенный для Microsoft Excel.
В любом случае данные будут копироваться в виде строки, состоящей только из цифр от 0 до 9.
Полный формат — строка длиной 162 символа. Каждая пара цифр, содержащаяся в ней, представляет сведения об одной ячейке:
• первая цифра — обозначает состояние блокировки ячейки: 0 — разблокирована, 1 — заблокирована;
• вторая цифра — это, собственно, цифра, которая установлена в ячейке, или 0, если ячейка не имеет цифры.
Сведения о ячейках записываются последовательно, без каких-либо разделителей: пер- вая пара цифр хранит сведения о ячейке с номером 0, вторая — о ячейке 1, третья — о ячейке 2 и т. д.
Компактный формат — строка длиной 81 символ. Она содержит только цифры, уста- новленные в ячейках; 0 обозначает отсутствие цифры в соответствующей ячейке. Первая цифра соответствует ячейке 0, вторая — ячейке 1 и т. д.
Формат для Excel — более длинная строка. Каждую ячейку представляет одна цифра — та, что установлена в нее (состояние блокировки не сохраняется). Если ячейка не имеет цифры, сохраняется пустая строка. Цифры или пустые строки, соответствующие всем ячейкам одной строки поля судоку, отделяются друг от друга символами табуляции.
Наборы цифр или пустых строк, соответствующие отдельным строкам, отделяются друг от друга последовательностями символов возврата каретки и перевода строки. Этой же последовательностью символов завершается сама строка с данными.
Чтобы реализовать копирование и вставку головоломок, нам потребуется добавить новые методы в классы
Widget и
MainWindow
772
Часть II. Библиотека PyQt 5 32.3.6.2. Реализация копирования и вставки в классе Widget
В классе
Widget мы объявим четыре новых метода:
getDataAllCells()
— возвращает данные о головоломке в полном формате;
getDataAllCellsMini()
— возвращает данные о головоломке в компактном формате;
getDataAllCellsExcel()
— возвращает данные о головоломке в формате для Excel.
Эти методы не будут непосредственно помещать данные о головоломке в буфер обмена, а станут лишь формировать их. Занесением готовых данных в буфер обмена займутся методы класса
MainWindow
, которые мы напишем потом.
Методы getDataAllCells()
и getDataAllCellsMini()
мы впоследствии используем для подготовки данных, которые будут записываться в файлы;
setDataAllCells()
— принимает с единственным параметром данные о головоломке, представленные в полном или компактном формате (формат распознается автоматиче- ски), и выполняет их вставку.
Опять же, этот метод не будет непосредственно извлекать данные из буфера обмена, а станет лишь выполнять вставку данных, извлеченных оттуда специальными методами класса
MainWindow
. Один из этих методов, помимо всего прочего, выполнит преобразо- вание полученных из буфера обмена данных, представленных в формате Excel, в полный формат перед тем, как передать их методу setDataAllCells()
Позднее мы используем этот метод для вставки данных о головоломке, прочитанных из файла.
Листинг 32.13. Метод getDataAllCells() def getDataAllCells(self): listAllData = [] for cell in self.cells: listAllData.append("0" if cell.isCellChange else "1") s = cell.text() listAllData.append(s if len(s) == 1 else "0") return "".join(listAllData)
Метод getDataAllCells()
возвращает строку с данными о головоломке в полном формате.
Формировать строку с копируемыми данными можно двумя способами. Первый способ заключается в том, что сначала объявляется переменная, хранящая пустую строку, а потом к этой строке постепенно добавляются символы, хранящие сведения о ячейках. Но в таком случае при очередном добавлении символов предыдущая строка останется в оперативной памяти — в результате эти «мусорные» строки будут постепенно накапливаться, засоряя память. Разумеется, рано или поздно они будут удалены особой подсистемой Python, нося- щей название сборщика мусора, но произойдет это только тогда, когда приложение будет простаивать.
Поэтому, чтобы избежать «замусоривания» памяти, мы используем второй способ: объявим пустой список, в который будем добавлять строки с символами, представляющие сведения об очередной ячейке, а под конец сформируем строку, составленную из элементов этого списка (для этого мы вызовем у пустой строки метод join()
, передав ему наш список). Это и будут данные о головоломке, записанные в полном формате.
Глава 32. Приложение «Судоку»
773
В самом методе getDataAllCells()
нет ничего особо сложного. Мы перебираем ячейки поля судоку в цикле. У каждой ячейки мы выясняем состояние блокировки (оно, как мы помним, хранится в атрибуте isCellChange класса
MyLabel
). Если ячейка разблокирована, добавляем в список строку "0"
, в противном случае —
"1"
. Далее мы вызовом унаследованного метода text()
извлекаем текстовое содержимое ячейки, проверяем, равна ли ее длина единице (т. е. установлена ли в ячейку цифра), и, если так, добавляем в список строку с этим содержимым
(т. е. с установленной в ячейку цифрой). В противном случае добавляем строку "0"
. Под конец мы формируем строку, составленную из элементов списка и представляющую собой данные, которые должны быть помещены в буфер обмена. Эту строку мы возвращаем в ка- честве результата.
Листинг 32.14. Метод getDataAllCellsMini() def getDataAllCellsMini(self): listAllData = [] for cell in self.cells: s = cell.text() listAllData.append(s if len(s) == 1 else "0") return "".join(listAllData)
Код метода getDataAllCellsMini()
, возвращающий данные о головоломке в компактном формате, работает аналогично. Вы, уважаемые читатели, и сами разберетесь, как.
Листинг 32.15. Метод getDataAllCellsExcel() def getDataAllCellsExcel(self): numbers = (9, 18, 27, 36, 45, 54, 63, 72) listAllData = [self.cells[0].text()] for i in range(1, 81): listAllData.append("\r\n" if i in numbers else "\t") listAllData.append(self.cells[i].text()) listAllData.append("\r\n") return "".join(listAllData)
Метод getDataAllCellsExcel()
, что станет формировать данные для вставки в Excel, немно- гим сложнее. Мы создаем кортеж, содержащий номера ячеек, перед которыми в результи- рующую строку вместо символа табуляции нужно вставить возврат каретки и перевод стро- ки, — как видим, это номера первых ячеек в каждой строке, кроме первой. Затем мы созда- ем список из единственного элемента — содержимого самой первой ячейки: цифры или пустой строки. Далее мы в цикле перебираем все ячейки от второй до последней. Если но- мер очередной ячейки входит в объявленный ранее кортеж (т. е. начинается новая строка поля судоку), мы добавляем в список возврат каретки и перевод строки, в противном слу- чае — символ табуляции. Далее добавляем в список содержимое ячейки. Наконец, заверша- ем формируемые данные возвратом каретки и переводом строки и формируем строку, со- ставленную из элементов списка.
Листинг 32.16. Метод setDataAllCells() def setDataAllCells(self, data): l = len(data) if l == 81:
774
Часть II. Библиотека PyQt 5 for i in range(0, 81): if data[i] == "0": self.cells[i].setText("") self.cells[i].clearCellBlock() else: self.cells[i].setText(data[i]) self.cells[i].setCellBlock() self.onChangeCellFocus(0) elif l == 162: for i in range(0, 162, 2): if data[i] == "0": self.cells[i // 2].clearCellBlock() else: self.cells[i // 2].setCellBlock() self.cells[i // 2].setText("" if data[i + 1] == "0"
else data[i + 1]) self.onChangeCellFocus(0)
Метод setDataAllCells()
, вставляющий данные о головоломке в поле судоку, будет самым сложным. Ведь сначала он должен выяснять, в каком формате представлены данные — в полном или компактном.
Данные, предназначенные для вставки, метод получает с единственным параметром, и дан- ные эти представлены в виде строки. Следовательно, узнать их формат мы можем, выяснив длину строки с данными:
если длина строки с данными равна 81-му символу, данные представлены в компактном формате. В этом случае мы перебираем все символы в полученной строке. Если очеред- ной символ представляет собой цифру "0"
, мы очищаем и разблокируем соответствую- щую ячейку, в противном случае заносим очередной символ (которым станет цифра) в ячейку и блокируем ее;
если же длина строки равна 162-м символам, данные представлены в полном формате.
Мы точно так же перебираем в цикле символы этой строки, но уже через один, извлекая тем самым на каждом проходе каждый четный символ (для чего применим цикл for i in range(0, 162, 2)
), — этим символом станет обозначение состояния блокировки со- ответствующей ячейки. Номер соответствующей символу ячейки мы можем получить, разделив номер очередного символа на 2 нацело (применив оператор
//
). Если извле- ченный символ является цифрой "0"
, мы разблокируем ячейку, в противном случае — блокируем. Далее мы извлекаем и проверяем следующий символ строки: если это циф- ра "0"
, очищаем ячейку, а если цифра, отличная от "0"
, заносим ее в ячейку.
В любом случае после вставки данных мы делаем активной самую первую (левую верх- нюю) ячейку поля судоку.
32.3.6.3. Реализация копирования и вставки в классе MainWindow
В классе
MainWindow мы внесем дополнения в код конструктора и объявим шесть новых методов:
onCopyData()
— копирует в буфер обмен данные в полном формате;
onCopyDataMini()
— копирует в буфер обмена данные в компактном формате;
Глава 32. Приложение «Судоку»
775
onCopyDataExcel()
— копирует в буфер обмена данные в формате для Excel;
onPasteData()
— вставляет из буфера обмена данные, представленные в полном или компактном формате, предварительно проверив эти данные на корректность;
onPasteDataExcel()
— вставляет из буфера обмена данные, представленные в формате для Excel, предварительно проверив эти данные на корректность;
dataErrorMsg()
— выводит сообщение о том, что предназначенные для вставки данные имеют неправильный формат.
Но начнем мы с того, что в самое начало файла, где находятся выражения импорта, добавим выражение, импортирующее модуль для поддержки регулярных выражений: import re
Регулярные выражения очень помогут нам реализовать проверку вставляемых данных на корректность.
Доработаем конструктор. Все необходимые добавления показаны в листинге 32.17 (добав- ленный код выделен полужирным шрифтом).
Листинг 32.17. Конструктор (дополнения) def __init__(self, parent=None): myMenuEdit = menuBar.addMenu("&Правка") action = myMenuEdit.addAction(QtGui.QIcon(r"images/copy.png"),
"К&опировать", self.onCopyData,
QtCore.Qt.CTRL + QtCore.Qt.Key_C) toolBar.addAction(action) action.setStatusTip("Копирование головоломки в буфер обмена") action = myMenuEdit.addAction("&Копировать компактно", self.onCopyDataMini) action.setStatusTip("Копирование в компактном формате") action = myMenuEdit.addAction("Копировать &для Excel", self.onCopyDataExcel) action.setStatusTip("Копирование в формате MS Excel") action = myMenuEdit.addAction(QtGui.QIcon(r"images/paste.png"),
"&Вставить", self.onPasteData,
QtCore.Qt.CTRL + QtCore.Qt.Key_V) toolBar.addAction(action) action.setStatusTip("Вставка головоломки из буфера обмена") action = myMenuEdit.addAction("Вставить &из Excel", self.onPasteDataExcel) action.setStatusTip("Вставка головоломки из MS Excel") myMenuEdit.addSeparator() toolBar.addSeparator()
776
Часть II. Библиотека PyQt 5 action = myMenuEdit.addAction("&Блокировать", self.sudoku.onBlockCell, QtCore.Qt.Key_F2)
Здесь мы добавляем в начало меню Правка пять пунктов: Копировать, Копировать компактно
, Копировать для Excel, Вставить и Вставить из Excel. Указываем для них в качестве обработчиков перечисленные ранее методы, а также добавляем нужные кнопки в панель инструментов.
Листинг 32.18. Методы onCopyData(), onCopyDataMini() и onCopyDataExcel() def onCopyData(self):
QtWidgets.QApplication.clipboard().setText( self.sudoku.getDataAllCells()) def onCopyDataMini(self):
QtWidgets.QApplication.clipboard().setText( self.sudoku.getDataAllCellsMini()) def onCopyDataExcel(self):
QtWidgets.QApplication.clipboard().setText( self.sudoku.getDataAllCellsExcel())
Методы onCopyData()
, onCopyDataMini()
и onCopyDataExcel()
, копирующие данные, очень просты. Они всего лишь помещают в буфер обмена результат, возвращенный, соответст- венно, методами getDataAllCells()
, getDataAllCellsMini()
и getDataAllCellsExcel()
клас- са поля судоку (листинг 32.18).
Листинг 32.19. Метод onPasteData() def onPasteData(self): data = QtWidgets.QApplication.clipboard().text() if data: if len(data) == 81 or len(data) == 162: r = re.compile(r"[^0-9]") if not r.match(data): self.sudoku.setDataAllCells(data) return self.dataErrorMsg()
Метод onPasteData()
вставляет данные в полном или компактном формате (листинг 32.19).
Перед тем как вызвать метод setDataAllCells()
класса поля судоку, передав ему данные для вставки, он выполняет их проверку. Сначала он удостоверяется, что данные для вставки вообще есть (не равны пустой строке), потом — что их длина равна 81-му или 162-м симво- лам. Далее он выполняет последнюю проверку — выясняет, не присутствует ли в строке символ, отличный от цифр 0...9. Для этого он создает регулярное выражение, совпадающее с любым из таких символов (
1 ... 72 73 74 75 76 77 78 79 ... 83
[^0-9]
), и выполняет поиск в строке с данными посредством быстро выполняющегося метода match()
. (Более подробно о работе с регулярными выраже- ниями рассказывалось в главе 7.)
Глава 32. Приложение «Судоку»
777
И только если все проверки выполнены, вызывается метод setDataAllCells()
, и ему пере- дается строка с данными для вставки. После чего сразу же выполняется выход из метода.
Если же какая-либо проверка завершилась неудачей, будет выполнено самое последнее вы- ражение — вызов метода dataErrorMsg()
, выводящего сообщение об ошибке. Мы напишем этот метод потом.
Листинг 32.20. Метод onPasteDataExcel() def onPasteDataExcel(self): data = QtWidgets.QApplication.clipboard().text() if data: data = data.replace("\r", "") r = re.compile(r"([0-9]?[\t\n]){81}") if r.match(data): result = [] if data[-1] == "\n": data = data[:-1] dl = data.split("\n") for sl in dl: dli = sl.split("\t") for sli in dli: if len(sli) == 0: result.append("00") else: result.append("0" + sli[0]) data = "".join(result) self.sudoku.setDataAllCells(data) return self.dataErrorMsg()
Метод onPasteDataExcel()
вставит из буфера обмена данные, представленные в формате для Excel, разумеется, также выполнив необходимые проверки (листинг 32.20). Сначала он убедится, что данные для вставки есть, и для удобства их дальнейшей проверки и обработки удалит из них символы возврата каретки (для этого можно использовать метод replace()
класса str
, указав у этого метода первым параметром удаляемый символ, а вторым — пус- тую строку).
Полученная нами строка представляет собой набор из строго 81-й комбинации двух симво- лов: цифры от 0...9, которая может присутствовать в единственном числе или отсутство- вать, и символа табуляции или перевода строки. Это правило прекрасно формализуется регулярным выражением
([0-9]?[\t\n]){81}
. Мы сравниваем с ним строку с данными и выполняем дальнейшие манипуляции, только если сравнение выполняется.
Сначала подготавливаем пустой список, в который будем помещать отдельные строки — фрагменты вставляемой головоломки. Удаляем из полученной строки с данными завер- шающий символ перевода строки, если он там есть. Разбиваем эту строку по символам пе- ревода строки, воспользовавшись методом split()
класса str
, и получаем список строк, представляющих отдельные строки поля судоку. Перебираем этот список и каждую из имеющихся в нем строк тем же методом разбиваем по символам табуляции, получив список строк, каждая из которых представляет сведения об одной ячейке. Перебираем этот список.