Your question has many aspects so I will try to be detailed in my answer and also this answer will be continuously updated because this type of questions are often asked but they are solutions for a specific case so I am going to take the liberty of giving it a general approach and be specific in the possible scenarios.
QML to Python:
Your method works because the type conversion in python is dynamic, in C++ it does not happen. It works for small tasks but it is not maintainable, the logic must be separated from the view so it should not be dependent. To be concrete, let's say that the printed text will be taken by the logic to perform some processing, then if you modify the name of the signal, or if the data does not depend on ApplicationWindow
but on another element, etc. then you will have to change a lot connection code.
The recommended as you indicate is to create a class that is responsible for mapping the data you need your logic and embed it in QML
, so if you change something in the view you just change the connection:
Example:
main.py
import sys
from PySide2.QtCore import QObject, Signal, Property, QUrl
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
class Backend(QObject):
textChanged = Signal(str)
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.m_text = ""
@Property(str, notify=textChanged)
def text(self):
return self.m_text
@text.setter
def setText(self, text):
if self.m_text == text:
return
self.m_text = text
self.textChanged.emit(self.m_text)
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
backend = Backend()
backend.textChanged.connect(lambda text: print(text))
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("backend", backend)
engine.load(QUrl.fromLocalFile('main.qml'))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Test")
width: 640
height: 480
visible: true
Column{
TextField{
id: tf
text: "Hello"
}
Button {
text: qsTr("Click Me")
onClicked: backend.text = tf.text
}
}
}
Now if you want the text to be provided by another element you just have to change the line: onClicked: backend.text = tf.text
.
Python to QML:
I can not tell you what you did wrong with this method because you do not show any code, but I do indicate the disadvantages. The main disadvantage is that to use this method you must have access to the method and for that there are 2 possibilities, the first one is that it is a rootObjects as it is shown in your first example or searching through the objectName, but it happens that you initially look for the object, you get it and this is removed from QML, for example the Pages of a StackView are created and deleted every time you change pages so this method would not be correct.
The second method for me is the correct one but you have not used it correctly, unlike the QtWidgets that focus on the row and the column in QML the roles are used. First let's implement your code correctly.
First textlines
is not accessible from QML
since it is not a qproperty
. As I said you must access through the roles, to see the roles of a model you can print the result of roleNames()
:
model = QStringListModel()
model.setStringList(["hi", "ho"])
print(model.roleNames())
output:
{
0: PySide2.QtCore.QByteArray('display'),
1: PySide2.QtCore.QByteArray('decoration'),
2: PySide2.QtCore.QByteArray('edit'),
3: PySide2.QtCore.QByteArray('toolTip'),
4: PySide2.QtCore.QByteArray('statusTip'),
5: PySide2.QtCore.QByteArray('whatsThis')
}
In the case that you want to obtain the text you must use the role Qt::DisplayRole
, whose numerical value according to the docs is:
Qt::DisplayRole 0 The key data to be rendered in the form of text. (QString)
so in QML
you should use model.display
(or only display
). so the correct code is as follows:
main.py
import sys
from PySide2.QtCore import QUrl, QStringListModel
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
model = QStringListModel()
model.setStringList(["hi", "ho"])
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("myModel", model)
engine.load(QUrl.fromLocalFile('main.qml'))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Test")
width: 640
height: 480
visible: true
ListView{
model: myModel
anchors.fill: parent
delegate: Text { text: model.display }
}
}
If you want it to be editable you must use the model.display = foo
:
import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Test")
width: 640
height: 480
visible: true
ListView{
model: myModel
anchors.fill: parent
delegate:
Column{
Text{
text: model.display
}
TextField{
onTextChanged: {
model.display = text
}
}
}
}
}
There are many other methods to interact with Python/C++ with QML but the best methods involve embedding the objects created in Python/C++ through setContextProperty
.
As you indicate the docs of PySide2 is not much, it is being implemented and you can see it through the following link. What exists most are many examples of PyQt5 so I recommend you understand what are the equivalences between both and make a translation, this translation is not hard since they are minimal changes.