Глава 2. Первые приложения на PyQt5

Глава 2. Первые приложения на PyQt5

Технология создания каждой программы с использованием PyQt5 будет одинакова (по крайней мере, в этой книге). Работа начинается с построения графического интерфейса пользователя, а затем к нему добавляются функции обработки данных. Уже на этом этапе Вы увидите одно из важнейших достоинств PyQt: наличие среды разработки Qt Designer. Как видно из Рис. 1.6, Qt Designer находится в группе, созданной при установке PyQt. После открытия среды на экране появится диалоговое окно (см. Рис. 2.1):

Рис. 2.1 Диалоговое окно при запуске Qt Designer


Здесь нам предлагается выбрать, какой виджет будет главным в создаваемой программе. Мы выберем установленный по умолчанию виджет MainWindow. Для этого нужно нажать кнопку «Создать». На экране появляется чистая заготовка для дальнейшей работы (см. Рис. 2.2):

Рис. 2.2 Заготовка для создания нового графического интерфейса пользователя

Здесь нужно ненадолго остановиться и разобраться с панелями Qt Designer. Слева находится панель виджетов. Виджеты просто перетаскиваются мышью в нужное место заготовки. Справа располагаются Инспектор объектов, Редактор свойств, Редактор действий и Обозреватель ресурсов. Сразу под меню, вверху экрана, находится панель инструментов. Если активировать меню Вид, то будут видны все доступные панели. Все эти панели необходимы нам уже сейчас. Мы разберемся в них, создавая программы.

Создадим интерфейс программы, которая будет получать от пользователя текст и выводить на экран длину этого текста в символах - вполне лингвистическая задача.

Мы остановились на чистой заготовке. В нее надо добавить три виджета: строку ввода (QLineEdit), кнопку (QPushButton) и надпись (QLabel). Перетаскивая эти виджеты один за другим получим следующее (см. Рис. 2.3):

Рис. 2.3 Заполнение заготовки


Очень хорошо. Теперь измените текст надписи. Для этого выделите мышью надпись, чтобы она оказалась в синей рамке (см. Рис. 2.4):

Рис. 2.4 Выделенный виджет


Теперь в Редакторе свойств (панель в правой части экрана) отобразились свойства выделенного виджета. Надо найти свойство text, которое по умолчанию имеет значение TextLabel (см. Рис. 2.5):

Рис. 2.5 Редактор свойств для виджета QLabel


Это значение нужно изменить на «Длина Вашего текста». Теперь виджет отражает нужный нам текст. Его самого можно немного растянуть, чтобы текст был виден целиком (см. Рис. 2.6):

Рис. 2.6 Измененная надпись


Интерфейс готов. Его можно сохранить в файл myinterface.ui (место расположение файла надо оставить как есть, по умолчанию, т.е. в папке PyQt5). Однако сам по себе файл с расширением .ui не представляет никакой ценности, пока он не используется в нашей будущей программе. Есть несколько способов превратить файл ui в полноценный компонент программы на Python. Один из них - это конвертировать файл ui в файл py. Для этого Вам придется поработать в командной строке.

Если Вы работаете в Windows, то для выхода в командную строку нужно нажать на меню Пуск и ввести в появившееся окно ввода команду «cmd» (см. Рис. 2.7):

Рис. 2.7 Выход в командную строку

Нажмите Enter. Если все получилось правильно, то вы увидите следующее (см. Рис. 2.8):

Рис. 2.8 Командная строка


Работа в командной строке возвращает нас во времена операционной системы DOS, когда еще не было Windows, и людям приходилось общаться с компьютером посредством набора команд в строке. Можно даже немного преувеличенно сказать, что тогда каждый пользователь ПК должен был быть немного программистом.

