Разработка приложений с графическим интерфейсом на PyQt для Python
Создание окна с кнопкой на PyQt
Базовый элемент любого приложения на PyQt - это главное окно, содержащее виджеты. В качестве примера рассмотрим окно с одной кнопкой, при нажатии на которую выводится сообщение.
import sys
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Пример окна')
self.setGeometry(300, 300, 300, 200)
button = QPushButton('Нажми меня', self)
button.move(100, 80)
button.clicked.connect(self.on_click)
def on_click(self):
QMessageBox.information(self, 'Сообщение', 'Кнопка нажата!')
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
Qt python приложение (создание приложения на pyqt)
В этом коде создаётся класс MainWindow, наследующий от QWidget. В методе initUI устанавливаются размеры, заголовок, создаётся кнопка и подключается сигнал clicked к слоту on_click. При запуске приложения появляется окно с кнопкой.
Типичная ошибка: забыть вызвать app.exec() или использовать sys.exit(). Без этого приложение не запустится. Ещё одна распространённая проблема - неправильное указание родительского виджета. Кнопка должна иметь родителя self, иначе она не отобразится.
Как организовать виджеты с помощью компоновки?
Ручное позиционирование через move() неудобно при изменении размеров окна. Компоновщики QHBoxLayout и QVBoxLayout автоматически управляют расположением виджетов.
import sys
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout
class MainWindow(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
btn1 = QPushButton('Кнопка 1')
btn2 = QPushButton('Кнопка 2')
layout.addWidget(btn1)
layout.addWidget(btn2)
self.setLayout(layout)
self.setWindowTitle('Компоновка')
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
Здесь создаётся вертикальный компоновщик, добавляются две кнопки, и компоновщик устанавливается для окна. При изменении размеров окна кнопки перестраиваются.
Ошибка: забыть вызвать setLayout(). Без этого виджеты не будут управляться компоновщиком. Также важно не добавлять один и тот же виджет в несколько компоновщиков.
Как добавить меню и строку состояния?
Для полноценного приложения часто требуется меню и статусная строка. Используется QMenuBar и QStatusBar.
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QMenu, QStatusBar
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Приложение с меню')
self.setGeometry(100, 100, 400, 300)
menubar = self.menuBar()
file_menu = menubar.addMenu('Файл')
exit_action = file_menu.addAction('Выход')
exit_action.triggered.connect(self.close)
self.statusBar().showMessage('Готово')
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
QMainWindow предоставляет готовые области для меню и статусной строки. Метод menuBar() возвращает строку меню, statusBar() - строку состояния. Действия создаются через addAction.
Ошибка: использование QWidget вместо QMainWindow. Только QMainWindow поддерживает меню и статусбар по умолчанию. Если нужно окно с меню, наследуйтесь от QMainWindow.
Как использовать Qt Designer для создания интерфейса?
Визуальный редактор Qt Designer позволяет создавать UI файлы (с расширением .ui), которые затем конвертируются в Python код с помощью утилиты pyuic6.
# Создание UI файла (через Qt Designer) сохраняется как window.ui.
# Конвертация в Python:
# pyuic6 window.ui -o window_ui.py
# Затем в приложении:
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow
from window_ui import Ui_MainWindow
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
После конвертации класс Ui_MainWindow содержит все виджеты, созданные в дизайнере. Доступ к ним осуществляется через self.ui.имя_виджета.
Ошибка: изменение сгенерированного файла вручную. При повторной конвертации изменения будут потеряны. Лучше наследоваться от сгенерированного класса или использовать композицию.
Как выполнять длительные задачи без зависания интерфейса?
Длительные вычисления блокируют цикл событий, интерфейс зависает. Решение - вынести задачу в отдельный поток с помощью QThread.
import sys
import time
from PyQt6.QtCore import QThread, pyqtSignal
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout
class Worker(QThread):
finished = pyqtSignal(str)
def run(self):
time.sleep(3) # имитация долгой работы
self.finished.emit('Работа завершена')
class MainWindow(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
self.label = QLabel('Ожидание...')
self.button = QPushButton('Старт')
self.button.clicked.connect(self.start_work)
layout.addWidget(self.label)
layout.addWidget(self.button)
self.setLayout(layout)
def start_work(self):
self.worker = Worker()
self.worker.finished.connect(self.on_finished)
self.worker.start()
def on_finished(self, text):
self.label.setText(text)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
Класс Worker наследует QThread и переопределяет метод run. Сигнал finished передаёт результат. Главный поток остаётся отзывчивым.
Ошибка: попытка обновлять GUI из потока напрямую. Это может привести к краху. Всегда используйте сигналы для передачи данных в главный поток.
Расширенные примеры использования PyQt
Передача нескольких параметров через кастомный сигнал
Стандартные сигналы передают определённые типы. Для передачи нескольких значений создаётся собственный сигнал с помощью pyqtSignal.
import sys
from PyQt6.QtCore import QObject, pyqtSignal
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton
class Communicator(QObject):
data_signal = pyqtSignal(str, int, float) # сигнал с тремя аргументами
class MainWindow(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
self.label = QLabel('Нет данных')
button = QPushButton('Отправить данные')
button.clicked.connect(self.send_data)
layout.addWidget(self.label)
layout.addWidget(button)
self.setLayout(layout)
self.comm = Communicator()
self.comm.data_signal.connect(self.display_data)
def send_data(self):
self.comm.data_signal.emit('Текст', 42, 3.14)
def display_data(self, text, number, real):
self.label.setText(f'{text} - {number} - {real}')
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
Результат: При нажатии на кнопку в метку выводится строка "Текст - 42 - 3.14". Сигнал data_signal определён с тремя аргументами: строка, целое, вещественное. Слот display_data принимает эти аргументы.
Ошибка: забыть импортировать pyqtSignal из PyQt6.QtCore. Также необходимо, чтобы объект, содержащий сигнал, был унаследован от QObject.
Использование QThreadPool для параллельных задач
QThreadPool упрощает управление потоками, особенно если требуется выполнить много коротких задач. Работы выполняются через QRunnable.
import sys
import time
from PyQt6.QtCore import QThreadPool, QRunnable, pyqtSignal, QObject
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton
class WorkerSignals(QObject):
finished = pyqtSignal(int)
class Task(QRunnable):
def __init__(self, task_id, signals):
super().__init__()
self.task_id = task_id
self.signals = signals
def run(self):
time.sleep(1)
self.signals.finished.emit(self.task_id)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
self.label = QLabel('Задачи не запущены')
button = QPushButton('Запустить 5 задач')
button.clicked.connect(self.run_tasks)
layout.addWidget(self.label)
layout.addWidget(button)
self.setLayout(layout)
self.pool = QThreadPool()
self.completed = 0
def run_tasks(self):
self.completed = 0
for i in range(5):
signals = WorkerSignals()
signals.finished.connect(self.task_done)
task = Task(i, signals)
self.pool.start(task)
self.label.setText('Задачи запущены...')
def task_done(self, task_id):
self.completed += 1
self.label.setText(f'Завершено {self.completed} из 5 задач (последняя: {task_id})')
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
Результат: При нажатии кнопки запускаются 5 задач параллельно (каждая ждёт 1 секунду). Метка обновляется по мере завершения каждой задачи. Используется QThreadPool с автоматическим управлением потоками.
Ошибка: не создать отдельный объект сигналов для каждой задачи, иначе сигналы будут перезаписаны. Важно, чтобы WorkerSignals был унаследован от QObject.
Работа с QGraphicsScene для отображения и анимации фигур
Библиотека QGraphicsScene позволяет создавать сложные 2D-сцены с возможностью взаимодействия и анимации.
import sys
import math
from PyQt6.QtCore import QTimer, QPointF
from PyQt6.QtGui import QGraphicsScene, QGraphicsEllipseItem, QBrush, QColor
from PyQt6.QtWidgets import QApplication, QMainWindow, QGraphicsView
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.scene = QGraphicsScene()
self.view = QGraphicsView(self.scene)
self.setCentralWidget(self.view)
self.setGeometry(100, 100, 500, 500)
# Создание круга
self.circle = QGraphicsEllipseItem(0, 0, 50, 50)
self.circle.setBrush(QBrush(QColor(255, 0, 0)))
self.scene.addItem(self.circle)
# Анимация
self.angle = 0
self.timer = QTimer()
self.timer.timeout.connect(self.animate)
self.timer.start(30) # 30 мс
def animate(self):
self.angle += 0.05
x = 250 + 100 * math.cos(self.angle)
y = 250 + 100 * math.sin(self.angle)
self.circle.setPos(QPointF(x, y))
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
Результат: Открывается окно, в котором красный круг движется по окружности с центром (250,250) и радиусом 100. Анимация реализована с помощью QTimer, который каждые 30 мс обновляет позицию круга.
Ошибка: забыть установить QGraphicsView как центральный виджет. Без этого сцена не отобразится. Также важно задать размер сцены с помощью setSceneRect, если элементы выходят за границы.