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 можно выделить три основных модуля.

  1. QtCore — ядро библиотеки, предоставляет доступ к ключевому функционалу, который не завязан на графическом интерфейсе: сигналы, слоты, базовые классы и др.

  2. QtGui — расширяет ядро некоторыми элементами gui: события, экраны, окна и др.

  3. 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!")

При запуске этого приложения должно появиться окно, которое в зависимости от операционной системы может выглядеть следующим образом.

../_images/main_window_1.png

Note

У метода show есть аналоги showFullScreen, showNormal, showMaximized и showMinimized.

Это окно уже можно

  • перемещать;

  • менять его размер, растягивая или сжимая его, а также растягивая его на весь экран;

  • сворачивать и разворачивать обратно;

  • закрывать его.

Т.е. Qt сам сгенерировал ряд элементов графического интерфейса, а также реализовал обработку ряда событий (например, нажатие на кнопку закрытия приложения).

При этом только после закрытия этого окна программа покинет цикл событий, сработает инструкция print и в консоли появится сообщение “Application is closed!”.

Кастомизация QMainWindow

В предыдущем примере окно было создано с дефолтным размером, дефолтной иконкой и дефолтным заголовком “python”, который даже полностью не влезает из-за размеров окна. Все это можно настроить под свои нужды.

  • Размер окна можно изменить методом setGeometry, который на вход принимает четыре параметра: координаты x и y левого верхнего угла окна, ширину w и высоту h окна. Ось \(Ox\) и \(Oy\) направлены слева направо и сверху вниз соответственно.

../_images/geometry.png
  • Заголовок окна можно изменить методом 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.

В итоге должны получить нечто следующее.

../_images/main_window_2.png

Виджет 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()
../_images/label_1.png

Макеты. 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()
../_images/HBoxLayout.png
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()
../_images/VBoxLayout.png