Интерфейс на Python: пишем с помощью PyQt

На языке Python написаны такие гиганты, как YouTube и Instagram. Если у вас в планах написать свой клевый сервис, то пора познакомиться с азами разработки UI.

Ранее мы уже познакомились с библиотекой PyQt и рассмотрели несколько простых примеров разработки приложений. Так же мы упомянули, что на Python написать код можно двумя способами: с помощью утилиты QtDesigner или вручную. Если вы выбрали второй вариант, то сегодня мы продолжим напишем простой пример интерфейса с PyQT с обработчиками и событиями. В прошлый раз мы пробовали написать небольшое окно с тулбаром

Сигналы и слоты

Интерфейс — это связь между программой и пользователем. Главное, что нужно знать при разработке на PyQT — это такие определения как сигналы и слоты. Сигнал — это какое-либо событие, которые вы сделали в интерфейсе: нажали на кнопку мыши, выбрали какой-то элемент из выпадающего списка, изменили что-то или удалили. Создать сигнал можно при помощи ключевого слова emit().

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

Какие бывают сигналы?

  • clicked — Данный сигнал испускается при активизации кнопки (т.е. когда нажатая кнопка отпускается при нахождении указателя мыши внутри кнопки)
  • pressed — Событие генерируется при нажатии кнопки мыши
  • released — Событие генерируется при отпускании кнопки мыши (которая была нажата раньше)
  • textChanged — генерация сигнала, когда происходит изменение текста (само изменение текста — это событие),
  • valueChanged — генерация сигнала, когда происходит изменение значения

Что может служить слотом?

  • Функция
  • Метод класса
  • Объект с методом вызова (__call__)
  • Анонимная функция (lambda-функция)
  • Слот класса (декоратор pyqtSignal — о нем ниже)

Пример:

Наша задача — создать объект и связать его с сигналом и реализовать обработчик этого самого сигнала. В теории это выглядит так

  1. Кнопка (список, элемент интерфейса). В общем, объект.
  2. Далее идёт событие (нажатие, клик).
  3. При наступлении п.2 (события) срабатывает сигнал
  4. Когда срабатывает п.3 (сигнал) вызывается обработчик

Сигнал может быть и встроенный, но мы напишем свой. Итак, у нас есть MainWindow — главное окно программы. Он наследуется от класса QMainWindow — встроенного класса, который импортируется из QWidget.  Так же у нас есть класс для объектов — AnyObjects, который в свою очередь наследуется от QObject. Внутри него pyqtSignal() — это конструктор, который позволяет создать собственный сигнал

В MainWindow не забываем прописывать метод super().__init__(). Как мы помним это позволяет работать с атрибутами класса родителя. Обратите внимание на функцию set_params, вызываемой внутри класса. В ней мы и прописываем наш сигнал в виде объекта — self.ao = AnyObjects()

Когда мы кликаем на объект срабатывает событие (метод) mousePressEvent. Функция emit() служит как раз для генерации сигнала. Обработчиком в нашем примере является вызов функции on_clicked, которую мы написали ниже — она просто выводит сообщение в консоль. Функция on_clicked является слотом.

Обратите внимание, что в данном примере срабатывает два сигнала — собственный, который мы создали и встроенным — стандартным (self.close — вызов функции закрытия окна). Именно поэтому при клике мы видим сообщение в консоли и наше окно закрываются.

import sys
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QMainWindow, QApplication

# базовый класс для всех объектов модуля
class AnyObjects(QObject):
    # создаем свой сигнал
    own_signal = pyqtSignal()

# создаем главное окно
class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.set_params()

    # метод, который срабатывает при нажатии на окно
    def mousePressEvent(self, event):
        # генерируем сигнал
        self.ao.own_signal.emit()
        self.close()

    def set_params(self):
        self.ao = AnyObjects()
        # обработчик сигнала, связанного с объектом
        self.ao.own_signal.connect(self.on_clicked)
        # параметры главного окна
        self.setGeometry(900, 300, 290, 150)
        self.setWindowTitle('Пример работы самописного сигнала')
        self.show()

    def on_clicked(self):
        print('Тут сообщение')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mw = MainWindow()
    sys.exit(app.exec_())

Результат при запуске:

Интерфейс на Python: пишем с помощью PyQt

Результат после клика по окну (1 — оно закрылось, 2 — вывелось сообщение)

Интерфейс на Python: пишем с помощью PyQt

Создание слайдера и прогресс-бара

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

Файлы имеют расширение UI (UserInterface) и являются обычным XML-кодом. Их можно программно перевести в py-файлы — мы их не создаем вручную

Интерфейс на Python: пишем с помощью PyQt

Данный интерфейс сохраняется в файл с расширением .ui. Как выглядит файл .ui, если его открыть в текстовом редакторе:

Интерфейс на Python: пишем с помощью PyQt

Таким образом, можно создавать простенькие элементы интерфейса, а затем релизить их в py-модули с помощью PyQt5 UI code generator.

Итак, что у нас в главном скрипте. Прежде всего, создадим сигнал ползунка — changed_value = pyqtSignal(int). Т.е. когда мы будем двигать ползунок будет генерироваться сигнал, а после него, соответственно, сработает обработчик. Функция on_changed_value начинает срабатывать, когда меняется значение, тогда и генерируется сигнал. В ней передается параметр value, значение которого постоянно меняется. Далее у нас идёт класс прогресс бара. Здесь нужно обратить внимание на специальный декоратор @pyqtSlot(int) — это объект (слот), который «ловит» наш сигнал, если точнее int (целое число). Таким образом наш прогресс бар устанавливает это значение.

import sys
from PyQt5.QtWidgets import QDialog, QWidget, QApplication
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
import slider
import progress

class SliderClass(QDialog):
    # Добавляем Qt—сигнал как атрибут класса
    changed_value = pyqtSignal(int)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.sd = slider.Ui_SliderDialog()
        self.sd.setupUi(self)

        # Связываем оригинальный сигнал слайдера с функцией данного класса
        # при передвигании слайдер будет срабатывать ф-ция on_changed_value
        self.sd.horizontalSlider.valueChanged.connect(self.on_changed_value)

    def on_changed_value(self, value):
        # В этой ф-ции on_changed_value мы генерируем сигнал
        self.changed_value.emit(value)


class ProgressClass(QDialog):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.ui = progress.Ui_ProgressDialog()
        self.ui.setupUi(self)

    def make_connection(self, slider_object):
        # Связываем "свой" сигнал со "своим" слотом
        slider_object.changed_value.connect(self.get_slider_value)

    # Создаем Qt—слот
    @pyqtSlot(int)
    def get_slider_value(self, val):
        self.ui.progressBar.setValue(val)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    slider = SliderClass()
    progress = ProgressClass()
    # Непосредственное связывание ProgressBar'а и Sliser'а
    progress.make_connection(slider)
    # отображаем слайдер
    slider.show()
    # отображаем прогресс-бар
    progress.show()
    sys.exit(app.exec_())

Результат :

Интерфейс на Python: пишем с помощью PyQt

Перехват событий

Перехват событий в Qt поможет «поймать» и выполнять обработку событий в режиме реального времени. Как это выглядит? В нашем примере мы создаем классы, которые наследуется от QWidget, а значит мы можем создать модальное окно, менять его размер и так далее. Самое главное здесь — функция event с параметром e на входе. В ней мы используем класс QEvent, где объекты являются событиями. Например, QEvent.MouseButtonPress — это клик мышкой. Мы перехватываем события и обрабатываем

import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QWidget, QApplication

class WindowClass(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.resize(300, 100)

    def event(self, e):
        if e.type() == QtCore.QEvent.KeyPress:
            print("Вы нажали клавишу на клавиатуре")
            print("Код:", e.key(), ", текст:", e.text())
        elif e.type() == QtCore.QEvent.Close:
            print("Вы закрыли окно")
        elif e.type() == QtCore.QEvent.MouseButtonPress:
            print("Совершен клик мышью. Координаты:", e.x(), e.y())

        # Событие отправляется дальше
        return QWidget.event(self, e)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    wc = WindowClass()
    wc.show()
    sys.exit(app.exec_())

Результат:

Интерфейс на Python: пишем с помощью PyQt

Как вам статья?

Рейтинг
( Пока оценок нет )
Диджитал на минималках
Комментарии: 7
  1. .

    В первом примере я не понял как вызывается функция mousePressEvent и где используется аргумент event?

  2. Аноним

    В первом примере, от куда взялось ао?
    def mousePressEvent(self, event):
    # генерируем сигнал
    self.ao.own_signal.emit()
    self.close()

    1. Админ (автор)

      Несмотря на то, что объект класса создается ниже ( self.ao = AnyObjects() ), функция set_params вызывается раньше, чем происходит вызов метода mousePressEvent. При рефакторинге ошибки никакой не будет

  3. GPS navigaciju remontas

    12295 766162I like what you guys are up too. Such smart function and reporting! Carry on the superb works guys Ive incorporated you guys to my blogroll. I believe it will improve the value of my website 119850

  4. YOURURL.com

    540519 507902Im so happy to read this. This is the type of manual that needs to be given and not the accidental misinformation thats at the other blogs. Appreciate your sharing this greatest doc. 760549

  5. re mushroom grow kits legal in Canada

    961158 403198Thanks for blogging and i enjoy the blog posting so no public comments.,,,,,,,,,,, 486041

  6. los cabos luxury real estate

    206217 49757I believe one of your commercials caused my internet browser to resize, you could well want to put that on your blacklist. 978764

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: