1
votes

I couldn't get through some problem for 2 days in my app, so I post here. I have UI written in pyqt5 (Qt designer), and part of main window consists QML objects (inside QQuickWidget).

My problem is refreshing gauge object inside QQuickWidget.

For example, if I run application with QML file as ApplicationWindow, everything is ok and I am able to manipulate data:

Gauge is refreshing

But when I place this object (and change object in QML to Rectangle) into the QQuickWidget, I am not able to update the state of this object.

gauge inside python UI application - not refreshing

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = GUI_MainWindow()    #Main window written in pyqt5
    qmlRegisterType(RadialBar, "SDK", 1,0, "RadialBar")

    # Setting source for QML Widget
    # batteryCWidget is the QQUickWidget object (4 of them are on main window)
    window.batteryCWidget.setSource(QUrl('qml_widget.qml'))

    batteryWidget = MyClass() # Class with function to update data in QML
    engine = QQmlApplicationEngine()
    context = engine.rootContext()
    context.setContextProperty("batteryWidget", batteryWidget)
    engine.load('qml_widget.qml')
    root = engine.rootObjects()[0]

    timer = QTimer()
    timer.start(200)

    #Every 200ms I generate new number in function random_value 
    timer.timeout.connect(batteryWidget.random_value)
    #and then update value in QML
    batteryWidget.randomValue.connect(root.setValue)

Is it possible to update/repaint/refresh state of object inside QQuickWidget?

This is qml_widget.qml:

import QtQuick 2.4
import SDK 1.0
import QtQuick.Layouts 1.1


