How to add headers to qml TableView from TableModel - python

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 { }
}
}

Related

PySide Qt dataChanged.emit() not working but LayoutChanged.emit works

from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType
from PySide6.QtCore import QObject, Signal, Slot, Property, Qt,QUrl, QTimer, QAbstractListModel
class CpuLoadModel(QAbstractListModel):
def __init__(self):
QAbstractListModel.__init__(self)
self.__update_timer = QTimer(self)
self.__update_timer.setInterval(1000)
self.__update_timer.timeout.connect(self.__update)
self.__update_timer.start()
self.todos = []
def __update(self):
self.todos.append(random.randint(0, 99))
self.layoutChanged.emit()
print(self.todos)
self.dataChanged.emit(self.index,self.index,[])
def data(self, index, role):
if role == Qt.DisplayRole:
text = self.todos[index.row()]
print(f"Text: {index.row()} role : {role}")
print("--------")
return text
def rowCount(self, index):
return len(self.todos)
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
qmlRegisterType(CpuLoadModel, 'PsUtils', 1, 0, 'CpuLoadModel')
engine.load(QUrl("main.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec())
self.dataChange.emit method is not working at all, however self.LayoutChange.emit works but with those mistakes from qml
TypeError: Cannot read property 'width' of null
The layoutChanged signal must be emitted when something in the model has changed (for example it has been reordered), and dataChanged when any of the items change data but none of them is used to indicate that a row was added, in that case it must use the beginInsertRows and endInsertRows methods.
def __update(self):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.todos.append(random.randint(0, 99))
self.endInsertRows()
Also change to:
def rowCount(self, index=QModelIndex()):
if index.isValid():
return 0
return len(self.todos)
import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Controls 2.0
import PsUtils 1.0
Window {
id: root
width: 640
height: 480
visible: true
title: "CPU Load"
ListView {
id:listviewId
anchors.fill: parent
model: CpuLoadModel { }
delegate: Rectangle {
width: listviewId.width
height: 30;
color: "white"
Rectangle {
id: bar
width: listviewId.width * display / 100.0
height: 30
color: "green"
}
Text {
anchors.verticalCenter: parent.verticalCenter
x: Math.min(bar.x + bar.width + 5, parent.width-width)
text: display + "%"
}
}
}
}
Basically the mistake was the parent.width in QML we better off to use id to refer to element instead of using parent.width ( use elementId.width)

PySide QML Charts update Series with real-time data

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()
}
}

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

Displaying pandas dataframe in QML

I have 2 pandas dataframes, one with 6 rows and 7 columns and another one with 21 rows and 12 columns, i would like to display them using QML, while googling i got to know about TableView TableView QML Type. In that example, the data has been entered already in ListElement and it is being shown. But in my case the data is from python pandas dataframe, how can i fetch the columns and rows from pandas straight into QML, should i create those columns well before under listelement? Any help would be appreciated
Note: I have used QtWidgets to display dataframe, it was just a drag and drop tableview and use the model created and set them using tableView.setModel(model). But i am kind of clueless here in QML
You can create a class that inherits from QAbstractTableModel as implemented in this old answer and use the QTableView of QtQuick 2.12 that was released in Qt 5.12 since it supports the QAbstractTableModel type model, so you will have to use the latest versions of PyQt5.
main.py
from PyQt5 import QtCore, QtGui, QtQml
import numpy as np
import pandas as pd
class DataFrameModel(QtCore.QAbstractTableModel):
DtypeRole = QtCore.Qt.UserRole + 1000
ValueRole = QtCore.Qt.UserRole + 1001
def __init__(self, df=pd.DataFrame(), parent=None):
super(DataFrameModel, self).__init__(parent)
self._dataframe = df
def setDataFrame(self, dataframe):
self.beginResetModel()
self._dataframe = dataframe.copy()
self.endResetModel()
def dataFrame(self):
return self._dataframe
dataFrame = QtCore.pyqtProperty(pd.DataFrame, fget=dataFrame, fset=setDataFrame)
#QtCore.pyqtSlot(int, QtCore.Qt.Orientation, result=str)
def headerData(self, section: int, orientation: QtCore.Qt.Orientation, role: int = QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return self._dataframe.columns[section]
else:
return str(self._dataframe.index[section])
return QtCore.QVariant()
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self._dataframe.index)
def columnCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return self._dataframe.columns.size
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid() or not (0 <= index.row() < self.rowCount() \
and 0 <= index.column() < self.columnCount()):
return QtCore.QVariant()
row = self._dataframe.index[index.row()]
col = self._dataframe.columns[index.column()]
dt = self._dataframe[col].dtype
val = self._dataframe.iloc[row][col]
if role == QtCore.Qt.DisplayRole:
return str(val)
elif role == DataFrameModel.ValueRole:
return val
if role == DataFrameModel.DtypeRole:
return dt
return QtCore.QVariant()
def roleNames(self):
roles = {
QtCore.Qt.DisplayRole: b'display',
DataFrameModel.DtypeRole: b'dtype',
DataFrameModel.ValueRole: b'value'
}
return roles
if __name__ == "__main__":
import os
import sys
app = QtGui.QGuiApplication(sys.argv)
df = pd.DataFrame(np.random.randint(0, 100, size=(6, 7)), columns=list('ABCDEFG'))
print(df)
model = DataFrameModel(df)
engine = QtQml.QQmlApplicationEngine()
engine.rootContext().setContextProperty("table_model", model)
qml_path = os.path.join(os.path.dirname(__file__), "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(qml_path))
if not engine.rootObjects():
sys.exit(-1)
engine.quit.connect(app.quit)
sys.exit(app.exec_())
main.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: table_model
delegate: Rectangle {
color: parseFloat(display) > 50 ? 'green' : 'red'
Text {
text: display
anchors.fill: parent
anchors.margins: 10
color: 'white'
font.pixelSize: 15
verticalAlignment: Text.AlignVCenter
}
}
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: table_model.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: table_model.headerData(modelData, Qt.Vertical)
color: '#aaaaaa'
font.pixelSize: 15
padding: 10
verticalAlignment: Text.AlignVCenter
background: Rectangle { color: "#333333" }
}
}
}
ScrollIndicator.horizontal: ScrollIndicator { }
ScrollIndicator.vertical: ScrollIndicator { }
}
}
I have used:
Python 3.7.2
PyQt5 5.12.1
Qt 5.12.2
pandas 0.24.1
Note: If you want to use the example with PySide2 you only have to change pyqtProperty with Property, pyqtSlot with Slot and PyQt5 to PySide2.

PyQt5 Dynamically add rectangles to QML grid

I have built a grid of rectangles in QML which is run from Python. engine.load('main.qml')
Window {
id: channels
Grid {
columns: 2
spacing: 9
Rectangle {
color: "#333"
width: 75
height: 75
}
Rectangle {
color: "#333"
width: 75
height: 75
}
}
}
However, I would like to have over fifty rectangles, so I need to be able to dynamically create and update them from python. How can I do that?
To provide information from python (or C++) to qml we can read this link. In it recommends to use QAbstractListModel since this notifies the changes to the qml, in addition to be added dynamically we will use Repeater.
main.qml:
import QtQuick.Window 2.2
import QtQuick 2.0
import QtQuick.Controls 1.4
Window {
visible: true
id: channels
Grid {
columns: 3
spacing: 9
Repeater{
model: myModel
delegate: Rectangle{
height: model.height
width: model.height
color: model.color
}
}
}
}
.py:
class Data(object):
def __init__(self, width=35, height=35, color=QColor("red")):
self._width = width
self._height = height
self._color = color
def width(self):
return self._width
def height(self):
return self._height
def color(self):
return self._color
class Model(QAbstractListModel):
WidthRole = Qt.UserRole + 1
HeightRole = Qt.UserRole + 2
ColorRole = Qt.UserRole + 3
_roles = {WidthRole: b"width", HeightRole: b"height", ColorRole: b"color"}
def __init__(self, parent=None):
QAbstractListModel.__init__(self, parent)
self._datas = []
def addData(self, data):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self._datas.append(data)
self.endInsertRows()
def rowCount(self, parent=QModelIndex()):
return len(self._datas)
def data(self, index, role=Qt.DisplayRole):
try:
data = self._datas[index.row()]
except IndexError:
return QVariant()
if role == self.WidthRole:
return data.width()
if role == self.HeightRole:
return data.height()
if role == self.ColorRole:
return data.color()
return QVariant()
def roleNames(self):
return self._roles
To make a test we use the following code:
main.py
if __name__ == "__main__":
import sys
QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
model = Model()
model.addData(Data(44, 33, QColor("red")))
model.addData(Data(23, 53, QColor("#333")))
context = engine.rootContext()
context.setContextProperty('myModel', model)
engine.load(QUrl.fromLocalFile("main.qml"))
if len(engine.rootObjects()) == 0:
sys.exit(-1)
qsrand(QTime.currentTime().msec())
timer = QTimer(engine)
timer.timeout.connect(lambda: model.addData(Data(20 + qrand() % 40,
20 + qrand() % 40,
QColor(qrand() % 255, qrand() % 255, qrand() % 255))))
timer.start(1000)
engine.quit.connect(app.quit)
sys.exit(app.exec_())
The complete example you find here.

Categories