Pull to refresh

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

Reading time7 min
Views12K
Благодаря хорошим людям автор этого цикла статей получил инвайт и все последующие статьи будут опубликоваться им, поэтому не присваивайте эту работу мне. Я просто его друг. ;)
image

Часть №2


     В прошлый раз вы узнали о том, как работает PyQt4 и написали ваше первое приложение!
Текстовые метки, пусть даже и с форматированием — это хорошо, но, думаю, вам потребуется написать что-то более сложное и скорее всего требующее взаимодействиея с пользователем. Qt4 содержит множество классов, обеспечивающих это. Он так же позволяет устанавливать реакции программы на действия со стороны пользователей, и, кстати, весьма изящьным способом. Но давайте перейдем от слов к делу и напишем простую программу. В прошлый раз уже было сказано, как запускать ее, поэтому останавливаться на этом не будем.

1	#!/usr/bin/python
2	from PyQt4 import Qt
3	import sys
4	if __name__ == "__main__" :
5		app = Qt.QApplication(sys.argv)
6		button = Qt.QPushButton("Exit")
7		button.resize(200, 70)
8		button.show()
9		app.exec_()


     Как и в прошлый раз, мы выполнили почти те же самые действия: создали объект app от класса QApplication, создали виджет, настроили его, показали и запустили приложение на выполнение. Погодите, настроили? Но ведь в прошлый раз этого небыло! Правильно. Если вы помните, я говорил в прошлый раз о том, что в начале все виджеты Qt создаются невидимыми, чтобы позволить настроить их и затем уже вывести на экран. Вот теперь это нам и пригодилось. В строке 6 мы создали кнопку с текстом «Exit», а в строке 8 сделали ее видимой. А вот в строке 7 мы произвели ее настройку — изменили размер. Виджеты Qt4 имеют большое количество разнообразных настроек, что позволяет создавать гибкие интерфейсы. В данном случали мы использовали ее свойство — размер, изменив его на 200x70 пикселей.
     Возможно, вы довольны результатом, но… На кнопке написано «Exit», но когда мы нажимаем на нее, ничего не происходит. Это потому, что мы не установили реакцию нажатие кнопки. Попробуем сделать это, добавивив одну строчку:

1	#!/usr/bin/python
2	from PyQt4 import Qt
3	import sys
4	if __name__ == "__main__" :
5		app = Qt.QApplication(sys.argv)
6		button = Qt.QPushButton("Exit")
7		button.resize(200, 70)
8		Qt.QObject.connect(button, Qt.SIGNAL("clicked()"), sys.exit)
9		button.show()
10		app.exec_()


     Запускаем, и видим, что внешне приложение ничуть не изменилось. Однако попробуйте теперь нажать на кнопку… Приложение закрылось! Вот и реакция на действие пользователя, ее обеспечитвает строка 8. Выглядит она немного необычно, но на самом деле все просто. Мы вызвали статическую функцию connect(), указав ей в качестве параметра объект, который посылает сигнал, далее сам сигнал, затем функцию, которая должны выполняться при поступлении этого сигнала. Что такое сигнал, спросите вы? Тут пожалуй следует сделать небольшое отступление и рассказать о концепции «Сигналов и Слотов».
     Виджеты Qt генерирую при определенных событиях сигналы (кстати, никак не связанные с сигналами UNIX). QPushButton, например, генерирует сигнал clicked() при нажатии на кнопку. Qt позволяет устанавливать реакцию на поступающие от виджетов сигналы и выполнять так называемые слоты — это функции, которые должны вызываьться при поступлении сигналов.
     «Сигналы и Слоты» играют очень важную роль, позволяя соединять разные объекты, ничего не знающие друг о друге. Для подключения сигналов ко слотам используется функция connect() класса QObject (вообще-то, почти все объекты Qt так или иначе являются потомками QObject, поэтому содержат его функции-методы). В классическом Qt для C++ синтаксис вызова connect() выглядит следующим образом:

	connect(отправитель, SIGNAL(сигнал()), получатель, SLOT(слот()))


     Это немного отличается от того, что мы использовали, но обо всем по порядку. «Отправитель» — это объект, который генерировал сигнал: в нем произошло какое-то событие. «Сигнал()» — это имя сигнала, который был выработан. «Получатель», это обект, который должен отреагировать на сигнал, ему принадлежит функция «слот()», выполняемая теперь при поступлении сигнала. В C++ макросы SIGNAL() и SLOT() — это часть синтаксиса, но в Python используется несколько другой синтаксис вызова. Поскольку в Python необходимо всегда указывать, к какому объекту принадлежит нужная функция при вызове, разработчики создали более короткую форму записи для PyQt4 (я рекомендую использовать именно ее, но форма C++ тоже может использоваться):

	connect(отправитель, SIGNAL("сигнал()"), слот_получателя)


     Слот получателя — это функция, которая должна реагировать на сигнал. Она может не принадлежать ни одномуклассу, как например sys.exit(). Это более гибкий синтаксис. Суть одна и та же: при поступлении сигнала выполняется функция-слот. В этом синтаксисе нужно описывать слот так (если функция является членом определенного класса): получатель.функция без скобок и параметров. Однако предусмотрены еще некоторые возможности.
     Сигнал можно подключать к другому сигналу (используя форму записи, похожую на C++), в этом случае при генерировании одного сигнала будет генерироваться и второй сигнал:

	connect(отправитель, SIGNAL("сигнал1()"), получатель, SIGNAL("сигнал2()"))


     Один сигнал можно подключать к нескольким слотам, причем выполняться они будут в произвольном порядке, и несколько сигналов к одному слоту. И наконец, связь можно аннулировать (можно использовать и короткую форму записи для PyQt4):

	disconnect(отправитель, SIGNAL("сигнал()"), получатель, SLOT("слот()"))


     Однако, возможности на этом не ограничиваются. Сигналы могут нести в себе определенные значения переменных, а слоты могут их принимать, например:

	Qt.QObject.connect(slider, Qt.SIGNAL("valueChanged(int)"), spinbox.setValue)


     или то же самое в форме для C++ (привыкайте к форме для Python, я буду использовать именно ее):

	Qt.QObject.connect(slider, Qt.SIGNAL("valueChanged(int)"), spinbox, Qt.SLOT("setValue(int)"))


     Как видим, переменные описываются списком типов в скобках. При генерировани сигнала ему даются все необходимые переменные, а слот может иметь параметр, и тогда он примет значения, которые несет сигнал. Параметры должны задаваться в одинаковом порядке и иметь одинаковый тип. Однако сигнал может иметь больше параметров, чем слот, тогда «лишние» параметы просто игнорируются. Если параметры имеют разные типы, то во время выполнения программы будет выдано соответствующее предупреждение.
     Чтож, давайте теперь попробуем написать что-то интересное, используя наши знания о сигналах и слотах. Это будет приложение, в котором можно ввести число от 0 до 100 используя либо ползунок либо наборный счетчик. Причем, при изменении числа в одном из виджетов будет происходить соответствующее изменение и в другом виджете, это свойство мы реализуем как раз через сигналы и слоты.

1	#!/usr/bin/python
2	from PyQt4 import Qt
3	import sys
4	if __name__ == "__main__" :
5		app = Qt.QApplication(sys.argv)
6
7		window = Qt.QWidget()
8		window.setWindowTitle("Enter a number")
9
10		layout = Qt.QHBoxLayout()
11		window.setLayout(layout)
12
13		slider = Qt.QSlider(Qt.Qt.Horizontal)
14		slider.setRange(0, 100)
15		layout.addWidget(slider)
16
17		spinbox = Qt.QSpinBox()
18		spinbox.setRange(0, 100)
19		layout.addWidget(spinbox)
20
21		Qt.QObject.connect(slider, Qt.SIGNAL("valueChanged(int)"), spinbox.setValue)
22		Qt.QObject.connect(spinbox, Qt.SIGNAL("valueChanged(int)"), slider.setValue)
23
24		slider.setValue(50)
25
26		window.show()
27
28		app.exec_()


     Наше приложение стало больше. Давайте разберемся.
     В строках 7-8 создается и настраивается виджет-окно. Я уже упоминал, что окном может быть любой виджет, и тут я применли абстрактный клас QWidget (который наследуют все виджеты Qt, кстати). В 8 строке устанавливается заголовок окна.
     В строках 10-11 появилось что-то новенькое. Это класс-менеджер компоновки. В Qt4 имеются три основных класса, отвечающих за компоновку виджетов:
     QHBoxLayout — группирует виджеты горизонтально;
     QVBoxLayout — группирует виджеты вертикально;
     QGridLayout — группирует виджеты по ячейкам сетки.
     С помощью этих менеджеров можно создавать какие угодно визуальные комбинации виджетов: можно не только размещать в Layout`ах виджеты, но и другие Layout`ы.
     В данном случае мы использовали менеджер горизонтальной компоновки, и установили его главным менеджером (window.setLayout()) для нашегго окна. В последствии, мо сможем после создания виджетов размещать их внутри менеджера, а он сам распределит их и даст оптимальные размеры им. В строке 13-14 создается и настраивается горизонтальный ползунок (устанавливается диапазон допустимых значений), а в строке 15 он помещается внутрь менеджера компоновки. Строки 17-19 аналогичны.
     Строки 20-21 соединяют два виджета между собой. Оба они имеют сигналы «valueChanged(int)», генерирующиеся при изменении значения пользователем, и слоты «setValue(int)», которые устанавливают новое значение. Сигнал о изменении значения в одном виджете соединяются со слотом другого виджета, передавая новое значение.
     В строке 24 для ползунка устанавливается значение 50, затем строка 26 делает видимым окно вместе со всеми виджетами, содержащимися в нем, и строка 28 «запускает» наше приложение.
     Стоит подробно описать, что же происходит при выполнении строки 24. Значение объекта slider изменяется, и он генерирует сигнал «valueChanged(int)», несущий в себе еще и новое значение. Сигнал приходит объекту spinbox и он тоже устанавливает себе такое же значение с помощью слота-функции «setValue(int)», которая принимает один аргумент — новое значение. Затем, поскольку значение spinbox изменилось, он тоже генерирует соответствующий сигнал, который приходит объекту slider. Но slider уже содержит значение, котороеприслал spinbox (ведь slider первым сообщил о новом значении), поэтому он никак не реагирует на новый сигнал, просто пропуская его. Это не позволяет уйти программе в бесконечный цикл (в своих виджетах, если вы будете создавать подобные сигналы и слоты, вы должны предусмотреть такие ситуации и создать защиту от них). Строку 24 можно было бы заменить на «spinbox.setValue(50)», это привело бы точно к такоу же результату, оба виджета имели бы одно и то же значение, но spinbox был бы первым, кто сообщил бы об изменении значения. То же самое происходит и при воздействии пользователся на эти виджеты: при этом генерируются те же самые сигналы и все происходит по тому же алгоритму.
     Как можно увидеть, взаимодействие с пользователем и настройка внешнего вида программы легко обеспечиваются средствами Qt. Общих подход выглядит так: создать окно, создать менеджеры компоновки и разместить их, затем создать виджеты и сразу же настроить их и разместить внутри менеджера компоновки, создать необходимые связи сигналов и слотов, дать значение по умолчанию для тех виджетов, которые влияют на другие через это значение (как строка 28). Затем окно можно сделать видимым или оставить до лучших времен, если в вашей программе много окон, можно создать все их заранее, и открывать по мере надобности. Такой подход считается наиболее правильным при работе с Qt.
image
На этом пока всё, ждите часть №3, спасибо за внимание!

Tags:
Hubs:
Total votes 55: ↑52 and ↓3+49
Comments53

Articles