Evgenii Legotckoi
3 апреля 2017 г. 0:22

PyQt5 - Урок 007. Работаем с QML QtQuick (Сигналы и слоты)

А теперь более глубоко погрузимся в работу с Qt с помощью PyQt5, воспользовавшись современными возможностями Qt. Под такими возможностями я подразумеваю QtQuick и QML. PyQt5 позволяет использовать классы Qt, которые могут обрабатывать QML код, а следовательно, можно написать интерфейс на QML, а также передавать сигналы в QML слой и вызывать слоты объектов, наследованных от QObject из QML слоя.

Чтобы познакомиться с такими возможностями PyQt5, напишем программу, которая реализует следующие задачи:

  • Интерфейс программы должен быть написан на QML
  • Должен быть реализован класс, наследованный от QObject и написанный на python, с которым будем взаимодействовать из QML
  • Приложение с помощью данного класса должно будет складывать и вычитать целые числа

Внешний вид приложения должен выглядеть следующим образом:


Структура проекта

В проекте будет всего два файла:

  • main .py - файл приложения на python, там же будет класс для вычислений
  • main.qml - файл интерфейса на QML

Сигналы в PyQt5

Сигнатура сигнала в общем случае будет выглядеть следующим образом:

  1. PyQt5.QtCore.pyqtSignal
( types [, name [, revision=0 [, arguments=[] ]]])

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

| Параметры: | * types – Типы, определяющие сигнатуру C++ сигнала. Каждый тип может быть объектом типа Python или строкой, которая является именем типа C++. Альтернативно, каждый может быть последовательностью аргументов типа. В этом случае каждая последовательность определяет сигнатуру перегрузки другого сигнала. Первая перегрузка будет использоваться по умолчанию.
name – Название сигнала. Если оно опущено, используется имя атрибута класса. Это может быть задано только как аргумент ключевого слова.
revision – ревизия сигнала, который экспортируется в QML. Это может быть задано только как аргумент ключевого слова.
* arguments – Последовательность имен аргументов сигнала, которые экспортируются в QML. Это может быть задано только как аргумент ключевого слова.
|

Слоты в PyQt5

Для объявления слотов в PyQt5 используется специальный декоратор.

  1. PyQt5.QtCore.pyqtSlot
( types [, name [, result [, revision=0 ]]])

| Параметры: | * types – Типы, которые определяют сигнатуру слота C ++. Каждый тип может быть объектом типа Python или строкой, которая является именем типа C ++.
name – Имя слота, который будет отображаться на C ++. Если опущено, будет использовано имя украшаемого метода Python. Это может быть задано только как аргумент ключевого слова.
revision – ревизия слота, который экспортируется в QML. Это может быть задано только как аргумент ключевого слова.
* result – Тип результата и может быть объектом типа Python или строкой, определяющей тип C ++. Это может быть задано только как аргумент ключевого слова.
|

main .py

  1. from PyQt5.QtGui import QGuiApplication
  2. from PyQt5.QtQml import QQmlApplicationEngine
  3. from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
  4.  
  5.  
  6. class Calculator(QObject):
  7. def __init__(self):
  8. QObject.__init__(self)
  9.  
  10. # cигнал передающий сумму
  11. # обязательно даём название аргументу через arguments=['sum']
  12. # иначе нельзя будет его забрать в QML
  13. sumResult = pyqtSignal(int, arguments=['sum'])
  14.  
  15. subResult = pyqtSignal(int, arguments=['sub'])
  16.  
  17. # слот для суммирования двух чисел
  18. @pyqtSlot(int, int)
  19. def sum(self, arg1, arg2):
  20. # складываем два аргумента и испускаем сигнал
  21. self.sumResult.emit(arg1 + arg2)
  22.  
  23. # слот для вычитания двух чисел
  24. @pyqtSlot(int, int)
  25. def sub(self, arg1, arg2):
  26. # вычитаем аргументы и испускаем сигнал
  27. self.subResult.emit(arg1 - arg2)
  28.  
  29.  
  30. if __name__ == "__main__":
  31. import sys
  32.  
  33. # создаём экземпляр приложения
  34. app = QGuiApplication(sys.argv)
  35. # создаём QML движок
  36. engine = QQmlApplicationEngine()
  37. # создаём объект калькулятора
  38. calculator = Calculator()
  39. # и регистрируем его в контексте QML
  40. engine.rootContext().setContextProperty("calculator", calculator)
  41. # загружаем файл qml в движок
  42. engine.load("main.qml")
  43.  
  44. engine.quit.connect(app.quit)
  45. sys.exit(app.exec_())

