6

How can bind the buttons I create in a .qml script to python PyQt5 code?

example: python:

import sys
from PyQt5.QtCore import QObject, QUrl, Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine

if __name__ == "__main__":
  app = QApplication(sys.argv)
  engine = QQmlApplicationEngine()
  ctx = engine.rootContext()
  ctx.setContextProperty("main", engine)

  engine.load('test.qml')

  win = engine.rootObjects()[0]
  win.show()
  sys.exit(app.exec_())

qml:

import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.1

ApplicationWindow {
 title: qsTr("Test Invoke")

 width: 200
 height: 100

 Button{
  y : 70
  text : "About"
  onClicked: {
   print('Hello')
  }

 }
}

How can I do something with Python when the button is clicked? Also: Does anyone has a good resource of examples or doc. about pyqt + qml (qt quick)?

Daivid
  • 627
  • 3
  • 12
  • 22
  • The code you show is python code that prints hello, what else do you want to do specifically that you can't put there instead? – Oliver Jun 09 '14 at 01:43
  • This application is very simple, I just want to connect the qml button with pyqt. My real application will use the buttons to start communications with an Web Service. – Daivid Jun 09 '14 at 02:09
  • You asked for an example of PyQt + QML. Here is a link to a Sudoku game where all the logic is in Python and the UI is in QML: https://github.com/pkobrien/sudoku-qml – Patrick Keith O'Brien May 06 '15 at 18:06

2 Answers2

3

If you name the button, you can connect to its onClick signal, or to a custom signal that it emits in onClicked. Example:

ApplicationWindow {
 title: qsTr("Test Invoke")
 width: 200
 height: 100

 Button {
  signal messageRequired
  objectName: "myButton"
  y : 70
  text : "About"
  onClicked: messageRequired()

 }
}

Note the signal in Button and the objectName property. Then the Python code just before exec could be for example:

def myFunction():
    print 'handler called'

button = win.findChild(QObject, "myButton")
button.messageRequired.connect(myFunction)
button.clicked.connect(myFunction) # works too

Note that in the Button above, onClicked just emits the messageRequired signal, so it is better to drop the custom signal and connect to clicked directly. Both onClicked() and any slots connected to clicked will get called when you click button.

Oliver
  • 27,510
  • 9
  • 72
  • 103
  • The older example didn't work. PyQt5 doesn't have the QDeclarativeView class, as said [here](http://pyqt.sourceforge.net/Docs/PyQt5/pyqt4_differences.html); Finally, I couldn't find anything similar to what I'm trying to do in the demos. – Daivid Jun 09 '14 at 06:06
  • All I got is: Traceback (most recent call last): File ".\main.py", line 29, in win.myButton.messageRequired.connect(myFunction) AttributeError: 'QWindow' object has no attribute 'myButton' – Daivid Jun 09 '14 at 18:14
  • Oh! changing your result a little bit makes it work! If I put the "signal messageRequired" as a direct child attribute of ApplicationWindow, I can capture the signal in python. Do you know how to capture the signal from the signal specified from the button? @Schollii – Daivid Jun 09 '14 at 22:16
  • @Daivid I have updated: to find the button you have to use the `objectName` property (`id` won't do), and signal in Button works. – Oliver Jun 11 '14 at 04:50
  • that works. But I tried something different: using win.findChild(QObject, 'anotherButton') that is inside an inner hierarchy of the QML. Then it didn't work. Do I have to iterate over the children of win to find it? What I did to work around this was to declare the signals inside ApplicationWindow and use in the buttons. – Daivid Jun 11 '14 at 22:03
  • @Daivid Strange, because `findChild` by default (no 3rd argument) searches recursively. If `findChild` can't find it, it's either not there, the type is wrong, or the `objectName` has a type or is wrong. Check those things (it's unfortunate that `findChild` doesn't search id's as well, or that there isn't an option to search `id` or even better, to specify which attribute to use to find the child). I would post another question showing the inner hierarchy and how `findChild` works at top level but not inner. – Oliver Jun 12 '14 at 02:17
  • 1
    That might be a bad architecture. The QML might be a UI layer on top of a model or business logic layer in Python. The model layer should not know that a button is controlling it, since then you could easily change the control to say a gesture or other control. The UI layer should know that there is some model that it controls and views. To do this, you implement some model class in Python and register it with QML. Then in the QML you can connect the clicked signal of the button to some slot of the model instance. I struggled with this too: https://github.com/bootchk/demoQMLPyQt.git – bootchk Oct 15 '14 at 21:55
2

There is another solution which uses a Python model (QObject) in QML.

Python

engine = QQmlApplicationEngine()
engine.load("main.qml")

class Greeter(QObject):
    @pyqtSlot(str)
    def sayHello(self, name):
        print("Hello, " + name)

ctx = engine.rootContext()
ctx.setContextProperty("greeter", Greeter())

QML

Button {
    onClicked: {
        greeter.sayHello("Alice")
    }
}

Ref.

  1. https://wiki.qt.io/Qt_for_Python/Connecting_QML_Signals
Giorgos Xou
  • 1,461
  • 1
  • 13
  • 32
bitdancer
  • 1,215
  • 2
  • 19
  • 34