Но вернемся к нашей задаче, посмотрим внимательно, что представляет собой командная строка. C:\Users\lenovo> означает, что мы находимся именно в этой папке (во времена DOS говорили не папка, а директория). Нам нужно выйти в директорию, в которой расположен файл myinterface.ui. На моей машине это C:\Python33\Lib\site-packages\PyQt5. Попасть туда не так просто. Для начала надо просто выйти в директорию диска С, т.е. выйти из директорий lenovo и Users. Наберите в строке команду cd.. (cd и две точки), нажмите Enter, мы поднялись на один уровень (см. Рис. 2.9):

Рис. 2.9 Использование команды cd..


Проделайте то же самое еще один раз. Теперь мы на диске C. Дальше можно или набрать сразу cd Python33\Lib\site-packages\PyQt5 или двигаться к цели по одной директории: cd Python33, затем cd Lib, затем cd site-packages, и наконец cd PyQt5. Разумеется, если у Вас другой путь, то надо следовать ему. Обратите внимание на то, что Copy/Paste в режиме командной строки не работает, все надо набирать руками. Итак, Вы в нужной директории (см. Рис. 2.10):

Рис. 2.10 Переход в нужную директорию


В этой же директории располагается файл pyuic5.bat. Именно он и нужен для конвертации файла myinterface.ui в myinterface.py. Наберите в командной строке следующее:

pyuic5 myinterface.ui -o myinterface.py -x

Тем самым мы указываем программе pyuic5, какой файл мы хотим конвертировать и каким образом. На первый взгляд ничего не произошло, но если посмотреть внимательно на содержание директории PyQt5, то будет заметно появление в ней файла myinterface.py. Файл можно скопировать в любое удобное для Вас место. Теперь откройте myinterface.py в IDLE и запустите (F5). Вот результат (см. Рис. 2.11):

Рис. 2.11 Интерфейс программы myinterface.py


Программа работает! Хотя она пока и не считает длину текста в строке ввода.

Подведем предварительный итог:

1. В среде разработки Qt Designer достаточно просто строить графический интерфейс пользователя.

2. Затем полученный файл .ui легко конвертируется в работающий файл .py.

3. Полученный файл .py НЕЛЬЗЯ конвертировать обратно в файл .ui и вносить в него изменения.

4. Дальше остается только добавить в файл .py функции обработки данных, но интерфейс изменять (если это необходимо) придется вручную, без Qt Designer.

Здесь у внимательного читателя может возникнуть справедливый вопрос: Неужели никто из программистов не видит неудобства в том, что после конвертации среда разработки Qt Designer остается недоступной? Ведь иногда приходится вносить в интерфейс программы значительные изменения, и лучше всего было бы это сделать мышью, а не кодом. Или придется переделывать всю программу заново?

Ответ: решение есть, мы его обсудим, но немного позже. В Qt Designer можно работать на любом этапе разработки программы.

Но пока у нас есть файл myinterface.py (см. Код 2.1):

Код 2.1 Программа myinterface.py

1.

# -*- coding: utf-8 -*-

2.

 

3.

# Form implementation generated from reading ui file 'myinterface.ui'

4.

#

5.

# Created: Tue Apr 22 17:15:14 2014

6.

# by: PyQt5 UI code generator 5.2.1

7.

#

8.

# WARNING! All changes made in this file will be lost!

9.

 

10.

from PyQt5 import QtCore, QtGui, QtWidgets

11.

 

12.

class Ui_MainWindow(object):

13.

def setupUi(self, MainWindow):

14.

MainWindow.setObjectName("MainWindow")

15.

MainWindow.resize(240, 320)

16.

self.centralwidget = QtWidgets.QWidget(MainWindow)

17.

self.centralwidget.setObjectName("centralwidget")

18.

self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)

19.

self.lineEdit.setGeometry(QtCore.QRect(60, 20, 113, 20))

20.

self.lineEdit.setText("")

21.

self.lineEdit.setObjectName("lineEdit")

22.

self.pushButton = QtWidgets.QPushButton(self.centralwidget)

23.

self.pushButton.setGeometry(QtCore.QRect(80, 60, 75, 23))

24.

self.pushButton.setObjectName("pushButton")

25.

self.label = QtWidgets.QLabel(self.centralwidget)

26.

self.label.setGeometry(QtCore.QRect(30, 120, 181, 20))

27.

self.label.setObjectName("label")

28.

MainWindow.setCentralWidget(self.centralwidget)

29.

self.menubar = QtWidgets.QMenuBar(MainWindow)

30.

self.menubar.setGeometry(QtCore.QRect(0, 0, 240, 21))

31.

self.menubar.setObjectName("menubar")

32.

MainWindow.setMenuBar(self.menubar)

33.

self.statusbar = QtWidgets.QStatusBar(MainWindow)

34.

self.statusbar.setObjectName("statusbar")

35.

MainWindow.setStatusBar(self.statusbar)

36.

 

37.

self.retranslateUi(MainWindow)

38.

QtCore.QMetaObject.connectSlotsByName(MainWindow)

39.

 

40.

def retranslateUi(self, MainWindow):

41.

_translate = QtCore.QCoreApplication.translate

42.

MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))

43.

self.pushButton.setText(_translate("MainWindow", "PushButton"))

44.

self.label.setText(_translate("MainWindow", "Длина Вашего текста"))

45.

 

46.

 

47.

if __name__ == "__main__":

48.

import sys

49.

app = QtWidgets.QApplication(sys.argv)

50.

MainWindow = QtWidgets.QMainWindow()

51.

ui = Ui_MainWindow()

52.

ui.setupUi(MainWindow)

53.

MainWindow.show()

54.

sys.exit(app.exec_())

 

Первые 9 строк являются комментариями (или вообще не заполнены). В них содержится описательная информация. Строка 10 производит импорт нужных модулей библиотеки PyQt5. В строках с 12 по 44 содержится класс Ui_MainWindow(), который включает в себя две функции: setupUi(self, MainWindow) и retranslateUi(self, MainWindow). Строки 47-54 инициализируют класс Ui_MainWindow(), в них строится бесконечный графический цикл.

Если Вы прочитали и поняли в книге Think Python: How To Think Like a Computer Scientist, что такое классы, то этот код не должен Вас испугать.

Разберем самое простое из приведенного кода. Функция setupUi(self, MainWindow) (строки 13-38) строит графический интерфейс пользователя, а функция retranslateUi(self, MainWindow) (строки 40-44) создает подписи для виджетов, которые могут иметь подписи. В нашей программе подписи могут иметь главное окно MainWindow, кнопка pushButton и подпись label. Чтобы быть уверенным в том, что Вы понимаете код, нужно попробовать его изменить. Например, если заменить в строке 43 подпись "PushButton" на "Кнопка", чтобы получилось

self.pushButton.setText(_translate("MainWindow", "Кнопка"))

и запустить программу, то текст кнопки изменится (см. Рис. 2.12):

Рис. 2.12 Изменение кнопки


Перейдем к функции setupUi(self, MainWindow). Строка 15 устанавливает размер главного окна. Вы наверное заметили, что под надписью «Длина вашего текста» остается много свободного места. Указанные в методе resize(x, y) величины задают ширину и высоту окна соответственно. Система координат при этом имеет следующую направленность (см. Рис. 2.13):

Рис. 2.13 Система координат виджетов PyQt


Установите значение высоты равное 180, чтобы строка 15 приняла вид

MainWindow.resize(240, 180)

и запустите программу. Геометрия окна стала более логичной (см. Рис. 2.14):

Рис. 2.14 Изменение размера главного окна


Чтобы еще потренироваться с размерами, сделаем поле ввода шире. Пусть оно тянется от одного края главного окна до другого. Вы наверняка догадались, что для этого нужно изменить строку 19. Метод setGeometry(QtCore.Qrect(x, y, width, height)) задает прямоугольник, в котором находится виджет. Параметры x и y устанавливают координаты левого верхнего угла виджета относительно родительского виджета (не экрана компьютера!), а width и height отвечают за ширину и высоту. Мы не будем менять измерения по вертикали, поэтому установим x на 5, а width на 230. Эти координаты я установил путем нескольких экспериментов. Запустите программу и посмотрите на результат (см. Рис. 2.15):