main.qml

  1. import QtQuick 2.5
  2. import QtQuick.Controls 1.4
  3. import QtQuick.Layouts 1.2
  4.  
  5. ApplicationWindow {
  6. visible: true
  7. width: 640
  8. height: 240
  9. title: qsTr("PyQt5 love QML")
  10. color: "whitesmoke"
  11.  
  12. GridLayout {
  13. anchors.top: parent.top
  14. anchors.left: parent.left
  15. anchors.right: parent.right
  16. anchors.margins: 9
  17.  
  18. columns: 4
  19. rows: 4
  20. rowSpacing: 10
  21. columnSpacing: 10
  22.  
  23. Text {
  24. text: qsTr("First number")
  25. }
  26.  
  27. // Поле ввода первого числа
  28. TextField {
  29. id: firstNumber
  30. }
  31.  
  32. Text {
  33. text: qsTr("Second number")
  34. }
  35.  
  36. // Поле ввода второго числа
  37. TextField {
  38. id: secondNumber
  39. }
  40.  
  41. Button {
  42. height: 40
  43. Layout.fillWidth: true
  44. text: qsTr("Sum numbers")
  45.  
  46. Layout.columnSpan: 2
  47.  
  48. onClicked: {
  49. // Вызываем слот калькулятора, чтобы сложить числа
  50. calculator.sum(firstNumber.text, secondNumber.text)
  51. }
  52. }
  53.  
  54. Text {
  55. text: qsTr("Result")
  56. }
  57.  
  58. // Здесь увидим результат сложения
  59. Text {
  60. id: sumResult
  61. }
  62.  
  63. Button {
  64. height: 40
  65. Layout.fillWidth: true
  66. text: qsTr("Subtraction numbers")
  67.  
  68. Layout.columnSpan: 2
  69.  
  70. onClicked: {
  71. // Вызываем слот калькулятора, чтобы вычесть числа
  72. calculator.sub(firstNumber.text, secondNumber.text)
  73. }
  74. }
  75.  
  76. Text {
  77. text: qsTr("Result")
  78. }
  79.  
  80. // Здесь увидим результат вычитания
  81. Text {
  82. id: subResult
  83. }
  84. }
  85.  
  86. // Здесь забираем результат сложения или вычитания чисел
  87. Connections {
  88. target: calculator
  89.  
  90. // Обработчик сигнала сложения
  91. onSumResult: {
  92.   // sum было задано через arguments=['sum']
  93. sumResult.text = sum
  94. }
  95.  
  96. // Обработчик сигнала вычитания
  97. onSubResult: {
  98.   // sub было задано через arguments=['sub']
  99. subResult.text = sub
  100. }
  101. }
  102. }
  103.  

Вам это нравится? Поделитесь в социальных сетях!

v
  • 4 августа 2017 г. 3:49

Спасибо. Все очень доходчиво!

P
  • 5 марта 2018 г. 17:32

Подскажите как закидывать компоненты из python на форму qml? Я хочу график matplotlib закинуть на page qml

Evgenii Legotckoi
  • 5 марта 2018 г. 17:52

