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

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


Скачать 7.92 Mb.
НазваниеНиколай Прохоренок Владимир Дронов
Дата05.05.2023
Размер7.92 Mb.
Формат файлаpdf
Имя файлаПрохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен.pdf
ТипДокументы
#1111379
страница74 из 83
1   ...   70   71   72   73   74   75   76   77   ...   83
(+) — копирование головоломки в буфер обмена в полном формате (аналогичном тому, в котором сохраняется файл при выборе пункта Сохра- нить меню Файл);

Копировать компактно
— копирование головоломки в буфер обмена в компактном формате (аналогичном тому, в котором сохраняется файл при выборе пункта Сохра- нить компактно меню Файл);

Копировать для Excel
копирование головоломки в буфер обмена в формате, предназначенном для вставки в таблицу Microsoft Excel;

Вставить
(+) — вставка головоломки, скопированной ранее в любом фор- мате.
Если головоломка была скопирована в компактном формате, все ячейки, содержащие цифры, будут автоматически заблокированы;

Вставить из Excel
— вставка из буфера обмена головоломки, скопированной из таб- лицы Microsoft Excel;

Блокировать
() — блокировка активной ячейки;

Глава 32. Приложение «Судоку»
755

Блокировать все
() — блокировка всех ячеек. Не забываем, что блокируются только ячейки, содержащие цифры;

Разблокировать
() — разблокировка активной ячейки;

Разблокировать все
() — разблокировка всех ячеек;
 меню Справка:

О программе
— вывод окна со сведениями о приложении «Судоку»;

О Qt
— вывод окна со сведениями о самой библиотеке Qt.
При наведении курсора мыши на любой пункт меню в строке состояния появляется его раз- вернутое описание.
В панели инструментов присутствуют кнопки (в порядке слева направо): Новый, Открыть,
Сохранить
, Печать, Предварительный просмотр, Копировать, Вставить, Блокировать все и Разблокировать все. Они выполняют те же действия, что и одноименные пункты меню. При наведении курсора мыши на кнопку панели инструментов в строке состояния появляется ее развернутое описание, а рядом с кнопкой спустя небольшой промежуток времени появляется всплывающая подсказка (на рис. 32.2 курсор мыши наведен на кнопку
Сохранить
).
Сохранение головоломок выполняется в текстовых файлах с расширением svd.
Форматы, в которых хранятся головоломки, будут описаны позже.
Окно приложения при закрытии сохраняет свое местоположение и впоследствии, после следующего запуска, восстанавливает его.
32.3. Программирование приложения
Определившись, что должно делать наше приложение, мы можем начать его разработку. Но сначала выполним некоторые подготовительные действия.
32.3.1. Подготовительные действия
Создадим где-либо каталог, в котором будут находиться все файлы нашего будущего при- ложения. Назовем его, скажем, sudoku
. В этом каталоге создадим два вложенных каталога:
 images
— для хранения значков, которые будут выводиться в пунктах меню и кнопках панели инструментов, а также значки самого приложения;
 modules
— для хранения файлов с программными модулями Python, которые напишем впоследствии.
Найдем в Интернете значки для представления самого приложения, пунктов меню и кнопок панели инструментов. Сохраним их в каталоге images под именами svd.png
(значок прило- жения), а также new.png
, open.png
, save.png
, print.png
, preview.png
, copy.png
, paste.png
, lock.png и unlock.png
(значки кнопок).
32.3.2. Класс MyLabel: ячейка поля судоку
Начнем мы разработку приложения с создания класса
MyLabel
, который будет представлять отдельную ячейку поля судоку, — 81 такой компонент и составят это поле.
Наш класс должен «уметь» выводить на экран цифру, занесенную в ячейку, отображать обычное, активное и заблокированное состояния, реагировать на щелчки мышью, чтобы

756
Часть II. Библиотека PyQt 5 сообщить компоненту поля судоку, что та или иная ячейка стала активной. Помимо этого, ячейка должна иметь возможность принимать фоновый цвет — ведь именно разными цве- тами фона мы будем выделять группы ячеек на поле.
Визуально ячейка должна иметь размеры 30 × 30 пикселов и выводить цифру, выровненную по середине без отступов от границ компонента.
Бо´льшую часть необходимой функциональности мы можем получить, просто сделав класс
MyLabel производным от класса надписи
QLabel
. В самом деле, чтобы вывести на экран цифру, можно просто задать ее в качестве содержимого надписи, воспользовавшись унасле- дованным методом setText()
, — также несложно будет указать необходимые размеры и выравнивание. А задать для текста и фона нужные цвета мы сможем, привязав к ячейке таблицу стилей, для чего воспользуемся методом setStyleSheet()
, опять же, унаследо- ванным.
Теперь подумаем, какие атрибуты должен поддерживать класс
MyLabel
:
 colorYellow