Rectangle {
    id: root
    Layout.alignment: Layout.Center
    width: 160
    height: 145
    color: "#181818"
    property var suffix: "A"
    property int minVal: 0
    property int maxVal: 100
    property var actVal: 0

    function setValue(v) {
        actVal = v
    }
Rectangle {
        Layout.alignment: Layout.Center
        width: 160
        height: 145
        color: "#1d1d35"
        border.color: "#000000"
        border.width: 3
Text {
    id: name
    text: "Battery Current"
    anchors.horizontalCenter: parent.horizontalCenter
    anchors.top: parent.top
    anchors.topMargin: 5
    font.pointSize: 13
    color: "#6affcd"
}

RadialBar {
    anchors.horizontalCenter: parent.horizontalCenter
    anchors.bottom: parent.bottom
    width: parent.width / 1.4
    height: width - (0.001)*actVal
    penStyle: Qt.RoundCap
    progressColor: "#6affcd"
    foregroundColor: "#191a2f"
    dialWidth: 11
    minValue: minVal
    maxValue: maxVal
    value: actVal
    suffixText: suffix
    textFont {
        family: "Halvetica"
        italic: false
        pointSize: 18
    }
    textColor: "#00ffc1"
}}

MyClass:

class MyClass(QObject):
    randomValue = pyqtSignal(float)
    def __init__(self, parent=None):
        super(MyClass, self).__init__(parent)

    def random_value(self):
        v = float(randrange(1, 100))
        self.randomValue.emit(v)

RadialBar class:

class RadialBar(QQuickPaintedItem):

    class DialType():
        FullDial = 0
        MinToMax = 1
        NoDial = 2

sizeChanged = pyqtSignal()
startAngleChanged = pyqtSignal()
spanAngleChanged = pyqtSignal()
minValueChanged = pyqtSignal()
maxValueChanged = pyqtSignal()
valueChanged = pyqtSignal()
dialWidthChanged = pyqtSignal()
backgroundColorChanged = pyqtSignal()
foregroundColorChanged = pyqtSignal()
progressColorChanged = pyqtSignal()
textColorChanged = pyqtSignal()
suffixTextChanged = pyqtSignal()
showTextChanged = pyqtSignal()
penStyleChanged = pyqtSignal()
dialTypeChanged = pyqtSignal()
textFontChanged = pyqtSignal()

def __init__(self, parent=None):
    super(RadialBar, self).__init__(parent)

    self.setWidth(200)
    self.setHeight(200)
    self.setSmooth(True)
    self.setAntialiasing(True)

    self._Size = 200
    self._StartAngle = 40
    self._SpanAngle = 280
    self._MinValue = 0
    self._MaxValue = 100
    self._Value = 50
    self._DialWidth = 25
    self._BackgroundColor = Qt.transparent
    self._DialColor = QColor(80,80,80)
    self._ProgressColor = QColor(135,26,50)
    self._TextColor = QColor(0, 0, 0)
    self._SuffixText = ""
    self._ShowText = True
    self._PenStyle = Qt.FlatCap
    self._DialType = RadialBar.DialType.MinToMax
    self._TextFont = QFont()

def paint(self, painter):
    painter.save()
    size = min(self.width(), self.height())
    self.setWidth(size)
    self.setHeight(size)
    rect = QRectF(0, 0, self.width(), self.height()) #self.boundingRect()
    painter.setRenderHint(QPainter.Antialiasing)
    pen = painter.pen()
    pen.setCapStyle(self._PenStyle)

    startAngle = -90 - self._StartAngle
    if RadialBar.DialType.FullDial != self._DialType:
        spanAngle = 0 - self._SpanAngle
    else:
        spanAngle = -360

    #Draw outer dial
    painter.save()
    pen.setWidth(self._DialWidth)
    pen.setColor(self._DialColor)
    painter.setPen(pen)
    offset = self._DialWidth / 2
    if self._DialType == RadialBar.DialType.MinToMax:
        painter.drawArc(rect.adjusted(offset, offset, -offset, -offset), startAngle * 16, spanAngle * 16)
    elif self._DialType == RadialBar.DialType.FullDial:
        painter.drawArc(rect.adjusted(offset, offset, -offset, -offset), -90 * 16, -360 * 16)
    else:
        pass
        #do not draw dial

    painter.restore()

    #Draw background
    painter.save()
    painter.setBrush(self._BackgroundColor)
    painter.setPen(self._BackgroundColor)
    inner = offset * 2
    painter.drawEllipse(rect.adjusted(inner, inner, -inner, -inner))
    painter.restore()

    #Draw progress text with suffix
    painter.save()
    painter.setFont(self._TextFont)
    pen.setColor(self._TextColor)
    painter.setPen(pen)
    if self._ShowText:
        painter.drawText(rect.adjusted(offset, offset, -offset, -offset), Qt.AlignCenter,str(self._Value) + self._SuffixText)
    else:
        painter.drawText(rect.adjusted(offset, offset, -offset, -offset), Qt.AlignCenter, self._SuffixText)
    painter.restore()

    #Draw progress bar
    painter.save()
    pen.setWidth(self._DialWidth)
    pen.setColor(self._ProgressColor)
    valueAngle = float(float(self._Value - self._MinValue)/float(self._MaxValue - self._MinValue)) * float(spanAngle)  #Map value to angle range
    painter.setPen(pen)
    painter.drawArc(rect.adjusted(offset, offset, -offset, -offset), startAngle * 16, valueAngle * 16)
    painter.restore()

@QtCore.pyqtProperty(str, notify=sizeChanged)
def size(self):
    return self._Size

@size.setter
def size(self, size):
    if self._Size == size:
        return
    self._Size = size
    self.sizeChanged.emit()

@QtCore.pyqtProperty(int, notify=startAngleChanged)
def startAngle(self):
    return self._StartAngle

@startAngle.setter
def startAngle(self, angle):
    if self._StartAngle == angle:
        return
    self._StartAngle = angle
    self.startAngleChanged.emit()

@QtCore.pyqtProperty(int, notify=spanAngleChanged)
def spanAngle(self):
    return self._SpanAngle

@spanAngle.setter
def spanAngle(self, angle):
    if self._SpanAngle == angle:
        return
    self._SpanAngle = angle
    self.spanAngleChanged.emit()

@QtCore.pyqtProperty(int, notify=minValueChanged)
def minValue(self):
    return self._MinValue

@minValue.setter
def minValue(self, value):
    if self._MinValue == value:
        return
    self._MinValue = value
    self.minValueChanged.emit()

@QtCore.pyqtProperty(int, notify=maxValueChanged)
def maxValue(self):
    return self._MaxValue

@maxValue.setter
def maxValue(self, value):
    if self._MaxValue == value:
        return
    self._MaxValue = value
    self.maxValueChanged.emit()

@QtCore.pyqtProperty(float, notify=valueChanged)
def value(self):
    return self._Value

@value.setter
def value(self, value):
    if self._Value == value:
        return
    self._Value = value
    self.valueChanged.emit()

@QtCore.pyqtProperty(float, notify=dialWidthChanged)
def dialWidth(self):
    return self._DialWidth

@dialWidth.setter
def dialWidth(self, width):
    if self._DialWidth == width:
        return
    self._DialWidth = width
    self.dialWidthChanged.emit()

@QtCore.pyqtProperty(QColor, notify=backgroundColorChanged)
def backgroundColor(self):
    return self._BackgroundColor

@backgroundColor.setter
def backgroundColor(self, color):
    if self._BackgroundColor == color:
        return
    self._BackgroundColor = color
    self.backgroundColorChanged.emit()

@QtCore.pyqtProperty(QColor, notify=foregroundColorChanged)
def foregroundColor(self):
    return self._ForegrounColor

@foregroundColor.setter
def foregroundColor(self, color):
    if self._DialColor == color:
        return
    self._DialColor = color
    self.foregroundColorChanged.emit()

@QtCore.pyqtProperty(QColor, notify=progressColorChanged)
def progressColor(self):
    return self._ProgressColor

@progressColor.setter
def progressColor(self, color):
    if self._ProgressColor == color:
        return
    self._ProgressColor = color
    self.progressColorChanged.emit()

@QtCore.pyqtProperty(QColor, notify=textColorChanged)
def textColor(self):
    return self._TextColor

@textColor.setter
def textColor(self, color):
    if self._TextColor == color:
        return
    self._TextColor = color
    self.textColorChanged.emit()

@QtCore.pyqtProperty(str, notify=suffixTextChanged)
def suffixText(self):
    return self._SuffixText

@suffixText.setter
def suffixText(self, text):
    if self._SuffixText == text:
        return
    self._SuffixText = text
    self.suffixTextChanged.emit()

@QtCore.pyqtProperty(str, notify=showTextChanged)
def showText(self):
    return self._ShowText

@showText.setter
def showText(self, show):
    if self._ShowText == show:
        return
    self._ShowText = show

@QtCore.pyqtProperty(Qt.PenCapStyle, notify=penStyleChanged)
def penStyle(self):
    return self._PenStyle

@penStyle.setter
def penStyle(self, style):
    if self._PenStyle == style:
        return
    self._PenStyle = style
    self.penStyleChanged.emit()

@QtCore.pyqtProperty(int, notify=dialTypeChanged)
def dialType(self):
    return self._DialType

@dialType.setter
def dialType(self, type):
    if self._DialType == type:
        return
    self._DialType = type
    self.dialTypeChanged.emit()

@QtCore.pyqtProperty(QFont, notify=textFontChanged)
def textFont(self):
    return self._TextFont

@textFont.setter
def textFont(self, font):
    if self._TextFont == font:
        return
    self._TextFont = font
    self.textFontChanged.emit()
1
share qml_widget.qml, provide a minimal reproducible example - eyllanesc
I've just edited my post and added qml file :) - Karol Siegieda
What is the code with QQuickWidget ?, could you show it - eyllanesc
what is RadialBar and MyClass?, We can not help you if you do not provide a minimal reproducible example, please check the link information. - eyllanesc
Ok, I just added them, sorry fot that - Karol Siegieda

1 Answers

1
votes

When you have an object created in Python/C++ and you want to connect it to an object created in QML, the correct option is to do it on the QML side using Connections, but for this you must create a property in MyClass.

main.py

import sys

from random import randrange

from PyQt5 import QtCore, QtGui, QtWidgets, QtQml, QtQuick, QtQuickWidgets

from RadialBar import RadialBar

class MyClass(QtCore.QObject):
    randomValueChanged = QtCore.pyqtSignal(float)

    def __init__(self, parent=None):
        super(MyClass, self).__init__(parent)
        self.m_randomValue = 0

    @QtCore.pyqtProperty(float, notify=randomValueChanged)
    def randomValue(self):
        return self.m_randomValue

    @randomValue.setter
    def randomValue(self, v):
        if self.m_randomValue == v:
            return
        self.m_randomValue = v
        self.randomValueChanged.emit(v)

    def random_value(self):
        v = float(randrange(1, 100))
        self.randomValue = v

class GUI_MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self, parent)
        self.batteryCWidget = QtQuickWidgets.QQuickWidget()
        self.setCentralWidget(self.batteryCWidget)
        self.batteryCWidget.setResizeMode(QtQuickWidgets.QQuickWidget.SizeRootObjectToView)

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = GUI_MainWindow()    #Main window written in pyqt5
    QtQml.qmlRegisterType(RadialBar, "SDK", 1,0, "RadialBar")
    batteryWidget = MyClass() # Class with function to update data in QML
    context = window.batteryCWidget.rootContext()
    context.setContextProperty("batteryWidget",batteryWidget)
    window.batteryCWidget.setSource(QtCore.QUrl.fromLocalFile('qml_widget.qml'))
    timer = QtCore.QTimer()
    timer.timeout.connect(batteryWidget.random_value)
    timer.start(200)
    window.show()
    sys.exit(app.exec_())

