PyQt load function on startup (qml loader) - python

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

Related

Output Console (print function) to Gui TextArea

I made a QtQuick Window Gui Application for Python 3.8 on Windows. The last thing I cant figure out is how to display Python print() in the Gui Text Area. What i want is, that wherever in my Python code a print statement is and gets executed during runtime, i want to output it into the TextArea in my Gui app
I read the following post, but failed to implemet it, different errors occured and am more confused then before:
the closest and most usefull was this one:
How to capture output of Python's interpreter and show in a Text widget?
and some others:
Python/PyQt/Qt Threading: How do I print stdout/stderr right away?
How to Redirect a Python Console output to a QTextBox
How can I flush the output of the print function?
How do I direct console output to a pyqt5 plainTextEdit widget with Python?
Python Printing StdOut As It Received
working Sample Code to send a string from Python into QML TextArea
main.py
import os
from pathlib import Path
import sys
from vantage import daily
# load GUI libs
from PySide2.QtGui import QGuiApplication
from PySide2.QtCore import QSettings, QObject, Signal, Slot
from PySide2.QtQml import QQmlApplicationEngine
# load app
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml"))
class Backend(QObject):
textwritten = Signal(str, arguments=['writen'])
def __init__(self):
super().__init__()
self.timer = QTimer()
self.timer.setInterval(100)
self.timer.timeout.connect(self.writer)
self.timer.start()
# console output write function
def writer(self):
towrite = 'i am writing'
self.textwritten.emit(str(towrite))
# create an instance of the Python object (Backend class)
back_end = Backend()
# give data back to QML
engine.rootObjects()[0].setProperty('writen', back_end)
# close app
sys.exit(app.exec_())
main.qml
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.15
Window {
width: 640
height: 480
visible: true
color: "#2f2f2f"
title: qsTr("alpha")
/*print out console text*/
property string texted: "Console Text"
property QtObject writen
ScrollView {
id: scrollViewCon
x: 58
y: 306
width: 507
height: 100
ScrollBar.vertical.verticalPadding: 4
ScrollBar.vertical.minimumSize: 0.4
ScrollBar.vertical.contentItem: Rectangle {
implicitWidth: 6
implicitHeight: 100
radius: width / 2
color: control.pressed ? "#81e889" : "#f9930b"
}
TextArea {
font.family: control.font
font.pointSize: 8
color:"#f9930b"
wrapMode: TextEdit.Wrap
KeyNavigation.priority: KeyNavigation.BeforeItem
KeyNavigation.tab: textField
placeholderTextColor : "#f9930b"
opacity: 1
text: texted
placeholderText: texted //qsTr("Console")
readOnly: true
background: Rectangle {
radius: 12
border.width: 2
border.color: "#f9930b"
}
}
}
Connections {
target: writen
function onTextwritten(msg) {
texted = msg;
}
}
}
i think what needs to happen is that everytime sys.stdout is called by print() it emits a signal with itself?
leaving main.qml as is and only changing main.py
main.py
...
class Backend(QObject):
textwritten = Signal(str, arguments=['writen'])
def __init__(self):
super().__init__()
sys.stdout = self.writer(str(sys.stdout))
def writer(self, message):
#towrite = 'i am writing'
self.textwritten.emit(message)
...
The print function writes over sys.stdout so the solution is to assign some QObject that has a write method that emits a signal. For this you can use contextlib.redirect_stdout:
import os
import sys
from contextlib import redirect_stdout
from functools import cached_property
from pathlib import Path
from PySide2.QtCore import (
QCoreApplication,
QDateTime,
QObject,
Qt,
QTimer,
QUrl,
Signal,
)
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = Path(__file__).resolve().parent
class RedirectHelper(QObject):
stream_changed = Signal(str, name="streamChanged", arguments=["stream"])
def write(self, message):
self.stream_changed.emit(message)
class TimerTest(QObject):
#cached_property
def timer(self):
return QTimer(interval=1000, timeout=self.handle_timeout)
def handle_timeout(self):
print(QDateTime.currentDateTime().toString())
def start(self):
self.timer.start()
def main():
ret = 0
redirect_helper = RedirectHelper()
with redirect_stdout(redirect_helper):
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("redirectHelper", redirect_helper)
filename = os.fspath(CURRENT_DIRECTORY / "main.qml")
url = QUrl.fromLocalFile(filename)
def handle_object_created(obj, obj_url):
if obj is None and url == obj_url:
QCoreApplication.exit(-1)
engine.objectCreated.connect(handle_object_created, Qt.QueuedConnection)
engine.load(url)
timer_test = TimerTest()
timer_test.start()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
import QtQuick 2.12
import QtQuick.Controls 2.12
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
Flickable {
id: flickable
flickableDirection: Flickable.VerticalFlick
anchors.fill: parent
TextArea.flickable: TextArea {
id: textArea
anchors.fill: parent
readOnly: true
font.pointSize: 8
color: "#f9930b"
wrapMode: TextEdit.Wrap
placeholderTextColor: "#f9930b"
opacity: 1
placeholderText: qsTr("Console")
background: Rectangle {
radius: 12
border.width: 2
border.color: "#f9930b"
}
}
ScrollBar.vertical: ScrollBar {
}
}
Connections {
function onStreamChanged(stream) {
textArea.insert(textArea.length, stream);
}
target: redirectHelper
}
}

Call QML function from Python

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

