Программирование на PyQt4. Часть 3

Добро пожаловать на третье занятие нашего курса по изучению PyQt4! Надеюсь, что на прошлых лекциях вы меня внимательно слушали, потому что сегодня мы будем изучать очень важный и интересный вопрос — как создавать сложные интерфейсы с множеством элементов управления. Иными словами, сегодня мы поговорим о менеджерах компоновки виджетов.
Каждое мало-мальски уважающее себя приложение содержит чуть больше, чем одну кнопку в интерфейсе, поэтому очень важно научиться правильно располагать в окне все элементы управления. Зачастую их может быть не меньше десятка, а то и больше… Представьте, что было бы, если бы все элементы интерфейса были свалены в одну кучу — никакого изящества, красоты и удобства, неговоря уже о вообще возможности работать в такой программе.
Существует несколько способов группировки виджетов и один из самых старых — создание формы с жестко зафиксированными позициями каждого виджета. Такой подход очень часто практиковался еще сравнительно недавно. Одним из отличительных признаков использования такого подходя является игнорирование действий пользователя на сжатие и растяжение окна, когда виджеты уходят за его пределы, в случае уменьшения размера, или наоборот, не занимают все предоставленное место, когда пользователь увеличил размер окна. Смотрится, надо сказать, ужасно. Да и просчитывать размеры каждого элемента и точные координаты в окна — было кошмаром для любого программиста. Для окон, которые могли изменять свой размер и при этом подстраивать виджеты под новые пропорции, приходилось писать сотни строк кода, только чтобы правильно просчитывались позиции каждого из элементов управления. Но потом был изобретен другой способ — менеджеры компоновки, которые брали эту рутинную задачу на себя.
Для того, чтобы понять, что же такое менеджер компоновки, представим себе коробку с множеством ячеек. Коробку можно поставить вертикально (как подставку для дисков), а можно положить горизонтально (как книжную полку). В каждую ячейку кладется по одному виджету, и коробка сама подбирает для них лучший размер, исходя из доступного ей пространства и индивидуальных характеристик виджетов. Замечательный способ, не так ли? Разработчики Qt посчитали так же и снабдили свою библиотеку классами QHBoxLayout (компоновка виджетов по горизонтали) и QVBoxLayout (компоновка по вертикали). Итак, давайте подробнее рассмотрим, как же работает менеджер компоновки на примере QHBoxLayout.
Предположим, что в вашей коробке еще нет виджетов. Вы добавили в него одну кнопку, и таким образом получили один единственный элемент, который будет занимать все доступное коробке пространство:
+---------+ | | | B1 | | | +---------+
Но, как я уже говорил, в эту же коробку можно добавить еще элементов, что мы и делаем дальше, добавив кнопку B2 (виджеты размещаются слева на право для горизонтальных менеджеров и сверху вниз для вертикальных):
+----+----+ | | | | B1 | B2 | | | | +----+----+
И что же мы получили? Коробка сохранила свой прежний размер, но вместила в себя еще одну кнопку, несколько потеснив первую. Кнопки будут иметь одинаковый размер, равный половине от того, что мы имели в случае с одной кнопкой. Давайте посмотрим, как это выглядит в программном коде на Python. И на этот раз мы будем использовать специальный виджет, в качестве окна — QWidget. Он представляет собой некий абстрактный класс, от которого наследуются все остальные виджеты — QPushButton, QLabel и многие другие. Как уже говорилось, любой виджет может быть окном, чем, собственно, мы и воспользуемся:
00 from PyQt4 import Qt
01 import sys
02 if __name__ == "__main__" :
03 app = Qt.QApplication(sys.argv)
04
05 window = Qt.QWidget()
06
07 layout = Qt.QHBoxLayout()
08 layout.setContentsMargins(3, 3, 3, 3)
09 layout.setSpacing(3)
10 window.setLayout(layout)
11
12 button1 = Qt.QPushButton("B1")
13 layout.addWidget(button1)
14
15 button2 = Qt.QPushButton("B2")
16 layout.addWidget(button2)
17
18 window.show()
19
20 app.exec_()
Это приложение очень похоже на те, что мы делали ранее, но теперь мы добавили некоторые новые элементы. в строке 5 мы создаем окно приложения, в роли которого выступает QWidget. Далее, в строках 7-9 мы создаем и настраиваем нашу горизонтальную коробку (QHBoxLayout), устанавливаем количество свободного пространство с каждой из сторон коробки (setContentsMargins()) и расстояние между виджетами внутри коробки (setSpacing()). В строке 10 мы устанавливаем для нашего окна менеджер компоновки layout. Затем каждую созданную кнопку мы разместим внутри layout`а (строки 13 и 16). в строке 18 мы показываем пользователю наше окно. Опять же, как я говорил на прошлых занятиях, не нужно выполнять метод show() для каждого виджета, Qt сделает это за вас для тех элементов, которые явным образом находятся внтури какого либо окна (например, с помощью менеджера компоновки). В нашем случае кнопки находятся в layout, который, в свою очередь, в window, для него мы и выполняем метод show().
Думаю, суть вы уловили — помещая в менеджер компоновки виджеты, мы можем удобно расположить их внутри окна. Кроме того, внутрь одного менеджера компоновки может быть включен другой менеджер компоновки, и таким образом у нас будет формироваться сложная структура компоновки для виджетов.
Например, создадим три кнопки, две из которых будут находится друг над другом, а третья — правее их обоих. Для этого нам придется использовать два менеджера компоновки — один горизонтальный, и один вертикальный:
+----+----+ | B1 | | +----+ B3 | | B2 | | +----+----+
И вот как это будет выглядеть:
00 from PyQt4 import Qt
01 import sys
02
03 if __name__ == "__main__" :
04 app = Qt.QApplication(sys.argv)
05
06 window = Qt.QWidget()
07
08 layout1 = Qt.QHBoxLayout()
09 layout1.setContentsMargins(3, 3, 3, 3)
10 layout1.setSpacing(3)
11 window.setLayout(layout1)
12
13 layout2 = Qt.QVBoxLayout()
14 layout2.setContentsMargins(3, 3, 3, 3)
15 layout2.setSpacing(3)
16 layout1.addLayout(layout2)
17
18 button1 = Qt.QPushButton("B1")
19 layout2.addWidget(button1)
20
21 button2 = Qt.QPushButton("B2")
22 layout2.addWidget(button2)
23
24 button3 = Qt.QPushButton("B3")
25 layout1.addWidget(button3)
26
27 window.show()
28
29 app.exec_()
Думаю, здесь в пояснении нуждается только одна строка — 16, в которой внутрь layout1 добавляется с помощью addLayout() layout2, как обычный виджет. Такой подход позволяет группировать элементы как угодно и в каком угодно порядке.
Существует еще третий класс для компоновки элементов, который называется QGridLayout. В общем случае его можно представить себе как некоторую коробку, поделенную на равные ячейки. В каждую из ячеей можно положить по одному виджету, указава координаты этой ячейки. Этот менеджер очень часто используется в диалогах настройки, чтобы все параметры приложения выглядели, как таблица:
+---------+---------+---------+ | | | | | | | | +---------+---------+---------+ | | | | | | | | +---------+---------+---------+ | | | | | | | | +---------+---------+---------+
В случае использования QGridLayout методу addWidget() требуется дополнительно два параметра — строка и столбец, куда нужно добавить виджет. В качестве примера могу предложить следующую небольшую программку, создающее окно с кнопками, похожими на телефонную клавиатуру. Главное место в ней занимает цикл, создающий кнопки и размещающий их согласно своим индексам в менеджере. Как можно увидеть, принцип работы с ним почти такой же, как и с массивом.
00 from PyQt4 import Qt 01 import sys 02 03 class Buttons(Qt.QMainWindow) : 04 def __init__(self, parent = None) : 05 Qt.QMainWindow.__init__(self, parent) 06 07 self.main_widget = Qt.QWidget() 08 self.setCentralWidget(self.main_widget) 09 10 self.main_layout = Qt.QVBoxLayout() 11 self.main_widget.setLayout(self.main_layout) 12 13 self.buttons_layout = Qt.QGridLayout() 14 self.main_layout.addLayout(self.buttons_layout) 15 16 ##### 17 18 for row in range(4) : 19 for column in range(3) : 20 num = row*3 + column + 1 21 if num == 10 : num = 0 22 button = Qt.QPushButton(str(num)) 23 self.buttons_layout.addWidget(button, row, column) 24 if num == 0 : break 25 26 27 if __name__ == "__main__" : 28 app = Qt.QApplication(sys.argv) 29 buttons = Buttons() 30 buttons.show() 31 app.exec_()
Здесь мы имеем уже большее количество нововведений, чем раньше. Во-первых, мы создали отдальный класс-виджет для окна. Во-вторых, внутри нашего окна есть несколько лейаутов (далее, для краткости, я буду использовать этот термин, вместо «менеджер компоновки»), а так же достаточно интересный прием, который позволил значительно сократить количество кода (да-да, я про вложенные циклы говорю). Итак, давайте разберемся, что к чему.
В строке 3 мы создаем новый класс Buttons, наследуя его от класса Qt4 QMainWindow. QMainWindow практически всегда используется для реализации главного окна приложения, поскольку имеет множество удобным методов, таких, например, как работу с тулбарами и панелью статуса (как та штучка в вашем браузере, на которой отображается адрес ссылки, если вы наведете на нее курсор :-) ), группировкой виджетов и различных панельей инструментов. В принципе, в качестве окна можно было взять любой из виджетов, но правильнее пользоваться именно этим, поскольку вам, в случае расширения функциональности вашего приложения, не нужно будет перестраивать пол-программы. Короче, учитесь писать сразу правильно :-).
Функция, начинающаяся со строки 4 и продолжающаяся по строку 24 включительно представляет собой конструктор класса, который вызывается каждый раз при создании экземпляра нашего класса. Для Qt, в начале каждого конструктора необходимо явным образом вызывать метод __init__() наследуемого класса, передавая ему в качестве параметров ссылку на текущий (наш) класс и параметры, переданные конструктору нашего класса (Смотри строку 5).
В строке 7 мы создаем главный виджет для окна — такова специфика использования QMainWindow и устанавливаем его с помощью метода setCentralWindget() (который, после наследования QMainWindow, уже относится к нашему классу). Далее создается лейаут для главного виджета и устанавливается на него. Заметье, что при создании переменной мы приписываем к ней «префикс» self, обозначающий, что это будет глобальная для всего класса переменная и она сохраниться, что очень важно, даже при выходе из метода (в данном случае конструктора __init__). Если вы будете создавать объекты без этой приписки, то вы не сможете получить к ним доступ из других виджетов и вообще, могут происходит очень странные вещи, вроде исчезания элементов управления из окна.
И наконец, строки 18-24, самая интересная часть данного примера. Я решил сделать некое подобие телефоной клавиатуры, пронумерованной от 1 до 9 и 0 в конце. Вместо того, чтобы вручную создавать 10 кнопок и указывать их координаты для QGridLayout, я применил небольшую хитрость — сделал два вложенных цикла, которые обходят лейаут, как двумерный массив, и создают в каждом из ячеек кнопку с уникальным номером. В принципе работы этой штуки я предлагаю разобраться вам самим, если что-то не будет понятно, я с удовольствием отвечу на все вопросы в комментарии. Единственное, что здесь стоит пояснение — это то, что я НЕ писал self перед именем переменной button. Как я уже говорил, отсутсвие его черевато глюками, но только не в этот раз. Python уничтожает переменные, если они не используются. При каждом проходе цикла создается переменная button и уничтожается, но кнопка на форме сохраняется, как же так?.. Дело в том, что я сохраняю в явном виде каждый объект, указывая, что кнопки расположены в ячейках лейаута buttons_layout. При этом, не затирается предыдущий объект, и не уничтожаются старые, поскольку они используются классами Qt. Это совершенно банальная теория Python, которую желательно освежить в памяти, сходив, скажем, на python.org :-)
Прочитать часть 2
Прочитать часть 1



комментарии (16)