PySide. Основы
Contents
PySide. Основы#
Установка PySide
#
Установить PySide6
можно используя pip
pip install PySide6
Репозиторий#
Здесь и везде далее по теме PySide
будет приводиться исходный код, эффект запуска которого не получится в полной мере продемонстрировать в рамках статического примера. Чтобы лучше почувствовать возможности Qt
, лучше самостоятельно запускать приводимые примеры на своей машине. Все необходимое для этого можно найти в репозитории по ссылке, клонировать который можно командой (потребуется установленный git)
git clone git@github.com:FadeevLecturer/PySide6_intro.git
Все зависимости, необходимые для запуска всех примеров, перечисленны в файле requirements.txt.
Установить все необходимые библиотеки в пустое виртуальное окружение можно командой
python -m pip install -r requirements.txt
Пространство имен PySide
#
В PySide
можно выделить три основных модуля.
QtCore — ядро библиотеки, предоставляет доступ к ключевому функционалу, который не завязан на графическом интерфейсе: сигналы, слоты, базовые классы и др.
QtGui — расширяет ядро некоторыми элементами
gui
: события, экраны, окна и др.QtWidgets — содержит в себе набор готовых к использованию элементов интерфейса (виджетов): кнопки и многое другое.
Note
С полным списком модулей можно ознакомиться здесь.
Часто в приложении используются десятки имен из модулей PySide
. В связи с этим нередко встречается wildcard
импорты следующего вида:
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
В данном случае это не так страшно, т.к. все имена в модулях PySide
имеют специфическую форму и не перекрываются с именами встроенной библиотеки. В крупных программах принято реализовывать графический интерфейс и остальную логику программы в разных местах, что ещё в большей степени нивелирует проблему “запутанности” пространства имен.
Qt приложение. QApplication
#
Чтобы разрабатывать приложение с графическим интерфейсом на основе Qt
необходимо в первую очередь создать объект типа QApplication. При этом такой объект должен быть ровно один вне зависимости от обстоятельств, количества окон у приложения и т.п. QApplication
делает очень много закулисной работы по обработке событий и организации виджетов.
Самое примитивное приложение на PySide
без каких либо окон выглядит примерно так.
import sys
from PySide6.QtWidgets import QApplication
app = QApplication(sys.argv)
app.exec()
print("Application is closed!")
Команда app.exec()
запускает цикл событий (event loop), который ожидает события (events) и вызывает соответствующий обработчик события (event handler
). Цикл события из себя представляет бесконечный цикл типа while
, выход из которого осуществляется при возникновении события exit
.
Важно понимать, что когда Qt
приложение запущено (app.exec()
), программа попадает в событийный цикл, из которого она не выйдет, пока приложение не будет закрыто. Иными словами исполнение кода в скрипте python
приостанавливается, управление потоком исполнения программы передаётся Qt
. Так, инструкция print
в скрипте выше не выполнится до тех пор, пока не будет вызван метод app.exit()
, к которому мы не предоставили прямого доступа.
Итого, в примере выше цикл событий будет крутиться в пустую, так как мы не обрабатываем ни одного события. Создадим окно у приложения, чтобы начать обрабатывать хоть какие-то события.
Главное окно приложения. QMainWindow
#
QApplication
самого по себе недостаточно, чтобы на экране появился графический интерфейс. Для этого необходимо создать какой-нибудь элемент интерфейса (widget
) и вывести его на экран, т.к. по умолчанию они скрыты. Для главного окна приложения удобнее всего использовать виджет QMainWindow.
Следующий пример расширяет предыдущий, добавляя создание экземпляр класса QMainWindow
и выводя его на экран методом show (по умолчанию все виджеты скрыты).
import sys
from PySide6.QtWidgets import QApplication, QMainWindow
app = QApplication(sys.argv)
main_window = QMainWindow()
main_window.show()
app.exec()
print("Application is closed!")
При запуске этого приложения должно появиться окно, которое в зависимости от операционной системы может выглядеть следующим образом.
Note
У метода show
есть аналоги
showFullScreen,
showNormal,
showMaximized и
showMinimized.
Это окно уже можно
перемещать;
менять его размер, растягивая или сжимая его, а также растягивая его на весь экран;
сворачивать и разворачивать обратно;
закрывать его.
Т.е. Qt
сам сгенерировал ряд элементов графического интерфейса, а также реализовал обработку ряда событий (например, нажатие на кнопку закрытия приложения).
При этом только после закрытия этого окна программа покинет цикл событий, сработает инструкция print
и в консоли появится сообщение “Application is closed!”.
Кастомизация QMainWindow
#
В предыдущем примере окно было создано с дефолтным размером, дефолтной иконкой и дефолтным заголовком “python”, который даже полностью не влезает из-за размеров окна. Все это можно настроить под свои нужды.
Размер окна можно изменить методом setGeometry, который на вход принимает четыре параметра: координаты
x
иy
левого верхнего угла окна, ширинуw
и высотуh
окна. Ось \(Ox\) и \(Oy\) направлены слева направо и сверху вниз соответственно.
Заголовок окна можно изменить методом setWindowTitle, который на вход принимает строку.
Иконку окна можно изменить с помощью метода setWindowIcon, которая на вход принимает объект QtGui.QIcon. Конструктор
QIcon
в свою очередь на вход принимает изображение или строку, содержащую путь к файлу с иконкой.
Изменим размер, заголовок и иконку окна из предыдущего примера. Можно было бы вызывать все перечисленные методы у уже существующего окна, но гораздо нагляднее будет унаследовать от класса QMainWindow
и модифицировать окно в момент инициализации.
import sys
import os
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtGui import QIcon
path_to_the_icon = os.path.join("..", "icons", "phys.png")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 300, 200)
self.setWindowTitle("My Qt Application")
self.setWindowIcon(QIcon(path_to_the_icon))
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()
Разберем этот пример детальнее, так как модификацию Qt
виджетов часто удобнее всего делать именно так.
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
Мы собираемся модифицировать класс QMainWindow
, поэтому от него и наследуемся. В методе __init__
в первую очередь делегируем инициализацию окна базовому классу.
self.setGeometry(100, 100, 300, 200)
self.setWindowTitle("My Qt Application")
self.setWindowIcon(QIcon(path_to_the_icon))
Затем настраиваем положение и размер окна, его заголовок и путь к окну.
Note
Для простоты в примере создаётся окно в абсолютных единицах, но можно было бы настроить геометрию окна под разрешение экрана. Метод primaryScreen объекта QApplication
возвращает объект QtGui.QScreen, который позволяет получить информацию о главном экране (ситуация несколько усложняется, если экранов несколько), в том числе и геометрию экрана методом availableGeometry.
main_window = MainWindow()
Ну и создаем мы теперь экземпляр нового класса MainWindow
, а не исходного QMainWindow
.
В итоге должны получить нечто следующее.
Виджет QLabel
#
Чтобы внутри окна появились элементы графического управления необходимо добавить виджеты. Все виджеты располагаются в модуле QtWidgets
, в том числе и один из самых простых — QLabel, который позволяет отображать текст (или изображение, но об этом в следующем разделе).
Любой виджет принимает ссылку на родительский виджет parent
. QLabel
на вход принимает ещё и python
строку. Метод setALignment ожидает на вход Qt.Core.AlignmentFlag и отвечает за выравнивание текста по центру (QtCore.Qt.AlignCenter
), по левому краю (QtCore.Qt.AlignRight
) и т.п.
Чтобы добавить виджет на окно, мало просто указать parent
при его создании. Необходимо также сказать самому окну этот виджет отрисовать. Пока у нас всего один виджет можно воспользоваться методом QMainWindow.setCentralWidget, чтобы сразу добавить наш текст в центр окна.
import sys
import os
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel
from PySide6.QtGui import QIcon, Qt
path_to_the_icon = os.path.join("..", "icons", "../python.png")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 300, 150)
self.setWindowTitle("My Qt Application")
self.setWindowIcon(QIcon(path_to_the_icon))
self.label = QLabel("Hello, World!", parent=self)
self.label.setAlignment(Qt.AlignCenter)
self.setCentralWidget(self.label)
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()
Макеты. Layout
#
Добавить несколько виджетов на окно проще всего используя макеты (layout
).
Вообще говоря, можно расположить виджеты на окне, указав окно в качестве родителя (parent
) и вызвав метод setGeometry
. При таком подходе можно указывать произвольное положение кнопок, но нет никакой гарантии, что с изменением геометрии самого окна элементы будут адекватно перестраиваться.
Qt
предоставляет набор стандартных макетов, которые сами следят за эффективным использованием пространства, занимаемого элементами интерфейса. Чтобы установить макет, необходимо вызвать метод setLayout у какого-нибудь разумного виджета (), но у QMainWindow
Чтобы добавить несколько виджетов на окно, необходимо:
установить в качестве центрального виджет QWidget, т.к. у
QMainWindow
всегда должен быть центральный виджет;расположить внутри центрального виджета все остальные, используя макеты (
layouts
).
Note
Подробнее о макетах можно почитать здесь.
QHBoxLayout
и QVBoxLayout
#
Макет QHBoxLayout выравнивает виджеты горизонтально слева направо , а QVBoxLayout вертикально сверху вниз. У обоих из них есть среди прочих следующие методы:
addWidget — добавляет виджет. В качестве параметра
alignment
можно указать выравнивание, как иQLabel
;addSpacing — добавляет пустой заполнитель заданного размера;
addLayout — добавляет подмакет. Например, можно встроить
QHBoxLayout
вQVBoxLayout
.
Макет сам распределяет пространство, которое будет занимать виджет исходя из геометрии окна, политики размера каждого из виджетов (QSizePolicy, который среди прочего определяем минимальный размер виджета) и коэффициента растяжения (stretching
), который определяет насколько жадно этот виджет будет занимать пространство. По умолчанию под каждый виджет отводится одинаковое количество места, но если указать параметр stretch
(его принимают все три вышеописанных метода), то виджета с большим значением будет занимать больше пространства.
В качестве примера, создадим два виджета QLabel
, положим в них картинку методом setPixmap, который ожидает на вход объект QPixmap, и добавим их в макет.
import sys
import os
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QWidget, QHBoxLayout
from PySide6.QtGui import QIcon, Qt, QPixmap
path_to_the_icon = os.path.join("..", "icons", "phys.png")
path_to_py_logo = os.path.join("..", "icons", "python.png")
path_to_qt_logo = os.path.join("..", "icons", "qt.png")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 300, 150)
self.setWindowTitle("My Qt Application")
self.setWindowIcon(QIcon(path_to_the_icon))
widget = QWidget(parent=self)
self.setCentralWidget(widget)
python_logo = QPixmap(path_to_py_logo)
python_logo_label = QLabel()
python_logo_label.setPixmap(python_logo)
qt_logo = QPixmap(path_to_qt_logo)
qt_logo_label = QLabel()
qt_logo_label.setPixmap(qt_logo)
layout = QHBoxLayout()
layout.addWidget(python_logo_label, alignment=Qt.AlignCenter)
layout.addWidget(qt_logo_label, alignment=Qt.AlignCenter)
widget.setLayout(layout)
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()
import sys
import os
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QWidget, QVBoxLayout
from PySide6.QtGui import QIcon, Qt, QPixmap
path_to_the_icon = os.path.join("..", "icons", "phys.png")
path_to_py_logo = os.path.join("..", "icons", "python.png")
path_to_qt_logo = os.path.join("..", "icons", "qt.png")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 300, 150)
self.setWindowTitle("My Qt Application")
self.setWindowIcon(QIcon(path_to_the_icon))
widget = QWidget(parent=self)
self.setCentralWidget(widget)
python_logo = QPixmap(path_to_py_logo)
python_logo_label = QLabel()
python_logo_label.setPixmap(python_logo)
qt_logo = QPixmap(path_to_qt_logo)
qt_logo_label = QLabel()
qt_logo_label.setPixmap(qt_logo)
layout = QVBoxLayout()
layout.addWidget(python_logo_label, alignment=Qt.AlignCenter)
layout.addWidget(qt_logo_label, alignment=Qt.AlignCenter)
widget.setLayout(layout)
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec()