I have a very simple qml+python app to play and test signal/slot communication.
All works fine so far, but when I run the app, a ReferenceError is reported on the QML side.
However, all works fine, it is so simple code:
QML:
import QtQuick 2.0
import QtQuick.Window 2.0
Window {
width: 1000
height: 480
visible: true
title: qsTr("Hello World")
Connections {
target: signalEmitter
ignoreUnknownSignals : true
function onSignal() {
console.log("HELLO QML")
}
}
Rectangle{
height: 100
width: 100
color: "green"
MouseArea {
anchors.fill: parent
onClicked: {
signalEmitter.sayHello()
}
}
}
Rectangle{
anchors.fill: parent
color: "transparent"
border.color: "black"
}
}
Python:
from PySide6.QtCore import QObject, Signal, Slot
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
import sys
class PythonSignalEmitter(QObject):
signal = Signal(str)
#Slot()
def sayHello(self):
print("HELLO PYTHON")
self.signal.emit("HELLO")
if __name__ == '__main__':
app = QGuiApplication([])
engine = QQmlApplicationEngine()
engine.load("main.qml")
signal_emitter = PythonSignalEmitter()
engine.rootContext().setContextProperty("signalEmitter", signal_emitter)
sys.exit(app.exec())
Why do I keep getting the error:
ReferenceError: signalEmitter is not defined
on line 12 in qml file. (app runs and signal/slot works as expected)
You should add the context properties before loading any qml:
if __name__ == '__main__':
app = QGuiApplication([])
engine = QQmlApplicationEngine()
signal_emitter = PythonSignalEmitter()
engine.rootContext().setContextProperty("signalEmitter", signal_emitter)
engine.load("main.qml") # new place
sys.exit(app.exec())
I'm trying to figure out how to use RectangularGlow to create a glowing label with PyQt5, but I don't know about QML, and I can't figure out how to do. Here is what I have so far:
example.qml:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
import QtGraphicalEffects 1.12
Label {
text: "Identifiant"
width: 400
height: 200
Rectangle {
id: background
anchors.fill: parent
color: "black"
}
RectangularGlow {
id: effect
anchors.fill: rect
glowRadius: 10
spread: 0.2
color: "white"
cornerRadius: rect.radius + glowRadius
}
Rectangle {
id: rect
color: "black"
anchors.centerIn: parent
width: Math.round(parent.width / 1.5)
height: Math.round(parent.height / 2)
radius: 25
}
}
And the python code that uses it:
import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtWidgets import *
if __name__ == "__main__":
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load('example.qml')
label = engine.rootObjects()[0]
win = QMainWindow()
win.setCentralWidget(label)
win.show()
sys.exit(app.exec_())
But I get this error:
Traceback (most recent call last):
File "test2.py", line 16, in <module>
win.setCentralWidget(label)
TypeError: setCentralWidget(self, QWidget): argument 1 has unexpected type 'QObject'
If you want to use qml items in Qt Widgets then you must use a QQuickWidget (or QQuickView + QWidget::createWindowContainer()):
import os.path
import sys
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtQuickWidgets import QQuickWidget
CURRENT_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)))
if __name__ == "__main__":
app = QApplication(sys.argv)
filename = os.path.join(CURRENT_DIR, "example.qml")
qml_widget = QQuickWidget()
qml_widget.setResizeMode(QQuickWidget.SizeRootObjectToView)
qml_widget.setSource(QUrl.fromLocalFile(filename))
win = QMainWindow()
win.setCentralWidget(qml_widget)
win.show()
sys.exit(app.exec_())
you can have glow effect with :
import QtGraphicalEffects 1.12
also this is a label with glow effect :
Rectangle {
width: 50
clip: true
height: 20
color: "#592e2e2e"
layer.enabled: true
layer.effect: Glow {
samples: 14
color: "#592e2e2e"
transparentBorder: true
}
Text {
clip: true
anchors.fill: parent
wrapMode: Text.WrapAnywhere
horizontalAlignment: Text.AlignHCenter
}
}
I search on the web but I don't find how select a row in a listview create in qml file with python. I test but each time I have error.
I just start qml and perhaps the base is not good. So my question, with my code, it's possible to acceed to my ListView ? If yes how can I select, in start application, the second item (for example) ?
When I do that :
print(win.findChild(QObject, "listview22").setCurrentIndex(2)))
I have this message
AttributeError: 'QQuickItem' object has no attribute 'setCurrentIndex'
But listview22 is a listview not an item.
Thank you for your help.
PS : I just start qml so if my script are no good tell me and then I can learn good programmation
main.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
ApplicationWindow {
title: qsTr("WoodMan Training")
width: 1000
height: 700
visible: true
GridLayout {
id: grid
anchors.fill: parent
columns: 3
anchors.margins: 0
columnSpacing: 0
ColumnLayout {
Layout.columnSpan: 1
Layout.row: 2
Layout.column: 0
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: 10
Layout.maximumWidth: 250
// ---
RowLayout{
Main_ListView{objectName: "lstGroupe"; id:lstGroupe; pyModel: ModelGroupe; pyClass: ModelGroupe}
}
Item {Layout.fillWidth: true; Layout.fillHeight: true}
}
}
}
Main_ListView.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
Rectangle {
property var pyClass: 0
property var pyModel: 0
border.color: "red"
Layout.fillWidth: true
height:150
ListView {
anchors.topMargin: 10
anchors.bottomMargin: 10
anchors.leftMargin: 10
anchors.fill: parent
id: listview22
objectName: "listview22"
model: pyModel
clip: true // --- Empeche la surbrillance de sortir du cadre
delegate: Component {
Item {
width: 200
height: 20
property int item_id: iid
Row {
Text {
anchors.verticalCenter: parent.verticalCenter
width: 60
text: " " + libelle
}
}
MouseArea {
anchors.fill: parent
onClicked: {
listview22.currentIndex = index
pyClass.onClickItem(item_id)
}
}
}
}
highlight: Rectangle {color: '#CDCDCD'}
focus: true
}
Button {
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.rightMargin: 5
anchors.bottomMargin: 5
width: 28
height: 25
iconSource: "ico/math-add-icon.png"
onClicked: {
//console.log("qml adding")
pyClass.onClickInsert(22, "aluis")
}
}
}
Main.py
from PyQt5.QtCore import QAbstractListModel, Qt, pyqtSignal, pyqtSlot, QModelIndex
import sys
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQuick import QQuickView
from PyQt5.QtGui import *#QGuiApplication, QColor, QStandardItemModel, QStandardItem
from PyQt5.QtQml import *#QQmlApplicationEngine, QQmlComponent
from PyQt5.QtCore import *#QUrl, Qt, QCoreApplication, QAbstractListModel, QModelIndex, QTimer, qsrand, qrand, QTime, QObject
from PyQt5.QtQuick import *#QQuickItem
class gGroupe(QAbstractListModel):
iid = Qt.UserRole + 1
libelle = Qt.UserRole + 2
data_change = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.liste = []
self.insert_list()
self.liste2 = {}
self.createIndex(0,0,125)
self.sort(1, order = Qt.DescendingOrder)
# ------------------------------------------------------------------
def roleNames(self):
return {
gGroupe.iid: b'iid',
gGroupe.libelle: b'libelle',
}
def data(self, index, role=Qt.DisplayRole):
row = index.row()
if role == gGroupe.iid:
return self.liste[row]["iid"]
if role == gGroupe.libelle:
return self.liste[row]["libelle"]
def rowCount(self, parent=QModelIndex()):
return len(self.liste)
# ------------------------------------------------------------------
def insert_list(self):
self.liste = [
{'iid': 10, 'libelle': 'groupe10'},
{'iid': 11, 'libelle': 'groupe11'},
{'iid': 12, 'libelle': 'groupe12'},
]
def delete(self, row):
self.beginRemoveRows(QModelIndex(), row, row)
del self.liste[row]
self.endRemoveRows()
def delete_all(self):
self.beginRemoveRows(QModelIndex(), 0, len(self.liste))
del self.liste[row]
self.endRemoveRows()
def select(self):
pass
#pyqtSlot(int, str)
def onClickInsert(self, iid=10, libelle='toto'):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.liste.append({'iid': iid, 'libelle': libelle})
self.endInsertRows()
return self.index(len(self.liste)-1)
#pyqtSlot(int)
def onClickItem(self, iid):
print(iid)
"""
#pyqtSlot(int, str, int)
def editPerson(self, row, name, age):
ix = self.index(row, 0)
self.persons[row] = {'iid': 22, 'name': name, 'age': age}
self.dataChanged.emit(ix, ix, self.roleNames())
"""
class MainWindow(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.g_groupe = gGroupe()
engine.rootContext().setContextProperty('ModelGroupe', self.g_groupe)
engine.load('main.qml')
win = engine.rootObjects()[0]
#win.findChild(QObject, "txtLibelle").setProperty("text", 'toto')
print(win.findChild(QObject, "listview22").setCurrentIndex(2))
a = win.findChild(QObject, "PersonModel")#.removeRows( 0, model.rowCount() )
if __name__ == "__main__":
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
# Création d'un objet QQmlContext pour communiquer avec le code QML
#ctx = engine.rootContext()
py_mainapp = MainWindow()
sys.exit(app.exec())
You have the following errors:
You should not export QML objects to python since it can bring you many errors like the one you get and even more critical errors, that is recommended in C++.
When trying to obtain the ListView using findChild (QObject, "listview22") in C++, only one QObject would be obtained but python tries to deduce the type and for that it has as a limit the public API of Qt, in your ListView it is not a QListView, and its type does not exist in Python or C++ since it is part of the private API, so a nearby class is QQuickItem since ListView inherits from that class.
What is recommended is to create a QObject and add qproperties that map the logic and export to QML, so if you modify the QObject in python it will modify it in QML.
main.py
from PyQt5.QtCore import (
pyqtProperty,
pyqtSignal,
pyqtSlot,
QAbstractListModel,
QModelIndex,
QObject,
Qt,
QTimer,
)
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
class gGroupe(QAbstractListModel):
iid = Qt.UserRole + 1
libelle = Qt.UserRole + 2
data_change = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.liste = []
self.insert_list()
self.sort(0, order=Qt.DescendingOrder)
def roleNames(self):
return {gGroupe.iid: b"iid", gGroupe.libelle: b"libelle"}
def data(self, index, role=Qt.DisplayRole):
row = index.row()
if role == gGroupe.iid:
return self.liste[row]["iid"]
if role == gGroupe.libelle:
return self.liste[row]["libelle"]
def rowCount(self, parent=QModelIndex()):
return len(self.liste)
# ------------------------------------------------------------------
def insert_list(self):
self.beginResetModel()
self.liste = [
{"iid": 10, "libelle": "groupe10"},
{"iid": 11, "libelle": "groupe11"},
{"iid": 12, "libelle": "groupe12"},
]
self.endResetModel()
def delete(self, row):
self.beginRemoveRows(QModelIndex(), row, row)
del self.liste[row]
self.endRemoveRows()
def delete_all(self):
self.beginRemoveRows(QModelIndex(), 0, len(self.liste))
del self.liste[row]
self.endRemoveRows()
def select(self):
pass
#pyqtSlot(int, str)
def insert(self, iid=10, libelle="toto"):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.liste.append({"iid": iid, "libelle": libelle})
self.endInsertRows()
#pyqtSlot(int)
def onClickItem(self, iid):
print(iid)
class MainWindow(QObject):
currentIndexChanged = pyqtSignal(int)
def __init__(self, parent=None):
super().__init__(parent)
self._g_groupe = gGroupe(self)
self._current_index = 0
#pyqtProperty(QObject, constant=True)
def g_groups(self):
return self._g_groupe
#pyqtProperty(int, notify=currentIndexChanged)
def currentIndex(self):
return self._current_index
#currentIndex.setter
def currentIndex(self, index):
if self._current_index == index:
return
self._current_index = index
self.currentIndexChanged.emit(index)
if __name__ == "__main__":
import sys
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
main_window = MainWindow()
engine.rootContext().setContextProperty("main_window", main_window)
engine.load("main.qml")
if not engine.rootObjects():
sys.exit(app.exec_())
main_window.currentIndex = 2
sys.exit(app.exec())
main.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
ApplicationWindow {
title: qsTr("WoodMan Training")
width: 1000
height: 700
visible: true
Connections{
target: main_window
onCurrentIndexChanged: lstGroupe.currentIndex = main_window.currentIndex
}
GridLayout {
id: grid
anchors.fill: parent
columns: 3
anchors.margins: 0
columnSpacing: 0
ColumnLayout {
Layout.columnSpan: 1
Layout.row: 2
Layout.column: 0
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: 10
Layout.maximumWidth: 250
// ---
RowLayout{
Main_ListView{
id:lstGroupe;
model: main_window.g_groups
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
}
Main_ListView.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
import QtQuick.Window 2.2
Rectangle {
id: root
property alias model: listview22.model
property alias currentIndex: listview22.currentIndex
border.color: "red"
Layout.fillWidth: true
height:150
ListView {
id: listview22
model: root.model
anchors.topMargin: 10
anchors.bottomMargin: 10
anchors.leftMargin: 10
anchors.fill: parent
clip: true
delegate: Component {
Item {
width: 200
height: 20
property int item_id: iid
Row {
Text {
anchors.verticalCenter: parent.verticalCenter
width: 60
text: " " + libelle
}
}
MouseArea {
anchors.fill: parent
onClicked: {
listview22.currentIndex = index
listview22.model.onClickItem(item_id)
}
}
}
}
highlight: Rectangle {color: '#CDCDCD'}
focus: true
}
Button {
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.rightMargin: 5
anchors.bottomMargin: 5
width: 28
height: 25
iconSource: "ico/math-add-icon.png"
onClicked: {
listview22.model.insert(22, "aluis")
}
}
}
What I'm trying to do is update the source of an image with QML in PyQt5. When I use element.setProperty("source", "./testImage.png") to change the image I get the following error message.
QML Image: Protocol "" is unknown
Any idea on how to fix this?
I've looked at other ways to interact with QML elements and if possible I'd like to stick with changing the image through Python code rather then through just QML.
main.py
from PyQt5.QtWidgets import *
from PyQt5.QtQml import *
from PyQt5.QtCore import *
from PyQt5.QtQuick import *
from PyQt5 import *
import sys
import resource_rc
class MainWindow(QQmlApplicationEngine):
def __init__(self):
super().__init__()
self.load("main.qml")
self.rootContext().setContextProperty("MainWindow", self)
self.window = self.rootObjects()[0]
self.cardLeft = self.window.findChild(QObject, "cardLeft")
#pyqtSlot()
def changeImage(self):
self.cardLeft.setProperty("source", "./images/3_of_clubs.png")
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
main.qml
import QtQml 2.11
import QtQuick 2.3
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.11
ApplicationWindow{
id: screen
width: 720
height: 490
visible: true
Material.theme: Material.Dark
Item {
id: cards
width: parent.width
height: parent.height - toolBar.height
anchors {
top: parent.top;
bottom: toolBar.top;
bottomMargin: 10
}
RowLayout {
anchors.fill: parent
spacing: 20
Image {
objectName: "cardLeft"
id: cardLeft
Layout.fillWidth: true
Layout.maximumHeight: 250
Layout.minimumHeight: parent.height
Layout.margins: 20
source: "./testImage.png"
fillMode: Image.PreserveAspectFit
}
}
}
Button {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.fillHeight: true
Layout.maximumHeight: 100
Layout.maximumWidth: 300
Layout.margins: 20
text: qsTr("Change Image")
highlighted: true
Material.accent: Material.color(Material.LightGreen)
onClicked: MainWindow.changeImage()
}
}
You have to pass a QUrl for it you must use QUrl::fromLocalFile():
import os
import sys
from PyQt5 import QtCore, QtGui, QtQml
# import resource_rc
dir_path = os.path.dirname(os.path.realpath(__file__))
class MainWindow(QtQml.QQmlApplicationEngine):
def __init__(self):
super().__init__()
self.load(QtCore.QUrl.fromLocalFile(os.path.join(dir_path, "main.qml")))
self.rootContext().setContextProperty("MainWindow", self)
if self.rootObjects():
self.window = self.rootObjects()[0]
self.cardLeft = self.window.findChild(QtCore.QObject, "cardLeft")
#QtCore.pyqtSlot()
def changeImage(self):
if self.cardLeft:
url = QtCore.QUrl.fromLocalFile(os.path.join(dir_path, "images/3_of_clubs.png"))
self.cardLeft.setProperty("source", url)
if __name__ == '__main__':
app = QtGui.QGuiApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
I'm following this PySide tutorial as close as possible using PyQt5. When I run my code, I get this error: ReferenceError: pythonListModel is not defined, and the list shows up black with no items.
This is my code
def main():
platform = Platform("Windows")
platform_wrp = qml_platforms.PlatformsWrapper(platform)
platform_model = qml_platforms.PlatformsListModel([platform_wrp])
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine(QUrl("main.qml"))
context = engine.rootContext()
context.setContextProperty('pythonListModel', platform_model)
window = engine.rootObjects()[0]
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
my model and wrapper
class PlatformsWrapper(QObject):
def __init__(self, platform):
QObject.__init__(self)
self.platform = platform
def full_name(self):
return str(self.platform.full_name)
changed = pyqtSignal()
full_name = pyqtProperty("QString", _full_name, notify=changed)
class PlatformsListModel(QAbstractListModel):
def __init__(self, platforms):
QAbstractListModel.__init__(self)
self.platforms = platforms
def rowCount(self, parent=QModelIndex()):
return len(self.platforms)
def data(self, index):
if index.isValid():
return self.platforms[index.row()]
return None
and my QML
import QtQuick 2.1
import QtQuick.Controls 1.1
ApplicationWindow{
ListView {
id: pythonList
width: 400
height: 200
model: pythonListModel
delegate: Component {
Rectangle {
width: pythonList.width
height: 40
color: ((index % 2 == 0)?"#222":"#111")
Text {
id: title
elide: Text.ElideRight
text: model.platform.full_name
color: "white"
font.bold: true
anchors.leftMargin: 10
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
}
MouseArea {
anchors.fill: parent
}
}
}
}
}
Why can't Qt find my contextProperty?
The problem is that "main.qml" is loaded before you set context property. Try load file after you setup your context:
def main():
platform = Platform("Windows")
platform_wrp = qml_platforms.PlatformsWrapper(platform)
platform_model = qml_platforms.PlatformsListModel([platform_wrp])
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
context = engine.rootContext()
context.setContextProperty('pythonListModel', platform_model)
engine.load( QUrl("main.qml") ) #load after context setup
window = engine.rootObjects()[0]
window.show()
sys.exit(app.exec_())