Графический интерфейс Python с PyQT / PySide2

Обзор популярного графического интерфейса для ваших проектов и руководство по нему.

Я уже некоторое время искал графический интерфейс для одного из моих проектов Python, в какой-то момент я подумал, что это Kivy, я все еще думаю, что это действительно крутой и полезный графический интерфейс ( особенно для прототипирования и разработки мобильных приложений), и я пробовал другие, такие как tkinter, который я могу порекомендовать для небольших проектов, QT, PyQT и PySide2 (которые все связаны) постоянно рекомендуются, но эти рекомендации также сопровождались предупреждениями, плюс это кажется немного пугающим с самого начала, здесь мы проверим это, немного кода для начала ...

INSTALL: pip install PySide2

Note: On Windows I had already installed Anaconda, pip gave me some PATH errors but installing straight from conda worked:
https://anaconda.org/conda-forge/pyside2

# Привет, мир:

from PySide2.QtWidgets import *
app = QApplication([]) # Start an application.
window = QWidget() # Create a window.
layout = QVBoxLayout() # Create a layout.
button = QPushButton("I'm just a Button man") # Define a button
layout.addWidget(QLabel('Hello World!')) # Add a label
layout.addWidget(button) # Add the button man
window.setLayout(layout) # Pass the layout to the window
window.show() # Show window
app.exec_() # Execute the App

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

👋👋 Hi there 👋👋 all my content is free for Medium subscribers, if you are already a subscriber I wanted to say thank you ! 🎉 If not and you are considering subscribing, you can use my membership referral link, you will be supporting this and other high quality content, Thank you !
⭐️⭐ Subscribe to Medium ! ⭐️⭐️

Основные документы:



PySide2, PySide, Qt, PyQt, pyQt5, Riverbank ... Whats going on !?
 
QT Is a company and framework (c++) for cross platform UIs and general software development.
PyQT Is QT for python, but it's not part of the QT company due to licensing issues, it's developed/offered by a company called Riverbank.
PySide (deprecated), PySide2 (Active) is basically the same as PyQt, but with a different license and developed by the QT company, to makes things even less clear it also goes by the name QT for Python.
⚠️ PySide and PyQT are functionally similar and can be used interchangeably as of this writing, the only difference is the import statement in most cases, so if you can't find help or a tutorial for one try the other, they might also diverge in the future, all examples here use PySide and that's where I would focus as a beginner, see also the licensing section later for more considerations and wrappers like QtPy ( which supports both ). 

Виджеты

PySide имеет несколько уровней абстракции и множество модулей, я думаю, что хорошее место для начала с PyQt - это виджеты, которые являются компонентами, которые вы можете добавить в свое приложение, обратите внимание, что вы также можете расширять и создавать свои собственные виджеты, если вы контролируете want не включен, но готовый список охватывает многое:



#Let's add a different widget, a common slider...
from PySide2.QtCore import Qt
from PySide2.QtWidgets import *
app = QApplication([])
window = QWidget()
layout = QVBoxLayout() 
# Define slider widget, note the orientation argument:
slider = QSlider(Qt.Horizontal) 
button = QPushButton(“I'm just a Button man")
layout.addWidget(QLabel('Hello World!'))
layout.addWidget(button)
layout.addWidget(slider) #Add the slider
window.setLayout(layout)
window.show()
app.exec_()

Взаимодействие с виджетами через слоты и сигналы

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

from PySide2.QtWidgets import *
from PySide2.QtCore import Slot
@Slot() # slot decorator 
def youClicked(): # A slot is basically a function you call 
    label.setText("You clicked the button")
app = QApplication([])
window = QWidget()
layout = QVBoxLayout()
button = QPushButton("I'm just a Button man")
label = QLabel('¯\_(ツ)_/¯')
button.clicked.connect(youClicked) # clicked signal
layout.addWidget(label)
layout.addWidget(button)
window.setLayout(layout)
window.show()
app.exec_()

Получение значения ползунков:

from PySide2.QtWidgets import *
from PySide2.QtCore import Slot, Qt
@Slot() #slot decorator
def sliderValue(val):
    label.setText('Slider Value: ' + str(val))
app = QApplication([])
window = QWidget()
layout = QVBoxLayout()
slider = QSlider(Qt.Horizontal)
slider.valueChanged[int].connect(sliderValue) #valueChanged signal
label = QLabel('Slider Value:')
layout.addWidget(label)
layout.addWidget(slider)
window.setLayout(layout)
window.show()
app.exec_()

⚠️ Slots are functions that get executed when a UI Widget's Signal gets triggered or emitted, this is analogous to listeners and emitters/events if you are coming from JS or another language/UI library, importantly you can get the signals, builtin slots and event details for each widget from the API docs although I found them hard to find at first:
https://doc.qt.io/qtforpython/PySide2/QtWidgets/QSlider.html

Макет

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

from PySide2.QtWidgets import *
app = QApplication([])
window = QWidget()
layout = QHBoxLayout() # HORIZONTAL BOX LAYOUT
label = QLabel('(㇏(•̀ᵥᵥ•́)ノ)')
button_1 = QPushButton("One Button")
button_2 = QPushButton("Two Button")
button_3 = QPushButton("Three Button")
layout.addWidget(label)
layout.addWidget(button_1)
layout.addWidget(button_2)
layout.addWidget(button_3)
window.setLayout(layout)
window.show()
app.exec_()

from PySide2.QtWidgets import *
app = QApplication([])
window = QWidget()
layout = QGridLayout() # GRID LAYOUT 
label = QLabel('(㇏(•̀ᵥᵥ•́)ノ)')
button_1 = QPushButton("(1,1) Button")
button_2 = QPushButton("(2,2) Button")
button_3 = QPushButton("(3,3) Button")
layout.addWidget(label, 0, 0) # Note the Row Column coordinates
layout.addWidget(button_1, 1, 1)
layout.addWidget(button_2, 2, 2)
layout.addWidget(button_3, 3, 3)
window.setLayout(layout)
window.show()
app.exec_()

The Elephant 🐘 in the Room:
While PyQt/PySide work great in Python they are bindings for a C++ library, this means that you will eventually hit a C++ bedrock, this caught me a bit off guard while reading the documentation which throws chunks of c++ code at you and might become an issue if you intend to overwrite or extend certain behaviors, I also found the documentation a bit lacking in general, c'est la developer vie I suppose.

Вы, конечно, можете создавать более сложные макеты, но перед этим (путем вложения или создания пользовательских виджетов) проверьте доступные:



Вы также можете изучить виджет / класс mainWindow, который обеспечивает основу для обычного размещения виджетов:



Тематика и стили:

Давайте будем честными, что мы, как разработчики в графическом интерфейсе, действительно хотим, так это темный режим, а PyQt обеспечивает большую гибкость в цветах и ​​шрифтах с помощью тем, создание темного режима упрощается:

from PySide2.QtWidgets import *
from PySide2.QtCore import Slot,Qt
from PySide2.QtGui import QPalette, QColor
app = QApplication([])
app.setStyle('Fusion') #Style needed for palette to work
# Dark Palette (found on github, couldn't track the original author)
default_palette = QPalette()
dark_palette = QPalette()
dark_palette.setColor(QPalette.Window, QColor(53, 53, 53))
dark_palette.setColor(QPalette.WindowText, Qt.white)
dark_palette.setColor(QPalette.Base, QColor(25, 25, 25))
dark_palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
dark_palette.setColor(QPalette.ToolTipBase, Qt.white)
dark_palette.setColor(QPalette.ToolTipText, Qt.white)
dark_palette.setColor(QPalette.Text, Qt.white)
dark_palette.setColor(QPalette.Button, QColor(53, 53, 53))
dark_palette.setColor(QPalette.ButtonText, Qt.white)
dark_palette.setColor(QPalette.BrightText, Qt.red)
dark_palette.setColor(QPalette.Link, QColor(42, 130, 218))
dark_palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
dark_palette.setColor(QPalette.HighlightedText, Qt.black)
window = QWidget()
layout = QGridLayout()
# Make a few Dials and buttons:
for i in range(0,4):
    Dial = QDial()
    Button = QPushButton('Button ' + str(i))
    Dial.setNotchesVisible(True)
    layout.addWidget(Button,0,i)
    layout.addWidget(Dial,1,i)
# Toggle theme function
@Slot()
def toggleDarkTheme():
    if not togglePushButton.isChecked():
        app.setPalette(dark_palette)
    else:
        app.setPalette(default_palette)
# Toggle push button
togglePushButton = QPushButton("Dark Mode")
togglePushButton.setCheckable(True)
togglePushButton.setChecked(True)
togglePushButton.clicked.connect(toggleDarkTheme)
layout.addWidget(togglePushButton,2,3)
window.setLayout(layout)
window.show()
app.exec_()

Подробнее о темах, стилях и настраиваемых виджетах, обратите внимание, что вы можете загрузить настраиваемую таблицу стилей CSS для виджетов и / или изменить палитры или создать внешний вид виджета с нуля ...







Интеграции

Еще одна распространенная вещь, которую делают с графическим интерфейсом пользователя, - это их интеграция с другими библиотеками или частями вашего скрипта, как правило, осторожно вбивая их на место, что обычно проходит так гладко, как и следовало ожидать, написание вашего приложения вокруг графического интерфейса или наличие конфликтующих библиотек является обычным явлением, Итак, с учетом сказанного, давайте проверим пару интеграций PyQT / PySide ...

Модули PyQt / PySide:

Прежде чем вы выберете внешний маршрут, PyQT / PySide предоставляет вам несколько модулей, которые могут позволить вам полностью пропустить интеграцию, скажем, вам нужна диаграмма, вместо использования библиотеки диаграмм вы можете использовать собственный модуль диаграммы PyQt / PySide. :

from PySide2 import QtGui
from PySide2.QtWidgets import *
from PySide2.QtCharts import QtCharts
from PySide2.QtGui import QPainter
from random import randint 
app = QApplication([])
window = QWidget()
layout = QVBoxLayout()
# Initialize chart
chart = QtCharts.QChart()
lineSeries = QtCharts.QLineSeries()
# Make some random data points
dataSeries = [(i+1, randint(0, 99999)) for i in range(200)]
# load data into chart:
for point in dataSeries:
    lineSeries.append(point[0],point[1])
# Add Some Chart Options
chart.addSeries(lineSeries)
chart.setTitle("Random Numbers from 0-9000")
chart.createDefaultAxes()
# Create a container (similar to a widget)
chartView = QtCharts.QChartView(chart)
chartView.setRenderHint(QPainter.Antialiasing)
# Some Chart Styling
lineSeries.setColor(QtGui.QColor("darkgray"))
chartView.chart().setBackgroundBrush(QtGui.QColor("ivory"))
layout.addWidget(chartView)
window.setLayout(layout)
window.show()
window.resize(600, 400)
app.exec_()

⚠️ These modules come with a varying degree of documentation and implementation, some are documented with examples, some are not and some seem to not be implemented at all, caveat emptor (buyers beware).

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



Внешние библиотеки

Под внешними библиотеками я имею в виду некоторый аппаратный ввод-вывод или графический элемент, созданный вне QT, и вопрос в том, насколько сложно их интегрировать с QT, давайте начнем с общей библиотеки диаграмм, такой как matplot / pyplot ...

import matplotlib
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PySide2 import QtCore, QtGui
from PySide2.QtWidgets import *
matplotlib.use('Qt5Agg') #Render to PySide/PyQt Canvas
app = QApplication([])
# generate the plot
fig = Figure(figsize=(600,600), dpi=72, facecolor=(1,1,1), edgecolor=(0,0,0))
ax = fig.add_subplot(111)
# Add data:
ax.plot([0,1])
# generate the canvas to display the plot
canvas = FigureCanvas(fig)
# Add a button
button = QPushButton("I'm still just a button man")
button.setMaximumWidth(200)
window = QWidget()
layout = QVBoxLayout()
layout.addWidget(button)
layout.addWidget(canvas)
window.setLayout(layout)
window.show()
app.exec_()

Pyplot оказался действительно простым, поскольку есть поддержка PyQt прямо из коробки, давайте рассмотрим другой распространенный случай, графику с использованием виджета QPixmap:

from PySide2.QtWidgets import *
from PySide2.QtGui import QPixmap
app = QApplication([])
window = QWidget()
#Add a Label:
label = QLabel()
#Add a pixmap instance to the label:
label.setPixmap(QPixmap("../PySide_Learn/50BusSM.jpg"))
layout = QVBoxLayout()
layout.addWidget(label)
window.setLayout(layout)
window.show()
app.exec_()

Также довольно просто, в общем, вы хотите использовать контейнер, который может быть встроенным виджетом, или создать собственный виджет, который мы не будем полностью здесь рассматривать (см. нужна более сложная интеграция библиотек (учебники Qt охватывают большую часть того, что вам понадобится):



Учебные пособия по Qt для Python - Qt для Python
Набор учебных пособий с« пошаговыми руководствами
предоставляется вместе с Qt для Python, чтобы помочь новым пользователям начать работу… doc .qt.io »



Аппаратная интеграция

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

Как назло, это именно то, что нужно моему личному проекту (без сбоев), в частности, мне нужен графический интерфейс, который взаимодействует с моей веб-камерой и OpenCv:

from PySide2.QtWidgets import *
from PySide2.QtCore import *
from PySide2.QtGui import *
import cv2 # OpenCV
import qimage2ndarray # for a memory leak,see gist
import sys # for exiting
“""Minimal Implementation OpenCv + PyQt"""
def displayFrame():
    ret, frame = cap.read()
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    image = qimage2ndarray.array2qimage(frame)
    label.setPixmap(QPixmap.fromImage(image))
app = QApplication([])
window = QWidget()
# OPENCV
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
# timer for getting frames
timer = QTimer()
timer.timeout.connect(displayFrame)
timer.start(60)
label = QLabel('No Camera Feed')
button = QPushButton("Quiter")
button.clicked.connect(sys.exit) # quiter button 
layout = QVBoxLayout()
layout.addWidget(button)
layout.addWidget(label)
window.setLayout(layout)
window.show()
app.exec_()
# See also: https://gist.github.com/bsdnoobz/8464000

Licensing :
- PyQT (Riverbank):GNU GPL v3 and commercial (~$550)
- PySide2/QT for Python(The QT Company): LGPL v3 and commercial (~$$$$)
Which one doc ?
To be honest I don't know because your project might be different from mine, LGPL is considered a better deal because you don't need to distribute your source code but GPL does, if you foresee going commercial I personally would go with the QT company even though they seem expensive, so I'd recommend PySide, but you need to understand the licenses and how they apply to your project, here's another take on the subject:
https://machinekoder.com/pyqt-vs-qt-for-python-pyside2-pyside/

Остатки

Я попытался сделать это практическим обзором, но в PyQt есть гораздо больше, что я мог бы здесь уместить, некоторые из вещей, которые я еще не пробовал, что привлекло мое внимание:

QT Designer. Приложение QT для разработки графических интерфейсов, Жесты, веб-возможности, есть встроенный веб-браузер и другие компоненты, Поддержка 3D, интеграция с базами данных, Создание пользовательских виджетов, модульных тестов, рисование и рисование пользовательских фигур на холсте, таблиц стилей виджетов.

Alternatives:
- KIVY: Perhaps the closest competitor due to the similar feature set, good documentation but crazy steep learning curve and a lot of quirks (theming and layouts for instance), good choice if your target is mobile (plug: check my overview).
- PySimpleGUI: A relative newcomer to Python GUI's, short, sweet pythonic syntax basic+ looks and some documentation issues.
The rest

Выводы

По моему опыту, QT / PySide / PyQT ... мощный, он также сложный и плохо документированный, раздробленная экосистема и источник большой путаницы при начале работы, но конечные результаты, если вы постигнете, - это красивые, надежные и функциональные графические интерфейсы на Python , стоит потраченного времени, если оно у вас есть, и я надеюсь, что этот пост поможет вам в этом.

Спасибо за прочтение.