Главная страница

ээдд. Прохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен. Николай Прохоренок Владимир Дронов


Скачать 7.92 Mb.
НазваниеНиколай Прохоренок Владимир Дронов
Дата05.05.2023
Размер7.92 Mb.
Формат файлаpdf
Имя файлаПрохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен.pdf
ТипДокументы
#1111379
страница76 из 83
1   ...   72   73   74   75   76   77   78   79   ...   83
QtCore.Qt.Key_F4) action.setStatusTip("Разблокирование активной ячейки") action = myMenuEdit.addAction(QtGui.QIcon(r"images/unlock.png"),
"Р&азблокировать все", self.sudoku.onClearBlockCells,
QtCore.Qt.Key_F5) toolBar.addAction(action) action.setStatusTip("Разблокирование всех ячеек")
Создаем меню Правка, его пункты Блокировать, Блокировать все, Разблокировать и
Разблокировать все и соответствующие им кнопки панели инструментов. В качестве обра- ботчиков действий указываем, соответственно, методы onBlockCell()
, onBlockCells()
, onClearBlockCell()
и onClearBlockCells()
компонента поля судоку. myMenuAbout = menuBar.addMenu("&Справка") action = myMenuAbout.addAction("О &программе...", self.aboutInfo) action.setStatusTip("Получение сведений о приложении") action = myMenuAbout.addAction("О &Qt...",
QtWidgets.qApp.aboutQt) action.setStatusTip("Получение сведений о библиотеке Qt")
Создаем меню Справка и его пункты О программе и О Qt. Для действия, связанного с первым пунктом, указываем в качестве обработчика метод aboutInfo()
класса
MainWindow
, который скоро напишем, а для действия, связанного с вторым пунктом, — статический ме- тод aboutQt()
приложения. toolBar.setMovable(False) toolBar.setFloatable(False) self.addToolBar(toolBar)
Запрещаем панели инструментов перемещаться внутри области, в которой она находится, и выноситься в отдельное окно (для нашего простого приложения это ни к чему), и добав- ляем ее в окно. Поскольку в вызове метода addToolBar()
окна мы не указали область, панель будет помещена в верхнюю часть окна. statusBar = self.statusBar() statusBar.setSizeGripEnabled(False) statusBar.showMessage("\"Судоку\" приветствует вас", 20000)
Получаем доступ к строке состояния, убираем из нее маркер изменения размера (если его оставить, пользователь сможет изменить размеры окна, а это нам совсем не нужно) и выво- дим приветственное сообщение, которое будет отображаться в течение 20 секунд.

770
Часть II. Библиотека PyQt 5 if self.settings.contains("X") and self.settings.contains("Y"): self.move(self.settings.value("X"), self.settings.value("Y"))
Проверяем, находятся ли в хранилище настроек значения с именами
X
(горизонтальная координата левого верхнего угла окна) и
Y
(его вертикальная координата), и, если это так, извлекаем эти значения и позиционируем окно по этим координатам. (Размеры окна хра- нить не имеет смысла, поскольку они неизменны.)
32.3.4.2. Остальные методы класса MainWindow
Нам осталось рассмотреть два метода класса основного окна
MainWindow
Листинг 32.10. Метод closeEvent() def closeEvent(self, evt): g = self.geometry() self.settings.setValue("X", g.left()) self.settings.setValue("Y", g.top())
Метод closeEvent()
будет автоматически вызван при закрытии окна. В нем мы выполняем сохранение текущих координат левого верхнего угла окна.
Листинг 32.11. Метод aboutInfo() def aboutInfo(self):
QtWidgets.QMessageBox.about(self, "О программе",
"
\"Судоку\" v2.0.0
"
"Программа для просмотра и редактирования судоку "
"(c) Прохоренок Н.А., Дронов В.А. 2011-2018 гг.")
Метод aboutInfo()
выведет стандартное окно со сведениями о приложении. Здесь коммен- тировать нечего.
32.3.5. Запускающий модуль
Теперь напишем запускающий модуль, который, собственно, и запустит приложение на выполнение. Мы сохраним его в файле start.pyw непосредственно в каталоге sudoku
. Код запускающего модуля представлен в листинге 32.12.
Листинг 32.12. Запускающий модуль from PyQt5 import QtGui, QtWidgets import sys from modules.mainwindow import MainWindow app = QtWidgets.QApplication(sys.argv) app.setWindowIcon(QtGui.QIcon(r"images/svd.png")) window = MainWindow() window.show() sys.exit(app.exec_())

Глава 32. Приложение «Судоку»
771
Здесь мы создаем экземпляр класса
QApplication
, представляющий приложение, указываем для него значок, подготовленный ранее, создаем экземпляр только что написанного класса
MainWindow
, представляющего основное окно, выводим окно на экран и запускаем прило- жение.
Выполним запуск приложения, запустив модуль start.pyw любым знакомым нам способом: щелчком мышью на самом файле или нажатием клавиши в окне IDLE, в котором открыт этот модуль. Проверим, работает ли активизация ячеек по щелчку мыши и посред- ством клавиш-стрелок поставим в какие-либо ячейки цифры и удалим их. Попробуем за- блокировать и разблокировать ячейки. Наконец, очистим поле судоку, вызовем окна сведе- ний о приложении и Qt и закроем приложение.
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


написать администратору сайта