ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 05.12.2023
Просмотров: 870
Скачиваний: 3
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
750
Часть II. Библиотека PyQt 5 ll = [] lSize = settings.beginReadArray("Список") for i in range(lSize): settings.setArrayIndex(i) ll.append(settings.value("Элемент")) settings.endArray() print(ll) settings.clear()
В консоли это приложение выведет следующее:
['Python', 'Ruby', 'PHP', 'JavaScript']
Сохраняем список
Считываем список
['Python', 'Ruby', 'PHP', 'JavaScript']
31.3. Вспомогательные методы класса QSettings
Теперь рассмотрим вспомогательные методы класса
QSettings
, которые могут пригодиться в некоторых случаях.
status()
— возвращает обозначение состояния, в котором пребывает хранилище на- строек после выполнения очередной операции, выраженное в виде одного из следующих атрибутов класса
QSettings
:
•
NoError
—
0
— операция была успешно выполнена, и никаких ошибок не возникло;
•
AccessError
—
1
— возникла ошибка доступа к реестру или INI-файлу (возможно, была произведена попытка чтения недоступного для текущего пользователя ключа реестра или файла или же запись в файл, недоступный для записи);
•
FormatError
—
2
— была выполнена попытка открыть некорректно сформированный
INI-файл;
isWritable()
— возвращает
True
, если хранилище настроек доступно для записи, и
False в противном случае (например, если хранилищем является INI-файл, на запись в который текущий пользователь не имеет прав).
31.4. Где хранятся настройки?
Осталось выяснить, где же хранятся настройки, которые мы записали в хранилище. Не бу- дем рассматривать случай, когда экземпляр класса
QSettings создан с применением пятого формата конструктора, в котором месторасположение хранилища указано напрямую — в виде конкретного пути к ключу реестра или INI-файлу (за подробностями — к разд. 31.1).
Сосредоточимся на первых четырех форматах, в которых месторасположение хранилища не указано.
Если выполняется сохранение в реестр Windows (в качестве типа хранилища указаны атрибуты
NativeFormat
,
Registry32Format или
Registry64Format
):
• если выполняется сохранение на уровне текущего пользователя (в качестве диапазо- на указан атрибут
UserScope
):
если название приложения указано (т. е. задан параметр application конструкто- ра) — в ветви
HKEY_CURRENT_USER\Software\<Название организации>\<Название при- ложения>
;
Глава 31. Сохранение настроек приложений
751
если название приложения не указано (т. е. параметр application конструктора не задан) — в ветви
HKEY_CURRENT_USER\Software\<Название организации>\
OrganizationDefaults
(похоже, что в таком случае PyQt считает, что заданные настройки применяются сразу ко всем приложениям, разработанным данной организацией);
• если выполняется сохранение на уровне системы (в качестве диапазона указан атри- бут
SystemScope
):
если название приложения указано — в ветви
HKEY_LOCAL_MACHINE\Software\<Название организации>\<Название приложения>
;
если название приложения не указано — в ветви
HKEY_LOCAL_MACHINE\Software\<Название организации>\OrganizationDefaults
Приложения, работающие под управлением 32-разрядной редакции Python на 64-раз- рядной редакции Windows, если указан тип хранилища, отличный от
Registry64Format
, сохраняют настройки в ветвях
HKEY_LOCAL_MACHINE\Software\WOW6432node\<Название организации>\<Название приложения>
и
HKEY_LOCAL_MACHINE\Software\WOW6432node\
<Название организации>\OrganizationDefaults соответственно.
Если выполняется сохранение в INI-файл (в качестве типа хранилища указан атрибут
IniFormat
):
• если выполняется сохранение на уровне текущего пользователя:
если название приложения указано — в файле
<каталог пользовательского про- филя>\AppData\Roaming\<Название организации>\<Название приложения>.ini
;
если название приложения не указано — в файле
<каталог пользовательского профиля>\AppData\Roaming\<Название организации>.ini
;
• если выполняется сохранение на уровне системы:
если название приложения указано — в файле
<корневой каталог системного дис- ка>\ProgramData\<Название организации>\<Название приложения>.ini;
если название приложения не указано — в файле
<корневой каталог системного диска>\ProgramData\<Название организации>.ini
ГЛ А В А
32
Приложение «Судоку»
В завершение в качестве примера напишем на языке Python с применением библиотеки
PyQt приложение «Судоку». Оно предназначено для создания и решения головоломок судоку и позволит, помимо всего прочего, сохранять головоломки в файлах, загружать их из файлов и выводить на печать.
Изначально приложение было разработано Н. Прохоренком в 2011 году на языке C++. Для этой книги оно переписано на Python и несколько усовершенствовано В. Дроновым в конце
2017 года.
32.1. Правила судоку
Судоку — традиционная японская числовая головоломка (иногда ее неправильно называют магическим квадратом). Далее приведены правила ее решения (полное описание судоку можно найти по интернет-адресу https://ru.wikipedia.org/wiki/Судоку).
Поле судоку представляет собой квадрат, разбитый на 81 ячейку (9 столбцов по 9 строк).
Каждые 9 ячеек объединены в группу 3 × 3 — итого получается 9 групп.
В каждую ячейку поля можно подставить только одну цифру от 1 до 9.
Ячейки группы не должны содержать одинаковых цифр.
Одна и та же цифра должна присутствовать в каждой строке и каждом столбце поля только один раз.
На рис. 32.1 приведен пример решенной судоку.
Рис. 32.1. Решенная судоку (группы выделены утолщенными рамками)
Глава 32. Приложение «Судоку»
753
Как правило, судоку решают вдвоем: один игрок произвольно расставляет цифры в некото- рых ячейках поля, а второй, собственно, решает головоломку.
32.2. Описание приложения «Судоку»
Приложение «Судоку» выполнено в традиционном ключе, присущем обычным Windows- приложениям. Ее окно (рис. 32.2) включает в себя главное меню, панель инструментов, ос- новное содержимое — поле судоку и набор кнопок для установки цифр в ячейки, и строку состояния.
В самом поле судоку группы ячеек выделены различными цветами фона: оранжевым и светло-серым. Установленные в них цифры выводятся черным цветом.
Одна из ячеек является активной — именно с активной ячейкой осуществляется взаимо- действие. Активная ячейка закрашена желтым цветом (на рис. 32.2 это ячейка с цифрой 4).
Чтобы сделать какую-либо ячейку активной, следует:
либо, пользуясь клавишами-стрелками, переместить на нее желтый фокус выделения;
либо просто щелкнуть на ней мышью.
Чтобы установить в активную ячейку какую-либо цифру, следует:
либо нажать соответствующую кнопку в наборе, расположенном ниже поля;
либо нажать соответствующую цифровую клавишу.
Чтобы убрать цифру из активной ячейки, нужно:
либо нажать кнопку Х, что находится в расположенном под полем наборе;
либо нажать клавишу пробела,
Рис. 32.2. Окно приложения «Судоку»
754
Часть II. Библиотека PyQt 5
Чтобы случайно не занести в какую-либо ячейку другую цифру, есть возможность заблоки- ровать ее. Для этого следует сделать нужную ячейку активной и нажать клавишу
В заблокированной ячейке цифра выводится красным шрифтом (на рис. 32.2 заблокирована ячейка с цифрой 5). Отметим, что блокировать можно только ячейки, содержащие цифры.
Снять блокировку с ячейки можно, сделав ее активной и нажав клавишу
Главное меню приложения содержит следующие пункты:
меню Файл:
•
Новый
(с ним связана комбинация клавиш
•
Открыть
(
•
Сохранить
(
При выборе этого пункта производится сохранение в полном формате, т. е. для каж- дой ячейки, помимо находящейся в ней цифры, сохраняется признак того, заблоки- рована ли она;
•
Сохранить компактно
— сохранение головоломки в файле в компактном формате.
Компактный формат не предусматривает хранения признака блокировки ячейки.
Вследствие этого файл, сохраненный в компактном формате, вдвое меньше полно- форматного.
При открытии файла, сохраненного в компактном формате, производится автомати- ческая блокировка всех ячеек, содержащих цифры;
•
Печать
(
) — вывод головоломки на печать;
•
Предварительный просмотр
— просмотр головоломки в том виде, в котором она будет выведена на печать;
•
Параметры страницы
— настройка параметров печатаемой страницы;
•
Выход
(
) — завершение работы приложения;
меню Правка:
•
Копировать
1 ... 69 70 71 72 73 74 75 76 ... 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()
— этот метод следует переопределить, чтобы получить возможность обрабатывать нажатия клавиш клавиатуры. Здесь мы будем, в зависимости от нажатой клавиши, перемещать по полю фокус выделения, ставить цифры в ячейки и очищать их;
onBtn
(где
— число от 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 или производного от него класса не может прини- мать фокус ввода. Чтобы дать ему возможность принимать фокус ввода при щелчке мышью и переходе нажатием клавиши
, передав ему в качестве параметра атрибут
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
Делаем активной ячейку с номером 0 и заносим тот же номер в атрибут idCellInFocus клас- са
Widget
. В результате изначально активной станет самая первая ячейка поля. i = 0 for j in range(0, 9): for k in range(0, 9): grid.addWidget(self.cells[i], j, k) i += 1
Помещаем все созданные ячейки в сетку. for cell in self.cells: cell.changeCellFocus.connect(self.onChangeCellFocus)
У всех ячеек задаем для сигнала changeCellFocus обработчик — метод onChangeCellFocus()
класса
Widget
, пока еще не объявленный. frame1.setLayout(grid) vBoxMain.addWidget(frame1, alignment=QtCore.Qt.AlignHCenter)
Помещаем сетку в панель с рамкой и добавляем последнюю в контейнер
VBoxLayout
, указав для нее горизонтальное выравнивание по середине. frame2 = QtWidgets.QFrame() frame2.setFixedSize(272, 36)
Набор кнопок, с помощью которых будет выполняться занесение цифр в ячейки, мы помес- тим в другую панель с рамкой
QFrame
. Это позволит нам привязать к кнопкам таблицу сти- лей, задающую для них представление. Для панели мы обязательно зададим фиксированные размеры — иначе их установит сам PyQt согласно своему разумению, которое вряд ли сов- падет с нашим. hbox = QtWidgets.QHBoxLayout() hbox.setSpacing(1)
Кнопки у нас будут выстроены по горизонтали, следовательно, наилучший вариант — по- местить их в контейнер
QHBoxLayout btns = [] for i in range(1, 10): btn = QtWidgets.QPushButton(str(i))
762
Часть II. Библиотека PyQt 5 btn.setFixedSize(27, 27) btn.setFocusPolicy(QtCore.Qt.NoFocus) btns.append(btn)
Создаем кнопки 1...9, задаем для них размеры 27 × 27 пикселов и добавляем в специально созданный для этого список. Также для каждой кнопки мы указываем, что она не должна принимать фокус ввода (для чего вызовем у нее метод setFocusPolicy()
с параметром
NoFocus
), — это нужно для того, чтобы поле судоку при нажатии любой из этих кнопок не теряло фокус, и пользователь смог продолжать манипулировать в нем с помощью клавиш. btn = QtWidgets.QPushButton("X") btn.setFixedSize(27, 27) btns.append(btn)
Таким же образом создаем кнопку Х, которая уберет цифру из ячейки. for btn in btns: hbox.addWidget(btn)
Помещаем все кнопки в контейнер
QHBoxLayout btns[0].clicked.connect(self.onBtn0Clicked) btns[1].clicked.connect(self.onBtn1Clicked) btns[2].clicked.connect(self.onBtn2Clicked) btns[3].clicked.connect(self.onBtn3Clicked) btns[4].clicked.connect(self.onBtn4Clicked) btns[5].clicked.connect(self.onBtn5Clicked) btns[6].clicked.connect(self.onBtn6Clicked) btns[7].clicked.connect(self.onBtn7Clicked) btns[8].clicked.connect(self.onBtn8Clicked) btns[9].clicked.connect(self.onBtnXClicked)
Привязываем к сигналам clicked всех этих кнопок соответствующие обработчики — мето- ды класса поля, которые объявим потом. frame2.setLayout(hbox) vBoxMain.addWidget(frame2, alignment=QtCore.Qt.AlignHCenter)
Помещаем контейнер с кнопками в панель с рамкой, а ее — во «всеобъемлющий» контей- нер
VBoxLayout
, не забыв указать горизонтальное выравнивание по середине. self.setLayout(vBoxMain)
И помещаем этот контейнер в компонент поля.
32.3.3.2. Прочие методы класса Widget
Теперь напишем код остальных методов класса
Widget
. Они существенно проще конструк- тора, и мы можем не рассматривать их по частям.
Листинг 32.2. Метод onChangeCellFocus() def onChangeCellFocus(self, id): if self.idCellInFocus != id and not (id < 0 or id > 80): self.cells[self.idCellInFocus].clearCellFocus() self.idCellInFocus = id self.cells[id].setCellFocus()