, colorOrange
, colorGrey
, colorBlack и colorRed
— будут хранить RGB-коды, соответственно, желтого, оранжевого, светло-серого, черного и красного цветов. По- скольку значения этих атрибутов будут одинаковыми для всех экземпляров класса ячей- ки, мы сделаем их атрибутами объекта класса. (Если же сделать их атрибутами экземп- ляра класса, каждый экземпляр будет хранить свой собственный набор этих атрибутов, что приведет к избыточному расходу оперативной памяти.);
 isCellChange
— значение
True сообщит о том, что ячейка разблокирована, а значение
False
— что она заблокирована;
 fontColorCurrent
— текущий цвет текста: черный или красный;
 bgColorDefault
— заданный при создании ячейки цвет фона: оранжевый или светло- серый. Он понадобится нам, чтобы перевести ячейку из активного в неактивное состоя- ние;
 bgColorCurrent
— текущий цвет фона: желтый, оранжевый или светло-серый;
 id
— порядковый номер ячейки. Он понадобится нам, чтобы при щелчке мышью на ячейке сообщить компоненту поля судоку, какая ячейка стала активной.
Компонент
MyLabel будет входить в состав компонента поля, который мы напишем потом.
Он должен уведомлять компонент поля, когда текущая ячейка становится активной после щелчка мышью. Наилучший способ сделать это — объявить сигнал, который будет генери- роваться при щелчке. Обрабатывая этот сигнал, поле всегда будет «в курсе», на какой ячей- ке был выполнен щелчок.
Мы объявим в ячейке сигнал cellChangeFocus
. Он будет передавать обработчику единст- венный параметр целочисленного типа — номер ячейки, на которой пользователь щелкнул мышью.
Теперь определим набор необходимых методов нашего класса и представим в общих чер- тах, что должен делать каждый из них:
 mousePressEvent()
— этот метод следует переопределить, чтобы получить возможность обрабатывать щелчки мышью. Внутри него мы будем генерировать сигнал cellChangeFocus
;
 showColorCurrent()
задаст для ячейки цвета текста и фона, взятые из атрибутов fontColorCurrent и bgColorCurrent соответственно, и тем самым обновит визуальное состояние ячейки, или, как говорят программисты, перерисует ее. Этот метод мы будем

Глава 32. Приложение «Судоку»
757 вызывать после перевода ячейки из неактивного состояния в активное, из разблокиро- ванного — в заблокированное и наоборот;
 setCellFocus()
— переведет ячейку из неактивного состояния в активное. Сделать это очень просто — мы занесем в атрибут bgColorCurrent код желтого цвета (который хранится в атрибуте объекта класса colorYellow
) и вызовем метод showColorCurrent()
, чтобы обновить визуальное представление ячейки;
 clearCellFocus()
— переведет ячейку из активного состояния в неактивное. Мы занесем в атрибут bgColorCurrent значение цвета фона, взятое из атрибута bgColorDefault
(он, как мы помним, хранит код изначального цвета фона), и вызовем метод showColorCurrent()
, который перерисует ячейку;
 setCellBlock()
— переведет ячейку из разблокированного состояния в заблокирован- ное. Здесь мы занесем в атрибут isCellChange значение
False
, тем самым указывая, что ячейка заблокирована, присвоим атрибуту fontColorCurrent код красного цвета, храня- щийся в атрибуте объекта класса colorRed
, и перерисуем ячейку вызовом метода showColorCurrent()
;
 clearCellBlock()
— переведет ячейку из заблокированного состояния в разблокирован- ное. Для этого достаточно присвоить атрибуту isCellChange значение
True
, занести в ат- рибут fontColorCurrent значение черного цвета из атрибута объекта класса colorBlack и не забыть перерисовать ячейку с помощью метода showColorCurrent()
;
 setNewText()
— занесет в ячейку новое число, переданное ему в качестве единственного параметра в виде строки. Здесь нужно предварительно проверить, не заблокирована ли ячейка (не хранится ли в атрибуте isCellChange значение
False
), и уже потом вызывать унаследованный от класса
QLabel метод setText()
;
 конструктор должен принимать в качестве обязательных параметров номер создаваемой ячейки (он будет занесен в атрибут id
), цвет ее фона (его мы присвоим атрибутам bgColorDefault и bgColorCurrent
), а также необязательный параметр родителя parent
Еще он должен присвоить атрибуту isCellChange значение
True
— вновь созданная ячейка изначально должна быть разблокирована. Напоследок он перерисует ячейку, вы- звав метод showColorCurrent()
Код класса
MyLabel относительно невелик, несложен и полностью приведен в листинге 32.1.
Его следует сохранить в файле mylabel.py в каталоге modules
Э
ЛЕКТРОННЫЙ АРХИВ
Напомним, что файлы с кодами разрабатываемого в этой главе приложения находятся в папке sudoku сопровождающего книгу электронного архива (см. приложение).
Листинг 32.1. Класс
MyLabel from PyQt5 import QtCore, QtWidgets class MyLabel(QtWidgets.QLabel): colorYellow = "#FFFF90" colorOrange = "#F5D8C1" colorGrey = "#E8E8E8" colorBlack = "#000000" colorRed = "#D77A38"

