PySide QML Charts update Series with real-time data - python

I tried to update the chart series dynamically with new data recieved using OPC UA Protocol
Python Code:
class Thread(QThread):
motor1 = Signal(int)
motor2 = Signal(int)
motor3 = Signal(int)
var = Signal(int)
strr = Signal(str)
def run(self):
client.connect()
while True:
d1 = client.get_node("ns=4;s=Deger_1")
d2 = client.get_node("ns=4;s=Deger_2")
d3 = client.get_node("ns=4;s=Deger_3")
button = client.get_node("ns=4;s=QT_Button")
string = client.get_node("ns=4;s=QT_String")
button = button.get_value()
string = string.get_value()
d1 = d1.get_value()
d2 = d2.get_value()
d3 = d3.get_value()
self.motor1.emit(d1)
self.motor2.emit(d2)
self.motor3.emit(d3)
self.var.emit(d1)
self.strr.emit(string)
class ChartModel(QObject):
def __init__(self, parent=None):
super(ChartModel, self).__init__(parent)
self.thread = Thread()
self.thread.var.connect(self.opcua)
self.thread.start()
self.timestamp = time()
self.my_data = []
self.my_list = []
self.index = -1
#Slot(QtCharts.QAbstractSeries)
def update_series(self, series):
self.index += 1
if self.index > 4:
self.index = 0
series.clear()
for p in self.my_data[self.index]:
series.append(p.x(), p.y())
#Slot()
def generateData(self):
for i in range(5):
my_list = []
for j in range(500):
my_list.append(QPoint(j, random.uniform(1, 70)))
self.my_data.append(my_list)
#Slot(int)
def opcua(self, val):
tsignal = time() - self.timestamp
XY = QPoint(tsignal, val)
self.my_list.append(XY)
#Slot()
def get_data(self):
self.my_data.append(self.my_list)
print(len(self.my_data))
class MainWindow(QObject):
def __init__(self):
QObject.__init__(self)
if __name__ == "__main__":
app = QApplication(sys.argv)
main = MainWindow()
chartmodel = ChartModel()
engine = QQmlApplicationEngine()
# engine.rootContext().setContextProperty("backend", main)
engine.rootContext().setContextProperty("chartmodel", chartmodel)
engine.load(os.path.join(os.path.dirname(__file__), "qml/main.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
QML Code
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtCharts 2.0
import QtQuick.Controls.Styles 1.0
import QtQuick.Dialogs 1.1
Item {
width: 1000
height: 800
Rectangle {
id: rectangle
property int amountOfData: 0
color: "#27273a"
anchors.fill: parent
Timer{
id: miTimer
interval: 100
running: true
repeat: true
onTriggered: {
chartmodel.update_series(chartViewItem.series(0))
}
}
MessageDialog {
id: msgbox
title: "Error"
text: "Data are not accepted !"
onAccepted: {
msgbox.close();
}
Component.onCompleted: visible = false
}
ChartView {
id: chartViewItem
x: 0
y: 0
width: 752
height: 592
antialiasing: true
theme:ChartView.ChartThemeDark
ValueAxis {
id: axisX
min:0
max:500
}
ValueAxis{
id: axisY
min:0
max:100
}
Rectangle {
id: horizontalScrollMask
visible: false
anchors.fill: parent
}
MouseArea {
id: chartMouseAreaA
anchors.fill: parent
anchors.rightMargin: -8
anchors.bottomMargin: -8
anchors.leftMargin: 8
anchors.topMargin: 8
acceptedButtons: Qt.LeftButton | Qt.RightButton
onMouseXChanged: {
if ((mouse.buttons & Qt.LeftButton) == Qt.LeftButton) {
chartViewItem.scrollLeft(mouseX - horizontalScrollMask.x);
horizontalScrollMask.x = mouseX;
}
}
onPressed: {
if (mouse.button == Qt.LeftButton) {
horizontalScrollMask.x = mouseX;
}
}
}
}
TextField {
id: mintxt
x: 766
y: 68
placeholderText: qsTr("min X")
}
TextField {
id: maxtxt
x: 766
y: 124
placeholderText: qsTr("max X")
}
Button {
id: button
x: 825
y: 176
text: qsTr("Zoom")
onClicked: {
if(mintxt.text>maxtxt.text)
msgbox.open()
else
axisX.min=mintxt.text;
axisX.max=maxtxt.text;
}
}
}
Component.onCompleted: {
var series = chartViewItem.createSeries(ChartView.SeriesTypeSpline,"Random",axisX,axisY)
chartmodel.get_data()
}
}
When I used chartmodel.generateData() , the code works with generated data but when I tried the get_data function it returns the error below:
Error: list index out of range
How to update the chart with new data instead of generated data?

The problem is that the OP is copying code without understanding it, for example is the index necessary? What is the purpose of the index? Surely in the original example the author of the code was trying to show a trivial example showing a cyclic data.
In this case, you have to iterate over the list:
import os
import sys
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtCharts import *
from PySide2.QtQml import *
from time import *
import random
class Thread(QThread):
motor1 = Signal(int)
motor2 = Signal(int)
motor3 = Signal(int)
var = Signal(int)
strr = Signal(str)
def run(self):
client.connect()
while True:
d1 = client.get_node("ns=4;s=Deger_1")
d2 = client.get_node("ns=4;s=Deger_2")
d3 = client.get_node("ns=4;s=Deger_3")
button = client.get_node("ns=4;s=QT_Button")
string = client.get_node("ns=4;s=QT_String")
button = button.get_value()
string = string.get_value()
d1 = d1.get_value()
d2 = d2.get_value()
d3 = d3.get_value()
self.motor1.emit(d1)
self.motor2.emit(d2)
self.motor3.emit(d3)
self.var.emit(d1)
self.strr.emit(string)
class ChartModel(QObject):
def __init__(self, parent=None):
super(ChartModel, self).__init__(parent)
self.thread = Thread()
self.thread.var.connect(self.opcua)
self.thread.start()
self.timestamp = time()
self.my_list = []
#Slot(QtCharts.QAbstractSeries)
def update_series(self, series):
series.clear()
for p in self.my_list:
series.append(p.x(), p.y())
#Slot(int)
def opcua(self, val):
tsignal = time() - self.timestamp
XY = QPoint(tsignal, val)
self.my_list.append(XY)
class MainWindow(QObject):
def __init__(self):
QObject.__init__(self)
if __name__ == "__main__":
app = QApplication(sys.argv)
main = MainWindow()
chartmodel = ChartModel()
engine = QQmlApplicationEngine()
# engine.rootContext().setContextProperty("backend", main)
engine.rootContext().setContextProperty("chartmodel", chartmodel)
engine.load(os.path.join(os.path.dirname(__file__), "qml/main.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtCharts 2.0
import QtQuick.Controls.Styles 1.0
import QtQuick.Dialogs 1.1
Window {
width: 1000
height: 800
visible: true
Rectangle {
id: rectangle
property int amountOfData: 0
color: "#27273a"
anchors.fill: parent
Timer{
id: miTimer
interval: 100
running: true
repeat: true
onTriggered: {
chartmodel.update_series(chartViewItem.series(0))
}
}
MessageDialog {
id: msgbox
title: "Error"
text: "Data are not accepted !"
onAccepted: {
msgbox.close();
}
Component.onCompleted: visible = false
}
ChartView {
id: chartViewItem
x: 0
y: 0
width: 752
height: 592
antialiasing: true
theme:ChartView.ChartThemeDark
ValueAxis {
id: axisX
min:0
max:500
}
ValueAxis{
id: axisY
min:0
max:100
}
Rectangle {
id: horizontalScrollMask
visible: false
anchors.fill: parent
}
MouseArea {
id: chartMouseAreaA
anchors.fill: parent
anchors.rightMargin: -8
anchors.bottomMargin: -8
anchors.leftMargin: 8
anchors.topMargin: 8
acceptedButtons: Qt.LeftButton | Qt.RightButton
onMouseXChanged: {
if ((mouse.buttons & Qt.LeftButton) == Qt.LeftButton) {
chartViewItem.scrollLeft(mouseX - horizontalScrollMask.x);
horizontalScrollMask.x = mouseX;
}
}
onPressed: {
if (mouse.button == Qt.LeftButton) {
horizontalScrollMask.x = mouseX;
}
}
}
}
TextField {
id: mintxt
x: 766
y: 68
placeholderText: qsTr("min X")
}
TextField {
id: maxtxt
x: 766
y: 124
placeholderText: qsTr("max X")
}
Button {
id: button
x: 825
y: 176
text: qsTr("Zoom")
onClicked: {
if(mintxt.text>maxtxt.text)
msgbox.open()
else
axisX.min=mintxt.text;
axisX.max=maxtxt.text;
}
}
}
Component.onCompleted: {
var series = chartViewItem.createSeries(ChartView.SeriesTypeSpline,"Random",axisX,axisY)
miTimer.start()
}
}

Related

Animating elements on canvas in QML from python

Here is the main.py script:
import sys, os, math
import numpy as np
import time
from PyQt5 import *
class Tab(QFrame):
def __init__(self):
super().__init__()
self.setGeometry(600, 600, 600, 600)
self.setWindowTitle("PyQt5 Tab Widget")
self.setWindowIcon(QIcon("../QML Files/Icons/Tab.png"))
vbox = QVBoxLayout()
tabWidget = QTabWidget()
tabWidget.addTab(Example01(), "Ex1")
vbox.addWidget(tabWidget)
self.setLayout(vbox)
class GG(QObject):
polygonsChanged = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self._polygons = []
def modify_Polygons(self) -> None:
for x in range(10):
time.sleep(1)
GG.set_dynamic_polygons(x, self)
def get_polygons(self) -> None:
return self._polygons
def set_polygons(self, polygons):
self._polygons = polygons
self.polygonsChanged.emit()
polygons = pyqtProperty(
"QVariant", fget=get_polygons, fset=set_polygons,
notify=polygonsChanged
)
def set_dynamic_polygons(i, p_gg) -> None:
numpy_arrays = np.array(
[[[100+i, 100], [150, 200], [50, 300]],
[[50, 60], [160, 20], [400, 10]]]
)
def set_polygons(myArray) -> []:
polygons = []
for ps in myArray:
polygon = []
# print("ps = "); print(ps)
for p in ps:
# print("p = "); print(p)
e = QPointF(*p)
polygon.append(e)
polygons.append(polygon)
return polygons
p_gg.polygons = set_polygons(numpy_arrays)
class Example01(QWidget):
def __init__(self):
super().__init__()
vbox = QVBoxLayout(self)
vbox.setContentsMargins(0, 0, 0, 0)
self.gg = GG()
GG.set_dynamic_polygons(0, self.gg)
view = QQuickWidget()
ROOT_DIR = os.path.realpath(os.path.dirname(sys.argv[0]))
qml = os.path.join(ROOT_DIR, "QML Files", "Demo01.qml")
view.setSource(QUrl.fromLocalFile(qml))
view.rootContext().setContextProperty("gg", self.gg)
view.setResizeMode(QQuickWidget.SizeRootObjectToView)
vbox.addWidget(view)
if __name__ == "__main__":
App = QApplication(sys.argv)
tabDialog = Tab()
tabDialog.show()
App.exec()
Next follows the Demo01.qml
import QtQuick 2.14
import QtQuick.Window 2.14
import QtGraphicalEffects 1.0
import QtQuick.Controls 2.15
Rectangle {
id: rect
visible: true
anchors.fill: parent
LinearGradient {
anchors.fill: parent
//setting gradient at 45 degrees
start: Qt.point(rect.width, 0)
end: Qt.point(0, rect.height)
gradient: Gradient {
GradientStop { position: 0.0; color: "#ee9d9d" }
GradientStop { position: 1.0; color: "#950707" }
}
}
Button{
id: btn
width: 100
height: 30
x: {parent.width - btn.width - 20}
y: {parent.height - btn.height - 20}
text: "Click Me"
onClicked: gg.modify_Polygons()
}
Canvas {
id: drawingCanvas
anchors.fill: parent
onPaint: {
var ctx = getContext("2d")
ctx.fillStyle = "rgb(100%,70%,30%)"
ctx.lineWidth = 5
ctx.strokeStyle = "blue"
//console.log(gg)
for(var i in gg.polygons){
var polygon = gg.polygons[i]
ctx.beginPath()
for(var j in polygon){
var p = polygon[j]
if(j === 0)
ctx.moveTo(p.x, p.y)
else
ctx.lineTo(p.x, p.y)
}
ctx.closePath()
ctx.fill()
ctx.stroke()
}
}
}
/*Connections{
target: gg
function onpolygonsChanged(){ drawingCanvas.requestPaint()}
}*/
}
The two triangles when I start the program are shown very nice.
The trouble appears when I click the button.
I tried all sorts of variants to indent the modify_Polygons() function inside the GG class.
In every version I received the same error: Property 'modify_Polygons' of object GG(0x103056bd0) is not a function comming from the Demo01.qml -> row 30.
I don't have any clue why this error appears because for me looks like a legitimate function.
What did I make wrong, please?
Only the elements of the QMetaObject are accessible from QML like the qproperties, signals and slots, and the other elements of the class are not visible from QML. So one solution is to use the #pyqtSlot decor.
On the other hand you should not use time.sleep as it will block the main thread and consequently freeze the GUI. If you want to do periodic tasks then use a QTimer.
#pyqtSlot()
def modify_Polygons(self) -> None:
for x in range(10):
# time.sleep(1)
GG.set_dynamic_polygons(x, self)
On the other hand, if you want to make animations (even if you only indicate it in the title of your post) then you must use QVariantAnimation:
#pyqtSlot()
def modify_Polygons(self) -> None:
animation = QVariantAnimation(self)
animation.setStartValue(0)
animation.setEndValue(10)
animation.valueChanged.connect(
lambda value: GG.set_dynamic_polygons(value, self)
)
animation.setDuration(10 * 1000)
animation.start(QAbstractAnimation.DeleteWhenStopped)

Faced with the problem of updating progressbar in Qt in python

Write player, and emerged question, when I launching song, I want to progressbar was updated in the time when goes music, made cycle, threw his in thread, values on renewal are transmitted through signal in qml, but in than the problem, these values are transmitted only then when I click on this button, but raze not in real time.
Main.py
progressMusicSignal = Signal(float, arguments=['progressMusic'])
#Slot('float')
def setValue(self, flagTrue):
global thread, que
if flagTrue == 1:
que = queue.Queue()
thread = Thread(target=lambda ques, arg1: ques.put(progressBarMusic(arg1)), args=(que, flagTrue),
daemon=True)
thread.start()
result = que.get()
self.progressMusicSignal.emit(result)
elif flagTrue == 2:
thread.join()
def playMusic(flagMusic=0):
if flagMusic == 1:
pygame.mixer.music.load(PATHLESS + MUSICFILEWAV)
pygame.mixer.music.play()
if flagMusic == 2:
pygame.mixer.music.pause()
if flagMusic == 3:
pygame.mixer.music.unpause()
def progressBarMusic(flagTrue):
if flagTrue == 1:
while True:
song = pygame.mixer.Sound(PATHLESS + MUSICFILEWAV)
getLengthMusic = pygame.mixer.Sound.get_length(song)
milSec = pygame.mixer.music.get_pos()
operationLength = getLengthMusic // 10
print(operationLength)
sec = milSec // 1000
secRes = milSec // 100
print(secRes)
operationSecPercent = (secRes / operationLength) / 100
print(operationSecPercent)
if sec != getLengthMusic:
return operationSecPercent
Main.qml
RoundButton {
id: plauPauseBtn
x: 370
y: 15
width: 50
height: 50
text: "\u25b7"
enabled: true
opacity: 1.0
font.weight: Font.ExtraBold
font.capitalization: Font.MixedCase
font.strikeout: false
font.underline: false
font.italic: false
display: AbstractButton.TextBesideIcon
font.bold: false
font.pointSize: 14
font.family: "Tahoma"
onClicked: {
plauPauseBtn.opacity = 0.0;
plauPauseBtn.enabled = false;
stopPauseBtn.opacity = 1.0;
stopPauseBtn.enabled = true;
con.playMusicInt(1)
con.setValue(1)
}
}
RoundButton {
id: stopPauseBtn
x: 370
y: 15
width: 50
height: 50
text: "||"
enabled: false
opacity: 0.0
bottomPadding: 13
font.weight: Font.ExtraBold
font.capitalization: Font.MixedCase
font.strikeout: false
font.underline: false
font.italic: false
display: AbstractButton.TextBesideIcon
font.bold: false
font.pointSize: 7
font.family: "Tahoma"
onClicked: {
con.playMusicInt(2)
con.setValue(2)
stopPauseBtn.opacity = 0.0;
stopPauseBtn.enabled = false;
playAgainBtn.opacity = 1.0;
playAgainBtn.enabled = true;
}
}
RoundButton {
id: playAgainBtn
x: 370
y: 15
width: 50
height: 50
text: "\u25b7"
enabled: false
opacity: 0.0
bottomPadding: 13
font.weight: Font.ExtraBold
font.capitalization: Font.MixedCase
font.strikeout: false
font.underline: false
font.italic: false
display: AbstractButton.TextBesideIcon
font.bold: false
font.pointSize: 14
font.family: "Tahoma"
onClicked: {
con.playMusicInt(3)
con.setValue(1)
playAgainBtn.opacity = 0.0;
playAgainBtn.enabled = false;
stopPauseBtn.opacity = 1.0;
stopPauseBtn.enabled = true;
}
}
ProgressBar {
id: musicProgressBar
x: 0
y: 0
width: 800
height: 5
indeterminate: false
value: 0.0
}
Connections {
target: con
onProgressMusicSignal: {
musicProgressBar.value = progressMusic
}
}
The code provided by the OP is understandable, so I will avoid analyzing it, so I will propose a solution from scratch.
In this case I have created a wrapper on pygame.mixer.music that exposes the properties of the source, the volume, the current state and has methods exposed through pyqtSlot, that class does not handle the logic of your application but is only a resource .
The logic of your application must be handled in QML regarding the state of the button, and in that case it is not necessary to create several buttons since only one in which you change the text is enough.
Considering the above, the solution is:
main.py
import os
import math
import pygame
from PyQt5 import QtCore, QtGui, QtQml
class PyGameSound(QtCore.QObject):
sourceChanged = QtCore.pyqtSignal()
volumeChanged = QtCore.pyqtSignal()
stateChanged = QtCore.pyqtSignal()
notifyIntervalChanged = QtCore.pyqtSignal()
progressChanged = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(str, arguments=["message"])
class State:
PlayingState, PausedState, StoppedState = range(3)
QtCore.Q_ENUMS(State)
def __init__(self, parent=None):
super().__init__(parent)
self.destroyed.connect(self.on_destroyed)
pygame.mixer.init()
self._source = ""
self._notifyInterval = 1000
self._progress = 0.0
self._volume = 1.0
self._notify_timer = QtCore.QTimer(self, timeout=self.on_notify_callback)
self._state = PyGameSound.State.StoppedState
#QtCore.pyqtProperty(State, notify=stateChanged)
def state(self):
return self._state
def _update_state(self, state):
self._state = state
self.stateChanged.emit()
def on_notify_callback(self):
if self.source:
try:
song = pygame.mixer.Sound(self.source)
total = song.get_length()
pos = pygame.mixer.music.get_pos()
if pos >= 0:
percentage = pos / (total * 1000.0)
if math.isclose(
percentage, 1.0, abs_tol=self.notifyInterval / 1000.0
):
percentage = 1.0
self.progress = percentage
except pygame.error as message:
self.error.emit(str(message))
#QtCore.pyqtProperty(str, notify=sourceChanged)
def source(self):
return self._source
#source.setter
def source(self, source):
try:
pygame.mixer.music.load(source)
except pygame.error as message:
self.error.emit(str(message))
source = ""
if self._source != source:
self._source = source
self.sourceChanged.emit()
#QtCore.pyqtProperty(float, notify=volumeChanged)
def volume(self):
return pygame.mixer.music.get_volume()
#volume.setter
def volume(self, volume):
pygame.mixer.music.set_volume(volume)
self.volumeChanged.emit()
#QtCore.pyqtProperty(int, notify=notifyIntervalChanged)
def notifyInterval(self):
return self._notifyInterval
#notifyInterval.setter
def notifyInterval(self, interval):
if self._notifyInterval != interval:
self._notifyInterval = interval
is_active = self._notify_timer.isActive()
if is_active:
self._notify_timer.stop()
self._notify_timer.setInterval(self._notifyInterval)
if is_active:
self._notify_timer.start()
#QtCore.pyqtProperty(float, notify=progressChanged)
def progress(self):
return self._progress
#progress.setter
def progress(self, progress):
self._progress = progress
self.progressChanged.emit()
#QtCore.pyqtSlot()
def play(self):
try:
pygame.mixer.music.play()
self._notify_timer.start()
except pygame.error as message:
self.error.emit(str(message))
return
self._update_state(PyGameSound.State.PlayingState)
#QtCore.pyqtSlot()
def unpause(self):
pygame.mixer.music.unpause()
self._notify_timer.start()
self._update_state(PyGameSound.State.PlayingState)
#QtCore.pyqtSlot()
def pause(self):
pygame.mixer.music.pause()
self._notify_timer.stop()
self._update_state(PyGameSound.State.PausedState)
#QtCore.pyqtSlot()
def stop(self):
pygame.mixer.music.stop()
self._notify_timer.stop()
self._update_state(PyGameSound.State.StoppedState)
def on_destroyed(self):
pygame.mixer.quit()
if __name__ == "__main__":
import sys
current_dir = os.path.dirname(os.path.realpath(__file__))
QtQml.qmlRegisterType(PyGameSound, "PyGame", 1, 0, "PyGameSound")
app = QtGui.QGuiApplication(sys.argv)
engine = QtQml.QQmlApplicationEngine()
filename = os.path.join(current_dir, "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.13
import QtQuick.Controls 2.5
import PyGame 1.0
ApplicationWindow{
visible: true
width: 640
height: 480
PyGameSound{
id: sound
notifyInterval: 10
source: "/path/of/music.wav"
volume: 1.0
onError: console.log(message)
}
RoundButton {
id: play_pause_button
x: 370
y: 15
width: 50
height: 50
text: "\u25b7"
display: AbstractButton.TextBesideIcon
font {
weight: Font.ExtraBold
capitalization: Font.MixedCase
strikeout: false
pointSize: 14
family: "Tahoma"
bold: false
underline: false
italic: false
}
onClicked: {
if(sound.state == PyGameSound.StoppedState){
sound.play()
play_pause_button.text = "||"
}
else if(sound.state == PyGameSound.PlayingState){
sound.pause()
play_pause_button.text = "\u25b7"
}
else if(sound.state == PyGameSound.PausedState){
sound.unpause()
play_pause_button.text = "||"
}
}
}
ProgressBar {
id: musicProgressBar
width: parent.width
height: 5
indeterminate: false
value: sound.progress
}
}
Although the simplest solution is to use the Audio module:
from PyQt5 import QtCore, QtGui, QtQml
if __name__ == "__main__":
import os
import sys
current_dir = os.path.dirname(os.path.realpath(__file__))
app = QtGui.QGuiApplication(sys.argv)
engine = QtQml.QQmlApplicationEngine()
filename = os.path.join(current_dir, "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.13
import QtQuick.Controls 2.5
import QtMultimedia 5.13
ApplicationWindow{
visible: true
width: 640
height: 480
Audio{
id: sound
notifyInterval: 10
source: "/path/of/music.wav"
}
RoundButton {
id: play_pause_button
x: 370
y: 15
width: 50
height: 50
text: "\u25b7"
display: AbstractButton.TextBesideIcon
font {
weight: Font.ExtraBold
capitalization: Font.MixedCase
strikeout: false
pointSize: 14
family: "Tahoma"
bold: false
underline: false
italic: false
}
onClicked: {
if(sound.playbackState == Audio.StoppedState){
sound.play()
play_pause_button.text = "||"
}
else if(sound.playbackState == Audio.PlayingState){
sound.pause()
play_pause_button.text = "\u25b7"
}
else if(sound.playbackState == Audio.PausedState){
sound.play()
play_pause_button.text = "||"
}
}
}
ProgressBar {
id: musicProgressBar
width: parent.width
height: 5
indeterminate: false
value: sound.position/sound.duration
}
}
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
import time
class Example(QWidget):
def __init__(self):
super().__init__()
# calling initUI method
self.initUI()
# method for creating widgets
def initUI(self):
# creating progress bar
self.pbar = QProgressBar(self)
# setting its geometry
self.pbar.setGeometry(30, 40, 200, 25)
# creating push button
self.btn = QPushButton('Start', self)
# changing its position
self.btn.move(40, 80)
# adding action to push button
self.btn.clicked.connect(self.doAction)
# setting window geometry
self.setGeometry(300, 300, 280, 170)
# setting window action
self.setWindowTitle("Python")
# showing all the widgets
self.show()
# when button is pressed this method is being called
def doAction(self):
# setting for loop to set value of progress bar
for i in range(101):
# slowing down the loop
time.sleep(0.05)
# setting value to progress bar
self.pbar.setValue(i)
# main method
if __name__ == '__main__':
# create pyqt5 app
App = QApplication(sys.argv)
# create the instance of our Window
window = Example()
# start the app
sys.exit(App.exec())
I have not found a code where the "amount" of progress would be known and could be filled in as a proportion of the total, but it is also possible at the end of each part to simply +1 to the total amount of progress

How to add headers to qml TableView from TableModel

I need to add a header to a TableView that uses a custom model defined in Python.
I've tried overriding the headerData function in QAbstractTableModel with my custom headers. I'm following the same series of steps described in this C++ example of the same type of implementation: Header to a TableView
Unfortunately, the headers still don't show up at the top of the table. The table does however contain the data from overriding the data function in QAbstractTableModel.
Python:
class RouteTableModel(QAbstractTableModel):
def __init__(self, parent=None, *args):
super().__init__()
self._datatable = None
self._header = {
0 : 'X',
1 : 'Y',
2 : 'Z'
}
def data(self, index, role=Value):
i = index.row()
j = index.column()
if role == self.Value:
return '{0}'.format(self._datatable[i][j]['value'])
elif role == self.Selected:
return self._datatable[i][j]['selected']
else:
return None
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return self._header[section]
else:
return None
else:
return None
# create the application instance
app = QApplication(sys.argv)
# create a QML engine
engine = QQmlApplicationEngine()
# instantiate the TableModel class
xyztablemodel = RouteTableModel()
engine.rootContext().setContextProperty('XYZTableModel', xyztablemodel)
# load main QML file and start app engine
engine.load('view.qml')
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
QML:
import QtQuick 2.12
import QtQuick.Controls 2.12
GridLayout {
id: gridfpc2
flow: GridLayout.TopToBottom
rows: 4
columns: 2
rowSpacing: 20
columnSpacing: 35
TableView {
id: xyztable
Layout.rowSpan: 4
// Layout.alignment: Qt.AlignCenter
Layout.fillHeight: true
model: XYZTableModel
width: 350
delegate: CustomComp.XYZTableDelegate {
implicitWidth: parent.width / 3
implicitHeight: 20
}
}
}
No error messages occur in the Python or qml code. I'd expect the header to populate above the columns in the TableView, but they do not show up.
In the example you indicate in your question, it is from a QTableView that is very different from the TableView offered by QML. In your case you are using the TableView of QtQuick. This TableView has no headers so you must implement it, in my example I will use Repeaters. On the other hand headerData is not accessible from QML, so I will implement the Q_INVOKABLE of C++ using #Slot(), passing as an argument the result of the type of variable that returns the function:
main.py
from PySide2 import QtCore, QtGui, QtQml
class RouteTableModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None):
super().__init__(parent)
self._header = {0: "X", 1: "Y", 2: "Z"}
def columnCount(self, parent=QtCore.QModelIndex()):
return 3
def rowCount(self, parent=QtCore.QModelIndex()):
return 100
def data(self, index, role=QtCore.Qt.DisplayRole):
i = index.row()
j = index.column()
if role == QtCore.Qt.DisplayRole:
return "{}-{}".format(i, j)
#QtCore.Slot(int, QtCore.Qt.Orientation, result="QVariant")
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return self._header[section]
else:
return str(section)
if __name__ == "__main__":
import os
import sys
app = QtGui.QGuiApplication(sys.argv)
engine = QtQml.QQmlApplicationEngine()
xyztablemodel = RouteTableModel()
engine.rootContext().setContextProperty("XYZTableModel", xyztablemodel)
current_dir = os.path.dirname(os.path.realpath(__file__))
filename = os.path.join(current_dir, "view.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
view.qml
import QtQuick 2.12
import QtQuick.Controls 2.4
import QtQuick.Window 2.11
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
color: '#222222'
TableView {
id: tableView
columnWidthProvider: function (column) { return 100; }
rowHeightProvider: function (column) { return 60; }
anchors.fill: parent
leftMargin: rowsHeader.implicitWidth
topMargin: columnsHeader.implicitHeight
model: XYZTableModel
width: 350
delegate: Rectangle {
implicitWidth: 100
implicitHeight: 50
Text {
text: display
}
}
Rectangle { // mask the headers
z: 3
color: "#222222"
y: tableView.contentY
x: tableView.contentX
width: tableView.leftMargin
height: tableView.topMargin
}
Row {
id: columnsHeader
y: tableView.contentY
z: 2
Repeater {
model: tableView.columns > 0 ? tableView.columns : 1
Label {
width: tableView.columnWidthProvider(modelData)
height: 35
text: XYZTableModel.headerData(modelData, Qt.Horizontal)
color: '#aaaaaa'
font.pixelSize: 15
padding: 10
verticalAlignment: Text.AlignVCenter
background: Rectangle { color: "#333333" }
}
}
}
Column {
id: rowsHeader
x: tableView.contentX
z: 2
Repeater {
model: tableView.rows > 0 ? tableView.rows : 1
Label {
width: 40
height: tableView.rowHeightProvider(modelData)
text: XYZTableModel.headerData(modelData, Qt.Vertical)
color: '#aaaaaa'
font.pixelSize: 15
padding: 10
verticalAlignment: Text.AlignVCenter
background: Rectangle { color: "#333333" }
}
}
}
ScrollIndicator.horizontal: ScrollIndicator { }
ScrollIndicator.vertical: ScrollIndicator { }
}
}

How do I bind a dynamically created qmlcomponent object to the property of another dynamic created qmlcomponent object?

I have two qml components sitting in one module.
components
|- Edge.qml
|- Class.qml
|- qmdir
main
|- main.qml
main.py
with main.qml
import urmelgraph.components 1.0
import CustomGeometry 1.0
ApplicationWindow {
visible: true
width: 640
height: 240
title: qsTr("Test")
color: "#2C3E50"
}
and qmdir
module urmelgraph.components
Class 1.0 Class.qml
Edge 1.0 Edge.qml
I am loading both inside my python main.py file.
from PyQt5.QtGui import QGuiApplication, QColor, QSurfaceFormat
from PyQt5.QtQml import QQmlApplicationEngine, QQmlComponent, QQmlContext, qmlRegisterType, QQmlProperty
from PyQt5.QtQuick import QQuickItem, QQuickView, QSGGeometryNode, QSGGeometry, QSGNode, QSGFlatColorMaterial
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QUrl, QPointF, QSizeF
from pathlib import Path
class StraightLine(QQuickItem):
p1_Changed = pyqtSignal()
p2_Changed = pyqtSignal()
segment_count_Changed = pyqtSignal()
def __init__(self, parent: QQuickItem, p1: QPointF = QPointF(0,0), p2: QPointF = QPointF(1,1), segment_count: int = 2):
super().__init__(parent)
self._p1 = p1
self._p2 = p2
self._segment_count = segment_count
self.setFlag(QQuickItem.ItemHasContents, True)
#pyqtProperty("QPointF", notify = p1_Changed)
def p1(self):
return self._p1
#p1.setter
def p1(self, p1: QPointF):
if p1 == self._p1:
return
self._p1 = p1
self.p1_Changed.emit()
self.update()
#pyqtProperty("QPointF", notify = p2_Changed)
def p2(self):
return self._p2
#p2.setter
def p2(self, p2: QPointF):
if p2 == self._p2:
return
self._p2 = p2
self.p2_Changed.emit()
self.update()
#pyqtProperty(int, notify = segment_count_Changed)
def segment_count(self):
return self._segment_count
#segment_count.setter
def segment_count(self, count: int):
if count == self._segment_count:
return
self._segment_count = count
self.segment_count_Changed.emit()
self.update()
def updatePaintNode(self, oldNode: QSGGeometryNode, _):
if oldNode == None:
node = QSGGeometryNode()
geometry = QSGGeometry(QSGGeometry.defaultAttributes_Point2D(), self._segment_count)
geometry.setLineWidth(3)
geometry.setDrawingMode(QSGGeometry.DrawLineStrip)
node.setGeometry(geometry)
node.setFlag(QSGNode.OwnsGeometry)
material = QSGFlatColorMaterial()
material.setColor(QColor(45, 100, 120))
node.setMaterial(material)
node.setFlag(QSGNode.OwnsMaterial)
else:
node = oldNode
geometry = node.geometry()
geometry.allocate(self._segment_count)
itemSize = self.size()
vertices = geometry.vertexDataAsPoint2D()
x1 = self._p1.x()
y1 = self._p1.y()
vertices[0].set(x1, y1)
x2 = self._p2.x()
y2 = self._p2.y()
vertices[1].set(x2, y2)
print(vertices[1].x)
node.markDirty(QSGNode.DirtyGeometry)
return node
if __name__ == "__main__":
import sys
path = Path("..")
resolved_path = path.resolve()
# Create an instance of the application
app = QGuiApplication(sys.argv)
# Set antialising 4 samples
format = QSurfaceFormat()
format.setSamples(4)
QSurfaceFormat.setDefaultFormat(format)
# register custom types
qmlRegisterType(StraightLine, "CustomGeometry", 1, 0, "StraightLine")
# Create QML engine
engine = QQmlApplicationEngine()
# Load the qml file into the engine
engine.addImportPath(str(resolved_path))
engine.load("main/main.qml")
# load the components
component = QQmlComponent(engine)
component.loadUrl(QUrl("components/Class.qml"))
line_component = QQmlComponent(engine)
line_component.loadUrl(QUrl("components/Edge.qml"))
# check for component creation errors
for error in component.errors():
print(error.toString())
# check for component creation errors
for error in line_component.errors():
print(error.toString())
classes = []
for index, class_name in enumerate(["Person", "Home"]):
# create a new instance of the component
cclass = component.create()
# set the class name property of the component
cclass.setProperty("className", class_name)
cclass.setX((cclass.width() + 50) * index)
cclass.setParentItem(engine.rootObjects()[0].findChild(QQuickItem))
classes.append(cclass)
line = line_component.beginCreate(engine.rootContext())
line.setProperty("anchor1", classes[0])
line.setProperty("anchor2", classes[1])
line_component.completeCreate()
# check for object creation errors
for error in line_component.errors():
print(error.toString())
for error in component.errors():
print(error.toString())
engine.quit.connect(app.quit)
sys.exit(app.exec_())
But now I want to connect the first point of the Edge E to a Class component A and the second point of the Edge E to a Class component B.
For that I have created properties in the Edge.qml.
import QtQuick 2.11
import CustomGeometry 1.0
import urmelgraph.components 1.0
StraightLine {
property Class anchor1
property Class anchor2
anchors.fill: parent
Component.onCompleted: {
console.log(anchor1)
console.log(anchor2)
}
p2: Qt.point(anchor2.x + (anchor2.width/2), anchor2.y + (anchor2.height/2))
p1: Qt.point(anchor1.x + (anchor1.width/2), anchor1.y + (anchor1.height/2))
}
This is my Class.qml
import QtQuick 2.11
import QtQuick.Layouts 1.11
Rectangle {
width: 50
height: 20
color: "#2980B9"
border.color: "#ECF0F1"
property string className
Drag.active: dragArea.drag.active
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
// disable delay when moved
drag.threshold: 0
}
Text {
text: className
}
}
In my main.py I have a classes list with all generated Class components but trying to connect the first class with the second class via Edge (line), for example, doesn't work:
line = line_component.beginCreate(engine.rootContext())
line.setProperty("anchor1", classes[0])
line.setProperty("anchor2", classes[1])
line_component.completeCreate()
However, if I create two Rectangles with id rect1 and rect2 within the main.qml file. using the QQuickItem StraightLine this code is working:
StraightLine {
anchors.fill: parent
p1: Qt.point(rect2.x + (rect2.width/2), rect2.y + (rect2.height/2))
p2: Qt.point(rect1.x + (rect1.width/2), rect1.y + (rect1.height/2))
}
Rectangle {
id: rect1
width: 10
height: 10
color: "red"
radius: width*0.5
Drag.active: dragArea.drag.active
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
// disable delay when moved
drag.threshold: 0
}
}
Rectangle {
id: rect2
width: 10
height: 10
color: "blue"
radius: width*0.5
Drag.active: dragArea2.drag.active
MouseArea {
id: dragArea2
anchors.fill: parent
drag.target: parent
// disable delay when moved
drag.threshold: 0
}
}
How can I pass the reference from those class components to my edge components to properly setup a binding for x,y,width,height?
The solution was to establish the data type of the property anchor1 and anchor2 to var.
Edge.qml
import QtQuick 2.11
import CustomGeometry 1.0
StraightLine {
property var anchor1
property var anchor2
anchors.fill: parent
Component.onCompleted: {
console.log(anchor1)
console.log(anchor2)
}
p2: Qt.point(anchor2.x + (anchor2.width/2), anchor2.y + (anchor2.height/2))
p1: Qt.point(anchor1.x + (anchor1.width/2), anchor1.y + (anchor1.height/2))
}
On the other hand, I have not included the QtQuick.Controls 1.4 import to recognize the ApplicationWindow in the main.qml:
import QtQuick.Controls 1.4
import urmelgraph.components 1.0
import CustomGeometry 1.0
ApplicationWindow {
visible: true
width: 640
height: 240
title: qsTr("Test")
color: "#2C3E50"
}
In the following link you will find the complete code

PyQt load function on startup (qml loader)

Hi i have the following problem :
this is my working code
import sys
from PyQt5.QtCore import QObject, QUrl, Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
import os
import vlc
from time import sleep
# define VLC instance
instance = vlc.Instance()
# Define VLC player
instance = vlc.Instance('--input-repeat=-1', '--fullscreen')
player = instance.media_player_new()
list_test = []
list_name = []
def prepare_url(url):
media = instance.media_new(url)
player.set_media(media)
if __name__ == "__main__":
os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
ctx = engine.rootContext()
ctx.setContextProperty("main", engine)
engine.load('SimpleQML.qml')
win = engine.rootObjects()[0]
win.show()
button = win.findChild(QObject, "playBtn")
def myFunction():
print("A fine piece of text")
button.clicked.connect(myFunction) # works on click
myFunction() #works with out clicking
sys.exit(app.exec_())
Now i would like to expand on that by doing the following code :
import sys
from PyQt5.QtCore import QObject, QUrl, Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
import os
import vlc
from time import sleep
# define VLC instance
instance = vlc.Instance()
# Define VLC player
instance = vlc.Instance('--input-repeat=-1', '--fullscreen')
player = instance.media_player_new()
list_test = []
list_name = []
def prepare_url(url):
media = instance.media_new(url)
player.set_media(media)
if __name__ == "__main__":
os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
ctx = engine.rootContext()
ctx.setContextProperty("main", engine)
engine.load('SimpleQML.qml')
win = engine.rootObjects()[0]
win.show()
button = win.findChild(QObject, "playBtn")
comboBox = win.findChild(QObject, "comboBox")
def myFunction():
print("das")
def list_fill():
with open("config/stations.txt") as f:
content = f.readlines()
content = [x.strip() for x in content]
list_t = [item.split("|")[0] for item in content if item]
list_n = [item.split("|")[1] for item in content if item]
del list_test[:]
del list_name[:]
comboBox.clear()
for x in list_t:
list_test.append(x)
for x in list_n:
list_name.append(x)
addItems(list_name)
button.clicked.connect(myFunction) # works too
myFunction()
list_fill() #calling this crashes program
sys.exit(app.exec_())
and at the very end this is the error
das
Traceback (most recent call last):
File "/home/flea/Desktop/quick qt/main.py", line 65, in <module>
list_fill()
File "/home/flea/Desktop/quick qt/main.py", line 55, in list_fill
comboBox.clear()
AttributeError: 'QObject' object has no attribute 'clear'
i tried to do ti with hardcoded list, but list is not the problem, for some reason my combo box is not recognized by python.I am not sure what is the problem here.
I can load my button and add a click event to it, (which works), but i cant add list to my comboBox.
here is my Qml
import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
import QtQuick.Controls.Material 2.3
ApplicationWindow {
id: applicationWindow
Material.theme: Material.Light
title: qsTr("Test Invoke")
width: 600
height: 500
Row {
id: row
width: 200
height: 400
anchors.left: parent.left
anchors.leftMargin: 5
anchors.top: parent.top
anchors.topMargin: 5
Button{
id: playBtn
objectName: "playBtn"
text : "Play"
checked: false
padding: 6
rightPadding: 8
font.wordSpacing: 0
font.pointSize: 10
font.family: "Times New Roman"
topPadding: 4
highlighted: true
Material.accent: Material.Red
}
Button {
id: stopBtn
objectName: "stopBtn"
text: qsTr("Stop")
anchors.left: playBtn.right
anchors.leftMargin: 5
}
Button {
id: stfBtn
text: qsTr("Save")
objectName: "stfBtn"
anchors.left: stopBtn.right
anchors.leftMargin: 5
}
Button {
id: minimize
objectName: "minBtn"
text: qsTr("Min")
anchors.left: stfBtn.right
anchors.leftMargin: 5
}
}
Column {
id: column
x: 135
y: 100
width: 200
height: 400
TextField {
objectName: "nameText"
id: nameText
width: 300
text: qsTr("")
}
TextField {
objectName: "urlText"
id: urlText
width: 300
text: qsTr("")
}
ComboBox {
objectName: "comboBox"
id: comboBox
width: 200
}
}
Slider {
id: slide
objectName: "slider"
x: 160
y: 311
value: 0.5
}
}
It is not a good nor maintainable over time to instantiate an object created in QML from Python or C++.
The appropriate thing is to create an object in Python or C++ and send it to QML, and then create qproperties and slots that allow interacting with the QML.
In your case, I guess that list_fill tries to add data to the ComboBox, but the ComboBox does not have a clear method, so if you want to clean it, just pass it an empty list, or in your case pass it the new list.
On the other hand it is not elegant to call show(), it is best to set the visible property of ApplicationWindow to true.
main.py
import sys
import os
from PyQt5.QtCore import QObject, QUrl, Qt, pyqtSlot, pyqtSignal, pyqtProperty
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
import vlc
# define VLC instance
instance = vlc.Instance()
# Define VLC player
instance = vlc.Instance('--input-repeat=-1', '--fullscreen')
player = instance.media_player_new()
list_test = []
list_name = []
def prepare_url(url):
media = instance.media_new(url)
player.set_media(media)
class Manager(QObject):
stationsChanged = pyqtSignal()
currentStationChanged = pyqtSignal()
def __init__(self):
QObject.__init__(self)
self.m_stations = []
self.m_currentStation = ""
self.currentStationChanged.connect(self.on_currentStationChanged)
#pyqtProperty(str, notify=currentStationChanged)
def currentStation(self):
return self.m_currentStation
#currentStation.setter
def currentStation(self, val):
if self.m_currentStation == val:
return
self.m_currentStation = val
self.currentStationChanged.emit()
#pyqtProperty(list, notify=stationsChanged)
def stations(self):
return self.m_stations
#stations.setter
def stations(self, val):
if self.m_stations == val:
return
self.m_stations = val[:]
self.stationsChanged.emit()
#pyqtSlot()
def play(self):
print("play", self.currentStation)
#pyqtSlot()
def stop(self):
print("stop")
#pyqtSlot()
def on_currentStationChanged(self):
print(self.currentStation)
def list_fill(self):
l = []
with open("config/stations.txt") as f:
content = f.readlines()
content = [x.strip() for x in content]
list_t = [item.split("|")[0] for item in content if item]
list_n = [item.split("|")[1] for item in content if item]
l += list_t + list_n
self.stations = l
if __name__ == "__main__":
os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
manager = Manager()
ctx = engine.rootContext()
ctx.setContextProperty("Manager", manager)
engine.load('main.qml')
if not engine.rootObjects():
sys.exit(-1)
manager.list_fill()
sys.exit(app.exec_())
main.qml
import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
import QtQuick.Controls.Material 2.3
ApplicationWindow {
id: applicationWindow
Material.theme: Material.Light
title: qsTr("Test Invoke")
visible: true
width: 600
height: 500
Row {
id: row
width: 200
height: 400
anchors.left: parent.left
anchors.leftMargin: 5
anchors.top: parent.top
anchors.topMargin: 5
Button{
id: playBtn
text : "Play"
checked: false
padding: 6
rightPadding: 8
font.wordSpacing: 0
font.pointSize: 10
font.family: "Times New Roman"
topPadding: 4
highlighted: true
Material.accent: Material.Red
onClicked: Manager.play()
}
Button {
id: stopBtn
text: qsTr("Stop")
anchors.left: playBtn.right
anchors.leftMargin: 5
onClicked: Manager.stop()
}
Button {
id: stfBtn
text: qsTr("Save")
objectName: "stfBtn"
anchors.left: stopBtn.right
anchors.leftMargin: 5
}
Button {
id: minimize
objectName: "minBtn"
text: qsTr("Min")
anchors.left: stfBtn.right
anchors.leftMargin: 5
}
}
Column {
id: column
x: 135
y: 100
width: 200
height: 400
TextField {
id: nameText
width: 300
text: qsTr("")
}
TextField {
id: urlText
width: 300
text: qsTr("")
}
ComboBox {
id: comboBox
width: 200
model: Manager.stations
onCurrentTextChanged: Manager.currentStation = currentText
}
}
Slider {
id: slider
x: 160
y: 311
value: 0.5
}
}
The advantage of this implementation is that you can modify the design and logic part independently. If you pass an object through setContextProperty it will be visible in all .qml files. With your previous approach you were going to have problems if you were going to have many .qml

Categories