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
Related
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()
}
}
I need to take informations from QML (from textInput in this case), make some operations on it and depending what is the operations result call appropriate function in QML. I know how to get the text from textInput, but can't find out how to response back, depending on the results. Here is my code:
main.qml:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
TextInput {
id: textInput
x: 280
y: 230
width: 80
height: 20
text: qsTr("Text Input")
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
selectByMouse: true
}
Dialog {
id: dialog1
modal: true
title: "OK"
Text {text: "Everything is OK!"}
x: parent.width/2 - width/2
y: parent.height/2 - height/2
}
Dialog {
id: dialog2
modal: true
title: "ERROR"
Text {text: "Check Internet connection!"}
x: parent.width/2 - width/2
y: parent.height/2 - height/2
}
Button {
id: button
x: 270
y: 318
text: qsTr("Check")
onClicked: {
bridge.check_tI(textInput.text)
}
}
}
main.py:
import sys
import os
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QObject, Slot, Signal, Property
class Bridge(QObject):
#Slot(str)
def check_tI(self, tf_text):
try:
# SOME OPERATIONS
# MUST BE DONE IN PYTHON
# IF EVERYTHING OK:
# dialog1.open()
print("OK! ", tf_text)
except:
# dialog2.open()
print("ERROR! ", tf_text)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
bridge = Bridge()
engine.rootContext().setContextProperty("bridge", bridge)
engine.load(os.path.join(os.path.dirname(__file__), "main.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
One possible is to return a boolean that can be used to make the decision to show one or the other dialog.
class Bridge(QObject):
#Slot(str, result=bool)
def check_tI(self, tf_text):
try:
# trivial demo
import random
assert random.randint(0, 10) % 2 == 0
print("OK! ", tf_text)
except:
print("ERROR! ", tf_text)
return False
else:
return True
onClicked: {
if(bridge.check_tI(textInput.text)){
dialog1.open()
}
else{
dialog2.open()
}
}
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()
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.
As #GrecKo says, a much simpler way is to make a binding.
...
property real actVal: batteryWidget.randomValue
Rectangle {
...
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
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.