Рис. 2.15 Изменение размеров строки ввода

Справедливо будет сказать, что все эти операции лучше было бы сделать в Qt Designer еще до конвертации файла .ui в файл .py, но мы тренируемся, чтобы начать понимать код, поэтому наши действия являются оправданными. Уже понятно, что если мы захотим изменить расположение и размер кнопки, то изменения нужно будет вносить в строку 23.

***

Проверьте и расширьте свое понимание (2.1): Измените кнопку так, чтобы ее ширина и высота стали равны ширине и высоте строки ввода. При этом координата y кнопки не должна измениться.

(2.2): Измените код главного окна так, чтобы при запуске программы в заголовке появлялась надпись «My Window», а не «MainWindow».

***

Остановимся пока на этом и перейдем к написанию функций обработки данных. Эти функции будут оставаться внутри класса Ui_MainWindow(), прибавим к уже имеющимся двум функциям пустую заготовку. Для этого между строками 44 и 47 надо написать следующее:

def myFunction(self):

pass

Не забудьте про отступы (indentations). Слово pass внутри функции означает, что функция ничего не содержит. Однако программу можно запускать без ошибок. То есть это заготовка, которая ничего не добавляет в программу, но и не мешает ее функционированию.

Нам нужно, чтобы функция myFunction() активировалась при нажатии кнопки pushButton. Сама функция должна выполнять три действия: брать введенный текст из строки ввода, находить его длину и выводить результат в надпись. Каждое действие может соответствовать одной строке функции:

def myFunction(self):

self.text = self.lineEdit.text()

self.length = len(self.text)

self.label.setText("Длина Вашего текста %d" % self.length)

Первая строка функции создает переменную self.text типа String и помещает в нее содержимое строки ввода. Это делается при помощи метода text(). Вторая строка создает переменную self.length типа Integer и записывает в нее длину переменной self.text. Третья строка устанавливает текст надписи self.label. В ней уже имеется текст «Длина Вашего текста», но при использовании метода setText() он заменится на новый.

Функцию можно записать и в одну строку, просто она будет длиннее и немного сложнее для чтения:

def myFunction(self):

self.label.setText("Длина Вашего текста %d" % len(self.lineEdit.text()))

При такой записи нам не нужно создавать две переменные, поэтому мы остановимся на этом варианте. Теперь у Вас есть функция, но она все еще бесполезна, т.к. никак не связана с кнопкой. Добавьте в конец функции setupUi() следующую строку:

self.pushButton.clicked.connect(self.myFunction)

Вы помните, что программа находится в бесконечном графическом цикле. При этом она как будто ожидает от пользователя некоторых действий или событий. Событие clicked (в терминологии PyQt эти события называются сигналами), привязанное к кнопке означает нажатие этой кнопки. С помощью метода connect() с событием соединяется некоторое действие (функция). Таким образом, приведенную выше строку можно прочитать так: «Кнопка, ожидай нажатия! Когда это произойдет, сразу вызывай указанную в скобках функцию!» Обратите внимание на написание функции. Она пишется в этом случае без скобок.

У каждого виджета есть свой определенный набор сигналов, многие из которых являются универсальными как в случае с сигналом clicked.

Настало время испытать программу. Запустите ее, введите какой-нибудь текст (например, «эксперимент») и нажмите кнопку; результат - 11 символов (см. Рис. 2.16):

Рис. 2.16 Результат работы программы при вводе слова «эксперимент»


В конце концов программа приняла следующий вид (см. Код 2.2):

Код. 2.2 Программа myinterface1.py, измененная версия программы myinterface.py

1.

from PyQt5 import QtCore, QtGui, QtWidgets

2.

 

3.

class Ui_MainWindow(object):

4.

def setupUi(self, MainWindow):

5.