qml_widget.qml

import QtQuick 2.4
import SDK 1.0
import QtQuick.Layouts 1.1


Rectangle {
    id: root
    Layout.alignment: Layout.Center
    width: 160
    height: 145
    color: "#181818"
    property string suffix: "A"
    property int minVal: 0
    property int maxVal: 100
    property real actVal: 0

    Connections{
        target: batteryWidget
        onRandomValueChanged: root.actVal = batteryWidget.randomValue
    }

    Rectangle {
        Layout.alignment: Layout.Center
        width: 160
        height: 145
        color: "#1d1d35"
        border.color: "#000000"
        border.width: 3
        Text {
            id: name
            text: "Battery Current"
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.top: parent.top
            anchors.topMargin: 5
            font.pointSize: 13
            color: "#6affcd"
        }

        RadialBar {
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottom: parent.bottom
            width: parent.width / 1.4
            height: width - (0.001)*actVal
            penStyle: Qt.RoundCap
            progressColor: "#6affcd"
            foregroundColor: "#191a2f"
            dialWidth: 11
            minValue: minVal
            maxValue: maxVal
            value: actVal
            suffixText: suffix
            textFont {
                family: "Halvetica"
                italic: false
                pointSize: 18
            }
            textColor: "#00ffc1"
        }
    }
}

You can find the complete code in the following link.

enter image description here


As @GrecKo says, a much simpler way is to make a binding.

...
property real actVal: batteryWidget.randomValue

Rectangle {
...