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")
}
}
}
Related
I have a list model in back end, at the first the list is empty, new data will append to the list when received from device, I expect view will update as well, but it did not.
So I get the code from somewhere for take a simple example for this issue, and there is a add button which will add a new row into model, it works fine.
I add a timer that triggered 5 seconds later, it will add a now row too, but view not update this row. Could anyone point out what happened?
main.py
import sys, model2
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQuick import QQuickView
class MainWindow(QQuickView):
def __init__(self, parent=None):
super().__init__(parent)
self.model = model2.PersonModel()
self.rootContext().setContextProperty('PersonModel', self.model)
self.rootContext().setContextProperty('MainWindow', self)
self.setSource(QUrl('test2.qml'))
myApp = QApplication(sys.argv)
ui = MainWindow()
ui.show()
sys.exit(myApp.exec_())
model2.py
from PyQt5.QtCore import QAbstractListModel, Qt, pyqtSignal, pyqtSlot, QModelIndex
from threading import Timer
class PersonModel(QAbstractListModel):
NameRole = Qt.UserRole + 1
AgeRole = Qt.UserRole + 2
personChanged = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.persons = [
{'name': 'jon', 'age': 20},
{'name': 'jane', 'age': 25}
]
self.timer = Timer(5, self.foo)
self.timer.start()
def data(self, index, role=Qt.DisplayRole):
row = index.row()
if role == PersonModel.NameRole:
return self.persons[row]["name"]
if role == PersonModel.AgeRole:
return self.persons[row]["age"]
def rowCount(self, parent=QModelIndex()):
return len(self.persons)
def roleNames(self):
return {
PersonModel.NameRole: b'name',
PersonModel.AgeRole: b'age'
}
#pyqtSlot(str, int)
def addPerson(self, name, age):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.persons.append({'name': name, 'age': age})
self.endInsertRows()
def foo(self):
self.addPerson('oops', 19)
#pyqtSlot(int, str, int)
def editPerson(self, row, name, age):
ix = self.index(row, 0)
self.persons[row] = {'name': name, 'age': age}
self.dataChanged.emit(ix, ix, self.roleNames())
#pyqtSlot(int)
def deletePerson(self, row):
self.beginRemoveColumns(QModelIndex(), row, row)
del self.persons[row]
self.endRemoveRows()
test2.qml
import QtQuick 2.6
import QtQuick.Controls 2.2
Rectangle {
anchors.fill: parent
color: "lightgrey"
ListView {
id: listExample
anchors.fill: parent
model: PersonModel
delegate:
Item {
width: 200
height: 60
Row {
Text {
width: 60
text: name + " " + age
horizontalAlignment: Text.AlignHCenter
anchors.verticalCenter: parent.verticalCenter
}
Button{
width: 20
text: "+"
onClicked: PersonModel.editPerson(index, name, age+1)
}
Button{
width: 20
text: "-"
onClicked: PersonModel.editPerson(index, name, age-1)
}
Button{
width: 20
text: "X"
onClicked: PersonModel.deletePerson(index)
}
}
}
}
Button {
width: 50
height: 25
anchors.bottom: parent.bottom
anchors.right: parent.right
text: "add"
onClicked: {
console.log("qml adding")
PersonModel.addPerson("luis", 22)
}
}
}
When the application is run, the following error message is obtained on the console:
QObject::connect: Cannot queue arguments of type 'QQmlChangeSet'
(Make sure 'QQmlChangeSet' is registered using qRegisterMetaType().)
That error indicates that you want to modify an object that is not thread-safe (like the model) from another thread. The model cannot and should not be modified from another thread so using threading.Timer is wrong, in this case you must use QTimer.singleShot():
from PyQt5.QtCore import (
QAbstractListModel,
Qt,
pyqtSignal,
pyqtSlot,
QModelIndex,
QTimer,
)
class PersonModel(QAbstractListModel):
NameRole = Qt.UserRole + 1
AgeRole = Qt.UserRole + 2
personChanged = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.persons = [{"name": "jon", "age": 20}, {"name": "jane", "age": 25}]
QTimer.singleShot(5000, self.foo)
I want to use Python to dynamically add custom components onto my view.qml, but I am not sure about my method because I can't see the Button.qml component in the resulting window. Ideally, I hope to be able to instantiate several rows of buttons into the ColumnLayout. By the way, Button.qml custom quick example/demo button whose's source code I have included too below. It is not QtQuick Button.qml from the PySide6 library
I thought I could just call functions from the view.qml but apparently not? I have seen another method that involves using a separate Javascript file, but I would like to avoid doing that if possible.
Main.py
import os
from pathlib import Path
import sys
from PySide6.QtCore import QUrl, QObject
from PySide6.QtGui import QGuiApplication
from PySide6.QtQuick import QQuickView
class CreateWidgets(QObject):
def instantiate_widgets(self, root, widgetsNeeded):
#for i in widgetsNeeded:
root.doSomething
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView);
qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml')
view.setSource(QUrl.fromLocalFile(qml_file))
if view.status() == QQuickView.Error:
sys.exit(-1)
root = view.rootObject()
widgetCreator = CreateWidgets()
widgetCreator.instantiate_widgets(root, 6)
view.show()
res = app.exec()
# Deleting the view before it goes out of scope is required to make sure all child QML instances
# are destroyed in the correct order.
del view
sys.exit(res)
view.qml
import QtQuick 2.0
import QtQuick.Layouts 1.12
Item{
function doSomething(){
var component = Qt.createComponent("Button.qml");
if (component.status === Component.Ready) {
var button = component.createObject(colLayout);
button.color = "red";
}
console.log("Button created");
}
ColumnLayout{
id: colLayout
Rectangle {
id: page
width: 500; height: 200
color: "lightgray"
}
}
}
Button.qml
import QtQuick 2.0
Rectangle { width: 80; height: 50; color: "red"; anchors.fill: parent}
(Code reference for questions in comments section)
Main.py
import os
import random
import sys
from pathlib import Path
from PySide6.QtCore import Property, QUrl, QObject, Qt
from PySide6.QtGui import QColor, QGuiApplication, QStandardItem, QStandardItemModel
from PySide6.QtQuick import QQuickView
ColorRole = Qt.UserRole
BorderRole = Qt.UserRole
class Manager(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QStandardItemModel()
self._model.setItemRoleNames({Qt.DisplayRole: b"display", ColorRole: b"custom", BorderRole: b"custom2"})
#Property(QObject, constant=True)
def model(self):
return self._model
def add_button(self, text, color, bColor):
item = QStandardItem(text)
item.setData(color, ColorRole)
item.setData(bColor, BorderRole)
self._model.appendRow(item)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
manager = Manager()
view = QQuickView()
view.rootContext().setContextProperty("manager", manager)
view.setResizeMode(QQuickView.SizeRootObjectToView)
qml_file = os.fspath(Path(__file__).resolve().parent / "view.qml")
view.setSource(QUrl.fromLocalFile(qml_file))
if view.status() == QQuickView.Error:
sys.exit(-1)
for i in range(6):
color = QColor(*random.sample(range(0, 255), 3))
border = QColor(*random.sample(range(0, 255), 3))
manager.add_button(f"button-{i}", color, border)
view.show()
res = app.exec()
sys.exit(res)
View.qml
import QtQuick 2.0
import QtQuick.Layouts 1.12
Item {
ColumnLayout {
id: colLayout
anchors.fill: parent
Repeater{
model: manager.model
Button{
color: model.custom
text: model.display
border.color: model.custom2
}
}
}
}
Button.qml
import QtQuick 2.0
Rectangle {
id: root
property alias text: txt.text
width: 80
height: 50
color: "red"
border.color: "black"
Text{
id: txt
anchors.centerIn: parent
}
}
The idea is that Python (or C++) provide the information to QML to create the items for example using a model and a Repeater.
On the other hand, if an item is going to be a child of a ColumnLayout then it should not use anchors since there will be conflicts since they both handle the geometry of the item.
Considering the above I have added more elements such as variable text, variable color, etc. to demonstrate the logic.
import os
import random
import sys
from pathlib import Path
from PySide6.QtCore import Property, QUrl, QObject, Qt
from PySide6.QtGui import QColor, QGuiApplication, QStandardItem, QStandardItemModel
from PySide6.QtQuick import QQuickView
ColorRole = Qt.UserRole
class Manager(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QStandardItemModel()
self._model.setItemRoleNames({Qt.DisplayRole: b"display", ColorRole: b"custom"})
#Property(QObject, constant=True)
def model(self):
return self._model
def add_button(self, text, color):
item = QStandardItem(text)
item.setData(color, ColorRole)
self._model.appendRow(item)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
manager = Manager()
view = QQuickView()
view.rootContext().setContextProperty("manager", manager)
view.setResizeMode(QQuickView.SizeRootObjectToView)
qml_file = os.fspath(Path(__file__).resolve().parent / "view.qml")
view.setSource(QUrl.fromLocalFile(qml_file))
if view.status() == QQuickView.Error:
sys.exit(-1)
for i in range(6):
color = QColor(*random.sample(range(0, 255), 3))
manager.add_button(f"button-{i}", color)
view.show()
res = app.exec()
sys.exit(res)
import QtQuick 2.0
import QtQuick.Layouts 1.12
Item {
ColumnLayout {
id: colLayout
anchors.fill: parent
Repeater{
model: manager.model
Button{
color: model.custom
text: model.display
}
}
}
}
import QtQuick 2.0
Rectangle {
id: root
property alias text: txt.text
width: 80
height: 50
color: "red"
Text{
id: txt
anchors.centerIn: parent
}
}
Update:
Each role must have a different numerical value since otherwise Qt cannot identify it, in your case you could change:
BorderRole = Qt.UserRole + 1
I've been trying to update my progress bar on my GUI but I can't figure out how to do it... The code I have provided shows the progress updating - I just need that value to update the progress bar on my GUI too! It's a QML file from Qt Creator.
main.py
import os
import sys
import time
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine, qmlRegisterType
from PySide2.QtCore import QObject, Slot, Signal, QTimer, QUrl, QThread
from pathlib import Path
from multiprocessing.pool import ThreadPool
class MainWindow(QObject):
def __init__(self):
QObject.__init__(self)
#Slot()
def thread_progress(self):
print("Worker")
self.worker = Worker()
self.thread = QThread()
self.worker.moveToThread(self.thread)
self.thread.start()
self.worker.run()
class Worker(QObject):
progress_value = Signal(float)
#Slot()
def run(self):
self.progress = 0
self.total = 100
for i in range(0, self.total):
self.update_progress()
def update_progress(self):
print(f"{self.progress} / {self.total}")
self.progress += 1
self.progress_value.emit(self.progress)
time.sleep(1)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
# Get Context
main = MainWindow()
engine.rootContext().setContextProperty("backend", main)
# Set App Extra Info
app.setOrganizationName("zardoss")
app.setOrganizationDomain("N/A")
engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
The main.py file connects with the main.qml file. The GUI has a button, progress bar and a text input element. I am only concerned with the progress bar filling up after the generate button has been pressed.
You can see from the console after pressing the generate button that the values from 1-100 are being filled up but it doesn't fill the progress bar as I am unsure how.
main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.15
import QtQuick.Shapes 1.15
Window {
id: mainWindow
width: 750
height: 500
visible: true
color: "#00000000"
// Remove title bar
flags: Qt.Window | Qt.FramelessWindowHint
// Properties
property int windowStatus: 0
property int windowMargin: 10
title: qsTr("Progress Bar")
Rectangle {
id: bg
color: "#2c313c"
border.color: "#f12c313c"
border.width: 1
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.rightMargin: windowMargin
anchors.leftMargin: windowMargin
anchors.bottomMargin: windowMargin
anchors.topMargin: windowMargin
z:1
Rectangle {
id: appContainer
height: 480
color: "#00000000"
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.rightMargin: 1
anchors.leftMargin: 1
anchors.bottomMargin: 1
anchors.topMargin: 1
Rectangle {
id: content
color: "#00000000"
anchors.left: parent.left
anchors.right: parent.right
anchors.top: topBar.bottom
anchors.bottom: parent.bottom
anchors.topMargin: 0
Button {
id: btnGenerate
x: 265
y: 44
width: 200
height: 50
text: "Generate"
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.horizontalCenterOffset: 0
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenterOffset: 165
// colorPressed: "#1e5425"
// colorMouseOver: "#42b852"
font.pointSize: 14
display: AbstractButton.TextBesideIcon
font.bold: false
// colorDefault: "#328a3f"
anchors.rightMargin: 250
onPressed: {
// backend.generate()
backend.thread_progress()
}
}
ProgressBar{
id: progressBar
x: 239
y: 64
visible: true
width: 661
height: 250
// text: "%"
anchors.verticalCenter: parent.verticalCenter
value: 0
//bgColor: "#00000/*000"
//dropShadowColor: "#20000000"
//samples: 16
anchors.verticalCenterOffset: -15
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
DropShadow{
anchors.fill: bg
horizontalOffset: 0
verticalOffset: 0
radius: 10
samples: 16
color: "#80000000"
source:bg
z: 0
}
Connections{
target: backend
function onLinkValid(valid) {
if(valid === true) {
textField.textColor = "#00FF00"
} else {
textField.textColor = "#FF00FF"
}
}
}
}
The problem in your case is that the signal that has the progress value is not connected to any element of the GUI.
In this case, a signal must be created in the class exposed to QML that connects with the ProgressBar, and this signal must receive information from the signal from the Worker. On the other hand, you should not invoke the "run()" method directly since it will be executed in the thread from which it was invoked, which in your case is the main thread so it will block the GUI, instead you must invoke it indirectly with a signal or using the QTimer.
import os
import sys
import time
from pathlib import Path
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QObject, Slot, Signal, QTimer, QUrl, QThread
class Backend(QObject):
progress_changed = Signal(float, name="progressChanged")
def __init__(self, parent=None):
super().__init__(parent)
self.worker = Worker()
self.worker.progress_changed.connect(self.progress_changed)
self.worker_thread = QThread()
self.worker.moveToThread(self.worker_thread)
self.worker_thread.start()
#Slot()
def start_worker(self):
QTimer.singleShot(0, self.worker.run)
class Worker(QObject):
progress_changed = Signal(float)
#Slot()
def run(self):
self.progress = 0
self.total = 100
for i in range(0, self.total):
self.update_progress()
def update_progress(self):
print(f"{self.progress} / {self.total}")
self.progress += 1
self.progress_changed.emit(self.progress)
time.sleep(1)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
backend = Backend()
engine.rootContext().setContextProperty("backend", backend)
engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml"))
if not engine.rootObjects():
sys.exit(-1)
ret = app.exec_()
# backend.worker_thread.quit()
# backend.worker_thread.wait()
sys.exit(ret)
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
Window {
id: mainWindow
width: 750
height: 500
visible: true
color: "#00000000"
title: qsTr("Progress Bar")
Row {
spacing: 10
anchors.centerIn: parent
Button {
id: btnGenerate
text: "start"
onClicked: backend.start_worker()
}
ProgressBar {
id: progressBar
from: 0
to: 100.0
}
}
Connections {
target: backend
function onProgressChanged(progress) {
progressBar.value = progress;
}
}
}
i am trying to insert/edit a python list that is subclassed from QAbstractListModel in pyqt5. this python list is read in the model property of ListView element in qml. i have no issues displaying the data in qml. problem arises when i try to append new data into the python list.
the following is what i have done so far:
main.py:
import sys, model2
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQuick import QQuickView
class MainWindow(QQuickView):
def __init__(self, parent=None):
super().__init__(parent)
self.model = model2.PersonModel()
self.rootContext().setContextProperty('PersonModel', self.model)
self.rootContext().setContextProperty('MainWindow', self)
self.setSource(QUrl('test2.qml'))
myApp = QApplication(sys.argv)
ui = MainWindow()
ui.show()
sys.exit(myApp.exec_())
model2.py
from PyQt5.QtCore import QAbstractListModel, Qt, pyqtSignal, pyqtSlot
class PersonModel(QAbstractListModel):
Name = Qt.UserRole + 1
Age = Qt.UserRole + 2
personChanged = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.persons = [
{'name': 'jon', 'age': 20},
{'name': 'jane', 'age': 25}
]
def data(self, QModelIndex, role):
row = QModelIndex.row()
if role == self.Name:
return self.persons[row]["name"]
if role == self.Age:
return self.persons[row]["age"]
def rowCount(self, parent=None):
return len(self.persons)
def roleNames(self):
return {
Qt.UserRole + 1: b'name',
Qt.UserRole + 2: b'age'
}
#pyqtSlot()
def addData(self):
self.beginResetModel()
self.persons = self.persons.append({'name': 'peter', 'age': 22})
self.endResetModel()
print(self.persons)
#pyqtSlot()
def editData(self):
print(self.model.persons)
test2.qml:
import QtQuick 2.6
import QtQuick.Controls 2.2
Rectangle {
anchors.fill: parent
color: "lightgrey"
ListView {
id: listExample
anchors.fill: parent
model: PersonModel
delegate: Text {
text: name + " " + age
}
}
Button {
width: 50
height: 25
anchors.bottom: parent.bottom
text: "add"
onClicked: {
console.log("qml adding")
PersonModel.addData()
}
}
.
.
.
}
error occurs when i click the add button which calls the addData method in model2.py. error lies in the rowCount and error message says TypeError: object of type 'NoneType' has no len(). do i have to emit the changes or pass in some index and role value so qml knows what is new/old and only reflect the changes accordingly?
any form of guidance is greatly appreciated!
The error you get is caused by the following line of code:
self.persons = self.persons.append({'name': 'peter', 'age': 22})
It is caused because the append function does not return anything, so it was meant to assign None to self.persons
To insert new data you must call beginInsertRows() and endInsertRows() to notify the view of the change.
the data method must be identical to that shown in the documentation, ie it must have the following format:
def data(self, index, role=Qt.DisplayRole):
The same with the rowCount method:
def rowCount(self, parent=QModelIndex()):
I have implemented the methods addPerson, editPerson and deletePerson that adds, edits and deletes a data from the list respectively. Also I added the necessary items to the .qml to be able to test it.
model2.py
from PyQt5.QtCore import QAbstractListModel, Qt, pyqtSignal, pyqtSlot, QModelIndex
class PersonModel(QAbstractListModel):
NameRole = Qt.UserRole + 1
AgeRole = Qt.UserRole + 2
personChanged = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.persons = [
{'name': 'jon', 'age': 20},
{'name': 'jane', 'age': 25}
]
def data(self, index, role=Qt.DisplayRole):
row = index.row()
if role == PersonModel.NameRole:
return self.persons[row]["name"]
if role == PersonModel.AgeRole:
return self.persons[row]["age"]
def rowCount(self, parent=QModelIndex()):
return len(self.persons)
def roleNames(self):
return {
PersonModel.NameRole: b'name',
PersonModel.AgeRole: b'age'
}
#pyqtSlot(str, int)
def addPerson(self, name, age):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.persons.append({'name': name, 'age': age})
self.endInsertRows()
#pyqtSlot(int, str, int)
def editPerson(self, row, name, age):
ix = self.index(row, 0)
self.persons[row] = {'name': name, 'age': age}
self.dataChanged.emit(ix, ix, self.roleNames())
#pyqtSlot(int)
def deletePerson(self, row):
self.beginRemoveColumns(QModelIndex(), row, row)
del self.persons[row]
self.endRemoveRows()
test2.qml
import QtQuick 2.6
import QtQuick.Controls 2.2
Rectangle {
anchors.fill: parent
color: "lightgrey"
ListView {
id: listExample
anchors.fill: parent
model: PersonModel
delegate:
Item {
width: 200
height: 60
Row {
Text {
width: 60
text: name + " " + age
horizontalAlignment: Text.AlignHCenter
anchors.verticalCenter: parent.verticalCenter
}
Button{
width: 20
text: "+"
onClicked: PersonModel.editPerson(index, name, age+1)
}
Button{
width: 20
text: "-"
onClicked: PersonModel.editPerson(index, name, age-1)
}
Button{
width: 20
text: "X"
onClicked: PersonModel.deletePerson(index)
}
}
}
}
Button {
width: 50
height: 25
anchors.bottom: parent.bottom
anchors.right: parent.right
text: "add"
onClicked: {
console.log("qml adding")
PersonModel.addPerson("luis", 22)
}
}
}
Edit:
.py
#pyqtSlot(int, str, int)
def insertPerson(self, row, name, age):
self.beginInsertRows(QModelIndex(), row, row)
self.persons.insert(row, {'name': name, 'age': age})
self.endInsertRows()
.qml
PersonModel.insertPerson(2, "luis", 1111)
Perhaps this is a mistake:
self.dataChanged.emit(ix, ix, self.roleNames())
After all, the signal is announced:
personChanged = pyqtSignal()
That is, it should be:
self.personChanged.emit(ix, ix, self.roleNames())
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_())