MainWindow.setObjectName("MainWindow")

6.

MainWindow.resize(240, 180)

7.

self.centralwidget = QtWidgets.QWidget(MainWindow)

8.

self.centralwidget.setObjectName("centralwidget")

9.

self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)

10.

self.lineEdit.setGeometry(QtCore.QRect(5, 20, 230, 20))

11.

self.lineEdit.setText("")

12.

self.lineEdit.setObjectName("lineEdit")

13.

self.pushButton = QtWidgets.QPushButton(self.centralwidget)

14.

self.pushButton.setGeometry(QtCore.QRect(80, 60, 75, 23))

15.

self.pushButton.setObjectName("pushButton")

16.

self.label = QtWidgets.QLabel(self.centralwidget)

17.

self.label.setGeometry(QtCore.QRect(30, 120, 181, 20))

18.

self.label.setObjectName("label")

19.

MainWindow.setCentralWidget(self.centralwidget)

20.

self.menubar = QtWidgets.QMenuBar(MainWindow)

21.

self.menubar.setGeometry(QtCore.QRect(0, 0, 240, 21))

22.

self.menubar.setObjectName("menubar")

23.

MainWindow.setMenuBar(self.menubar)

24.

self.statusbar = QtWidgets.QStatusBar(MainWindow)

25.

self.statusbar.setObjectName("statusbar")

26.

MainWindow.setStatusBar(self.statusbar)

27.

 

28.

self.retranslateUi(MainWindow)

29.

QtCore.QMetaObject.connectSlotsByName(MainWindow)

30.

 

31.

self.pushButton.clicked.connect(self.myFunction)

32.

 

33.

def retranslateUi(self, MainWindow):

34.

_translate = QtCore.QCoreApplication.translate

35.

MainWindow.setWindowTitle(_translate("MainWindow", "My Window"))

36.

self.pushButton.setText(_translate("MainWindow", "Кнопка"))

37.

self.label.setText(_translate("MainWindow", "Длина Вашего текста"))

38.

 

39.

def myFunction(self):

40.

self.label.setText("Длина Вашего текста %d" % len(self.lineEdit.text()))

41.

 

42.

if __name__ == "__main__":

43.

import sys

44.

app = QtWidgets.QApplication(sys.argv)

45.

MainWindow = QtWidgets.QMainWindow()

46.

ui = Ui_MainWindow()

47.

ui.setupUi(MainWindow)

48.

MainWindow.show()

49.

sys.exit(app.exec_())

 

***

Проверьте и расширьте свое понимание (2.3): В рассмотренной нами программе функция вызывается нажатием кнопки. Однако пользователям привычнее после набранного в строке ввода текста нажать клавишу Enter. Как модифицировать программу, чтобы она работала именно таким образом? Примечание: это сложное задание. Для его решения, попробуйте поискать информацию в Интернете, в частности на форумах Stack Overflow [Stack Overflow].

(2.4): Представьте, что пользователь ввел в строку ввода одни пробелы. Модифицируйте программу так, чтобы в этом случае длина текста равнялась нулю. Это не должно касаться случая, когда вместе с пробелами введены и какие-то символы.

(2.5): Усовершенствуйте программу дальше. Сделайте так, чтобы вывод принял форму «a / b», где a - количество всех символов кроме пробелов, b - количество пробелов. Например, при вводе «я - лингвист» получился бы вывод «10 / 2».

***

Итак, наша первая программа на PyQt5 получилась достаточно интересной. Несмотря на небольшой объем в программе есть все основные блоки, которые будут представлены и в более крупных проектах. Вернемся к вопросу об удобстве изменения интерфейса.

Секрет в том, чтобы держать в отдельных файлах класс интерфейса и функции обработки данных.

Конвертируйте файл интерфейса myinterface.ui немного по-другому. Уберите из командной строки параметр -x, чтобы получилось следующее:

pyuic5 myinterface.ui -o myinterface.py

Полученный файл переименуйте в myinterface2.py. Этот файл отличается от предыдущего тем, что в нем нет графического цикла и его нельзя запустить самостоятельно. Вы легко можете убедиться в этом сами. Поэтому нужно создать главный исполняемый файл (не путайте с файлами с расширением .exe!). Назовем его myintmain.py. Он должен иметь следующее содержание (см. Код 2.3):

Код 2.3 Программа myintmain.py

1.

import sys

2.

from myinterface2 import *

3.

from PyQt5 import QtCore, QtGui, QtWidgets

4.

 

5.

class MyWin(QtWidgets.QMainWindow):

6.

def __init__(self, parent=None):

7.

QtWidgets.QWidget.__init__(self, parent)

8.

self.ui = Ui_MainWindow()

9.

self.ui.setupUi(self)

10.

 

11.

if __name__ == "__main__":

12.

app = QtWidgets.QApplication(sys.argv)

13.

myapp = MyWin()

14.

myapp.show()

15.

sys.exit(app.exec_())

 

Это достаточно небольшая но очень важная программа. Строка 2 импортирует все классы из файла myinterface2.py, в котором находится код интерфейса. Строка 5 объявляет класс MyWin(). Вы можете назвать этот класс и по-другому, главное, чтобы новое название было отражено и в строке 13. Строка 6 запускает инициализирующую функцию. Строки 11-15 создают бесконечный цикл графического интерфейса. В принципе, приведенный выше код можно использовать в качестве универсального, изменяя только строку 2.

Теперь нужно правильно добавить в программу myintmain.py функцию myFunction() и привязать ее к кнопке. Можно скопировать прежний фрагмент кода, но он выведет ошибки. И вот почему. Когда весь код находился внутри одного файла, то мы ссылались на переменные виджетов напрямую, используя self. Теперь кнопка и другие виджеты находятся в другом файле, т.е. в другом классе, поэтому ссылаться на них нужно через переменную, в которой находится их класс. Это переменная self.ui, которая создается в строке 8. Таким образом, self.pushButton превращается в self.ui.pushButton. Внесите нужные изменения и сохраните полученный файл как myintmain1.py (см. Код 2.4):

1.

import sys

2.

from myinterface2 import *

3.

from PyQt5 import QtCore, QtGui, QtWidgets

4.

 

5.

class MyWin(QtWidgets.QMainWindow):

6.

def __init__(self, parent=None):

7.

QtWidgets.QWidget.__init__(self, parent)

8.

self.ui = Ui_MainWindow()

9.

self.ui.setupUi(self)

10.

 

11.

self.ui.pushButton.clicked.connect (self.myFunction)

12.

 

13.

def myFunction(self):

14.

self.ui.label.setText("Длина Вашего текста %d" % len(self.ui.lineEdit.text()))

15.

 

16.

if __name__ == "__main__":

17.

app = QtWidgets.QApplication(sys.argv)

18.

myapp = MyWin()

19.

myapp.show()

20.

sys.exit(app.exec_())

 

Теперь можно снова открыть файл myinterface.ui в Qt Designer и изменить с помощью мыши размеры виджетов, надписи, цветовую гамму и многое другое. Измененный файл надо просто конвертировать в файл с расширением .py и все. Изменения сразу вступят в силу при запуске программы главной программы myintmain1.py. Убедитесь в этом сами, поэкспериментируя с интерфейсом. Измените заголовок главного окна и размеры виджетов.

***

Проверьте и расширьте свое понимание (2.6): Выполните задание 2.3, изменив файл myintmain1.py (Задание про клавишу Enter).

***

В этой главе Вы построили свое первое приложение на PyQt5. Вы использовали такие виджеты как главное окно, строка ввода, кнопка и надпись. Виджеты были соединены с функцией посредством сигналов clicked и returnPressed. Также Вы научились конвертировать файлы ui в py при помощи командной строки. Вы познакомились с двумя способами построения программ: в одном файле и разделяя графический интерфейс и функции обработки данных.