758
Часть II. Библиотека PyQt 5 changeCellFocus = QtCore.pyqtSignal(int) def __init__(self, id, bgColor, parent=None):
QtWidgets.QLabel.__init__(self, parent) self.setAlignment(QtCore.Qt.AlignCenter) self.setFixedSize(30, 30) self.setMargin(0) self.setText("") if id < 0 or id > 80: id = 0 self.id = id self.isCellChange = True self.fontColorCurrent = self.colorBlack self.bgColorDefault = bgColor self.bgColorCurrent = bgColor self.showColorCurrent() def mousePressEvent(self, evt): self.changeCellFocus.emit(self.id)
QtWidgets.QLabel.mousePressEvent(self, evt) def showColorCurrent(self): self.setStyleSheet("background-color:" + self.bgColorCurrent +
";color:" + self.fontColorCurrent + ";") def setCellFocus(self): self.bgColorCurrent = self.colorYellow self.showColorCurrent() def clearCellFocus(self): self.bgColorCurrent = self.bgColorDefault self.showColorCurrent() def setCellBlock(self): self.isCellChange = False self.fontColorCurrent = self.colorRed self.showColorCurrent() def clearCellBlock(self): self.isCellChange = True self.fontColorCurrent = self.colorBlack self.showColorCurrent() def setNewText(self, text): if self.isCellChange: self.setText(text)
В принципе, объяснять здесь более особо нечего — все было рассказано ранее. Нужно лишь отметить пару чисто технических деталей:
 в методе mousePressEvent()
, выполнив все необходимые действия, а именно, сгенериро- вав сигнал changeCellFocus
, мы в обязательном порядке вызываем тот же метод супер- класса. Если этого не сделать, возможны проблемы;

Глава 32. Приложение «Судоку»
759
 в методе showColorCurrent()
мы вызовом метода setStyleSheet()
привязываем к наше- му компоненту таблицу стилей, которая установит для ячейки цвета текста и фона.
(Прочие параметры для компонента мы установим в таблице стилей, привязанной к компоненту основного окна, которую создадим позже.)
32.3.3. Класс Widget: поле судоку
Класс
Widget представит само поле судоку, составленное из 81-го компонента
MyLabel
, написанного ранее, и набора из 10-ти обычных кнопок, с помощью которых пользователь будет вставлять цифры в ячейки.
Сразу после создания компонент
Widget должен вывести на экран поле судоку и набор кно- пок. Он даст пользователю возможность делать ячейки активными с помощью клавиш- стрелок (активизация посредством щелчка мышью уже реализована нами в классе
MyLabel
), устанавливать в ячейки цифры либо клавишами, либо кнопками из набора и удалять цифры, опять же, клавишами или специальной кнопкой. Это основная функциональность компо- нента.
Что касается дополнительной функциональности, то ее мы будем реализовывать по частям.
И сейчас мы сделаем лишь очистку поля, блокировку и разблокировку его ячеек.
Поскольку функциональности, специфической для какого-либо уже имеющегося в библио- теке компонента, нам не требуется, класс
Widget мы сделаем производным от класса
QWidget
Необходимый нам набор атрибутов класса очень невелик:
 cells
— массив ячеек поля судоку — экземпляров класса
MyLabel
. Мы сохраним этот массив в атрибуте, поскольку в других методах этого класса нам понадобится получать к нему доступ;
 idCellInFocus
— порядковый номер ячейки, являющейся активной в настоящий момент.
Его нужно знать в любом случае — хотя бы для того, чтобы визуально выделить актив- ную ячейку, вызвав у нее метод setCellFocus()
(см. разд. 32.3.2).
Набор методов, поддерживаемый классом
Widget
, будет более объемным (даже с учетом того, что мы еще не реализовали в классе дополнительную функциональность). Поскольку эти методы сложнее таковых у класса
MyLabel
, мы рассмотрим их вкратце, не углубляясь в технические детали — полное их описание будет приведено далее, вместе с их кодом:
 onChangeCellFocus()
— обработчик сигналов changeCellFocus всех ячеек, что имеются в поле. (Упомянутый здесь сигнал, как мы помним, генерируется при щелчке на ячейке мышью.) Он получит с единственным параметром номер ячейки, на которой был выпол- нен щелчок, и активизирует ее;
 keyPressEvent()
— этот метод следует переопределить, чтобы получить возможность обрабатывать нажатия клавиш клавиатуры. Здесь мы будем, в зависимости от нажатой клавиши, перемещать по полю фокус выделения, ставить цифры в ячейки и очищать их;
 onBtnClicked()
(где

— число от 0 до 8 или буква
X
) — обработчики щелчков на кнопках 1...9 и Х. Они будут ставить в ячейку соответствующую цифру или же очищать ячейку;
 onClearAllCells()
— очистит поле судоку. Будет вызываться при выборе пункта Новый меню Файл или нажатии кнопки Новый панели инструментов;
 onBlockCell()
— заблокирует активную ячейку, если она содержит цифру и еще не за- блокирована. Будет вызываться при выборе пункта Заблокировать меню Правка;

760
Часть II. Библиотека PyQt 5
 onBlockCells()
— заблокирует все ячейки, содержащие цифры и не заблокированные.
Будет вызываться при выборе пункта Заблокировать все меню Правка или нажатии кнопки Заблокировать все панели инструментов;
 onClearBlockCell()
— разблокирует активную ячейку, если она заблокирована. Будет вызываться при выборе пункта Разблокировать меню Правка;
 onClearBlockCells()
— разблокирует все заблокированные ячейки. Будет вызываться при выборе пункта Разблокировать все меню Правка или нажатии кнопки Разблоки- ровать все панели инструментов;
 конструктор, который создаст все необходимые компоненты и выполнит привязку обра- ботчиков к сигналам.
Весь код класса
Widget мы сохраним в файле widget.py в каталоге modules
. Поскольку этот код довольно велик, мы рассмотрим его по частям.
32.3.3.1. Конструктор класса Widget
Конструктор — самый сложный метод класса
Widget
. Поэтому мы не станем приводить его полный листинг, а рассмотрим его по фрагментам. from PyQt5 import QtCore, QtGui, QtWidgets
Помимо модулей
QtCore и
QtWidgets
, которые понадобятся нам уже сейчас, мы импортиру- ем модуль
QtGui
. Объявленные в нем классы пригодятся нам позже, при реализации печати. from modules.mylabel import MyLabel
Не забываем импортировать из модуля mylabel.py
, что хранится в каталоге modules
, класс
MyLabel
, представляющий отдельную ячейку и написанный нами ранее. class Widget(QtWidgets.QWidget): def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent) self.setFocusPolicy(QtCore.Qt.StrongFocus)
По умолчанию экземпляр класса
QWidget или производного от него класса не может прини- мать фокус ввода. Чтобы дать ему возможность принимать фокус ввода при щелчке мышью и переходе нажатием клавиши , мы вызовем у него метод setFocusPolicy()
, передав ему в качестве параметра атрибут
StrongFocus vBoxMain = QtWidgets.QVBoxLayout()
Поскольку само поле и набор кнопок будут располагаться друг над другом, мы используем для их размещения контейнер
QVBoxLayout frame1 = QtWidgets.QFrame() frame1.setStyleSheet(
"background-color:#9AA6A7;border:1px solid #9AA6A7;")
Поле (которое будет создано контейнером-сеткой
QGridLayout
) мы поместим в панель с рамкой
QFrame
. Для этой панели с помощью таблицы стилей укажем тонкую рамку и фон одинакового темно-серого цвета. Панель с рамкой займет все выделенное под него про- странство контейнера, а поле судоку поместится в центре этой панели. В результате этого будет казаться, что поле окружено толстой темно-серой рамкой, что выглядит весьма эффектно. grid = QtWidgets.QGridLayout() grid.setSpacing(0)

Глава 32. Приложение «Судоку»
761
Создаем сетку
QGridLayout
, которая сформирует само поле. idColor = (3, 4, 5, 12, 13, 14, 21, 22, 23,
27, 28, 29, 36, 37, 38, 45, 46, 47,
33, 34, 35, 42, 43, 44, 51, 52, 53,
57, 58, 59, 66, 67, 68, 75, 76, 77)
Объявляем массив, хранящий номера ячеек, которые должны быть выделены светло-серым фоном. self.cells = [MyLabel(i, MyLabel.colorGrey if i in idColor else

MyLabel.colorOrange) for i in range(0, 81)]
Создаем список из 81-й ячейки
MyLabel
, сохранив его в атрибуте cells класса
Widget
. Здесь мы используем выражение генератора списка, которое позволит нам радикально упростить код. Если номер создаваемой ячейки имеется в объявленном ранее массиве, задаем для нее светло-серый цвет фона, в противном случае — оранжевый. self.cells[0].setCellFocus() self.idCellInFocus = 0
1   ...   70   71   72   73   74   75   76   77   ...   83


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