Error in getting the root object from QQmlApplicationEngine in Component.onCompleted

I try to get the root object after window having completed, but I get a error:
QmlObj = self.engine.rootObjects()[0]
Error: list index out of range
The strange thing is that it works when I try to call foo.init_window() after the MouseArea having clicked.
Here is my python code:
main.py
from PySide2.QtWidgets import QApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QObject, QUrl, Slot
import sys
import win32gui
flag = False
class Foo(QObject):
def __init__(self):
super().__init__()
self.engine = QQmlApplicationEngine()
#Slot()
def init_window(self):
global flag
if not flag:
QmlObj = self.engine.rootObjects()[0]
desk = win32gui.FindWindow("Progman", "Program Manager")
print(desk)
sndWnd = win32gui.FindWindowEx(desk, 0, "SHELLDLL_DefView", None)
print(sndWnd)
targetWnd = win32gui.FindWindowEx(sndWnd,
0, "SysListView32", "FolderView")
print(targetWnd)
win32gui.SetParent((int)(QmlObj.winId()), targetWnd)
flag = True
if __name__ == "__main__":
app = QApplication(sys.argv)
foo = Foo()
foo.engine.rootContext().setContextProperty("foo", foo)
foo.engine.load(QUrl("main.qml"))
# win = foo.engine.rootObjects()[0]
# win.show()
if not foo.engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
Here is the .qml file:
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 2.0
Window {
width: 200
height: 100
visible: true
//flags: Qt.FramelessWindowHint
//flags: Qt.WindowStaysOnBottomHint
//flags: Qt.WindowMinMaxButtonsHint
Rectangle {
anchors.fill: parent
color: "red"
Component.onCompleted: foo.init_window()
MouseArea {
anchors.fill: parent
onClicked: foo.init_window()
}
Text {
anchors.centerIn: parent
text: "Hello, World!"
}
Button {
text: "Ok"
onClicked: {
console.log("OK Button clicked....")
}
}
}
}
The problem is that in Component.onCompleted the window(the rootObject) has finished building but the engine list has not been updated. The solution is to invoke init_window an instant later using Qt.callLater():
Component.onCompleted: Qt.callLater(foo.init_window)

PySide2 (5.14.2) signal with named parameter

PySide Signal argument can't be retrieved from QML
According to this post, PySide2 (version > 5.12.5) supports signal with named parameter like in PyQt5. So i tried here in PySide2 (5.14.2) and got error like
file:///E:/QML/projects/main.qml:72:5: Cannot assign to non-existent property "onSpitInput"
Tell me what's wrong here.
*app.py
import os
import sys
from PySide2 import QtCore, QtGui, QtWidgets, QtQml
class controller(QtCore.QObject):
spitInput = QtCore.Signal(str, arguments=['userinput'])
def __init__(self):
QtCore.QObject.__init__(self)
#QtCore.Slot(int, result=list)
def getUserInput(self, first):
self.spitInput.emit(str(first) + 'is the value given by user')
controller = controller()
app = QtWidgets.QApplication(sys.argv)
current_dir = os.path.dirname(os.path.realpath(__file__))
engine = QtQml.QQmlApplicationEngine()
engine.addImportPath(current_dir)
engine.rootContext().setContextProperty("controller", controller)
filename = os.path.join(current_dir, "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
engine.quit.connect(app.quit)
sys.exit(app.exec_())
main.qml
import QtQuick 2.13
import QtQuick.Controls 2.13
ApplicationWindow {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
id: bufferRectId
width: 640
height: 480
anchors.fill: parent
TextField{
id:firstTextInputFieldId
font.pointSize: 16
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
}
Button{
id:calcButtonId
width: 60
height: 30
text: "Click Me"
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 60
onClicked: {
controller.getUserInput(firstTextInputFieldId.text)
}
}
}
onSpitInput: console.log(userinput)
}
When you use the following code:
ApplicationWindow {
id: root
// ...
onSpitInput: console.log(userinput)
}
You are stating that onSpitInput belongs to "root" which is clearly false since it belongs to "controller" and therefore fails.
In this case you should use Connections:
import QtQuick 2.13
import QtQuick.Controls 2.13
ApplicationWindow {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
id: bufferRectId
anchors.fill: parent
TextField{
id:firstTextInputFieldId
font.pointSize: 16
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
}
Button{
id:calcButtonId
width: 60
height: 30
text: "Click Me"
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 60
onClicked: controller.getUserInput(firstTextInputFieldId.text)
}
}
Connections{
target: controller
onSpitInput: console.log(userinput)
}
}
On the other hand there is another error: The getUserInput method receives a string and does not return anything, but according to your code it must receive an integer and return a list. The correct is:
import os
import sys
from PySide2 import QtCore, QtGui, QtWidgets, QtQml
class controller(QtCore.QObject):
spitInput = QtCore.Signal(str, arguments=["userinput"])
#QtCore.Slot(str)
def getUserInput(self, first):
self.spitInput.emit("{} is the value given by user".format(first))
if __name__ == "__main__":
controller = controller()
app = QtWidgets.QApplication(sys.argv)
current_dir = os.path.dirname(os.path.realpath(__file__))
engine = QtQml.QQmlApplicationEngine()
engine.addImportPath(current_dir)
engine.rootContext().setContextProperty("controller", controller)
filename = os.path.join(current_dir, "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
engine.quit.connect(app.quit)
sys.exit(app.exec_())

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

Categories