Вот сейчас Вы меня конкретно загрузили этим вопросом. Не работал с этой библиотекой, но я глянул, что есть сети по этому поводу и кое-какие мысли уже есть у меня. Но надо ещё самому разобраться, что можно придумать. Сегодня/завтра после работы гляну, и потом отвечу Вам, какие мысли у меня будут.

Но вообще, всё скорее всего должно сводиться к множественному наследованию от QQuickPaitedItem и класса библиотек matplotlib.

 
С теоретической исследовательской точки зрения мне это интересно. С практической не уверен, что стоит использовать, тем более, что есть Qt Charts.
 
Но я гляну, что там можно сделать.
P
  • 5 марта 2018 г. 17:58

Спасибо, Вам, за отклик. У меня существует готовое решение с использованием pyqt5 + matplotlib. Где я просто на форму кидаю виджет, а виджет есть график matplotlib. Теперь хотелось бы интерфейс приятный сделать. Вчера потратил весь день на Qt Charts и осознал, что он очень не очень. Ощущение, что студенты на коленках делали.

Evgenii Legotckoi
  • 5 марта 2018 г. 18:06
Ощущение, что студенты на коленках делали.
Ну... может поэтому Qt Charts вынесли из коммерческой лицензии и теперь он доступен в Community Edition ))))
Пожалуй, будет зависеть от того, что вам требуется, возможно, что его функционала действительно не хватает...

Если у вас есть готовый минимальный пример приложения с PyQt5 + matplotlib, то не могли бы создать тему обсуждения в этом разделе форума и прикрепить архив того примера.

Там по сути нужно правильно продёрнуть виджет в QML, либо перенаследовать некоторые классы, но обычно это делается через наследование от QQuickPaintedItem . Продолжим тогда обсуждение в ветке на форуме.
k
  • 15 июня 2018 г. 16:59

onClicked: { // Вызываем слот калькулятора, чтобы вычесть числа calculator.sub(firstNumber.text, secondNumber.text) //код после вызова слота }
k
  • 15 июня 2018 г. 17:00

почему не выполняется код после вызова слота?

Evgenii Legotckoi
  • 18 июня 2018 г. 13:12

Я вот сейчас банальность скажу, но у меня всё работало. Так что даже и не знаю, надо на код смотреть, что ещё у вас добавлено или отсутствует из библиотек.


P/S/ Извините, вы сейчас всё подряд пробуете или работаете именно с Python?
k
  • 8 октября 2018 г. 17:30

Изменив математические действия столкнулся с проблемой - не выводит десятичные дроби

Evgenii Legotckoi
  • 8 октября 2018 г. 17:36

Проблема должна быть в описании слота, там целочисленные аргументы

  1. @pyqtSlot(int, int)

В вашем случае, я думаю, так должно быть

  1. @pyqtSlot(float, float)
k
  • 8 октября 2018 г. 17:42

заменил, та же картина, причем в python я вывожу переменную 11.5, а в qml вижу 11


Evgenii Legotckoi
  • 8 октября 2018 г. 17:44

Вот это ещё замените

  1. sumResult = pyqtSignal(int, arguments=['sum'])

на вот это

  1. sumResult = pyqtSignal(float, arguments=['sum'])
ogustbiller
  • 8 апреля 2020 г. 19:12

Круто! Немного начинает проясняться что к чему. Спасибо.

ogustbiller
  • 1 ноября 2022 г. 19:08

А можно ли из QML сделать привязку свойства к свойству пайтоновского объекта? Ну, т.е. , например, у нашего объекта Calculator обхвялем свойства sumresult и subresult c с декоратором @pyqtProperty, а в QMLе делаем типа такого:

  1. // Здесь увидим результат сложения
  2. Text {
  3. id: sumResult
  4. text: calculator.sumResult
  5. }
  6. ...
  7. // Здесь увидим результат сложения
  8. Text {
  9. id: subResult
  10. text: calculator.subResult
  11. }

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь