Slot as method of a locally instantiated class is never called - python

I have a list of mp3 files that I'm trying to play through pygame whenever different buttons are pressed (one file per button). Since there is a variable number of those files, I'm simply implementing a for loop, and I have an AudioPlayer class that I instantiate each time as follows:
import sys, pygame
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class AudioPlayer(QObject):
def __init__(self, filename):
super().__init__()
self.filename = filename
print("Created " + filename)
def play(self):
print("Playing " + self.filename)
pygame.mixer.music.load(self.filename)
pygame.mixer.music.play()
class Session(QMainWindow):
def __init__(self):
super().__init__()
self.mainWid = QWidget(self)
self.vbox = QVBoxLayout()
self.mainWid.setLayout(self.vbox)
self.setCentralWidget(self.mainWid)
self.show()
pygame.mixer.init()
filenames = [r'C:\...\file1.mp3', r'C:\...\file2.mp3']
for filename in filenames:
playButton = QPushButton('Play', self)
localPlay = AudioPlayer(filename)
playButton.clicked.connect(localPlay.play)
self.vbox.addWidget(playButton)
if __name__ == '__main__':
app = QApplication(sys.argv)
Session()
sys.exit(app.exec_())
My problem is simply that the files do not play when I press the button, neither does the message get printed at all; it's like the slot never gets called:
admin#home> python main2.py
Created C:\...\file1.mp3
Created C:\...\file2.mp3
admin#home>
If I play the files manually outside of the loop, like this, it works:
class Session(QMainWindow):
def __init__(self):
# ...
filenames = [r'C:\...\file1.mp3', r'C:\...\file2.mp3']
pygame.mixer.music.load(filenames[0])
pygame.mixer.music.play()
As you can see, I made sure AudioPlayer inherited QObject and called __init__, so I believe it should be able to receive signals. So what is going on here? Is it a local variable issue?

The problem is caused by the fact that the AudioPlayer objects created in the loop are local variables, so when they finish running the constructor they are deleted from memory. There are 2 possible solutions, the first is to make them attributes of the class, or the second is to pass them a parent since they inherit from QObject, I will use this second method:
class AudioPlayer(QObject):
def __init__(self, filename, parent=None):
super().__init__(parent=parent)
self.filename = filename
print("Created " + filename)
#pyqtSlot()
def play(self):
print("Playing " + self.filename)
pygame.mixer.music.load(self.filename)
pygame.mixer.music.play()
class Session(QMainWindow):
def __init__(self):
super().__init__()
self.mainWid = QWidget(self)
self.vbox = QVBoxLayout()
self.mainWid.setLayout(self.vbox)
self.setCentralWidget(self.mainWid)
self.show()
pygame.mixer.init()
filenames = [r'C:\...\file1.mp3', r'C:\...\file2.mp3']
for filename in filenames:
playButton = QPushButton('Play', self)
localPlay = AudioPlayer(filename, self)
playButton.clicked.connect(localPlay.play)
self.vbox.addWidget(playButton)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Session()
sys.exit(app.exec_())

Related

Proper way to pass an infromation/variable across QTabWidgets?

EDIT: Changed title, few mistakes in code were resolved (now working properly)
The GUI I am trying to make will be a simple QTabWidget, leading a user straightforwardly towards the end tab by tab.
For now, I have three *.py files - main.py, tab1.py, tab2.py. In main.py is the main window of the app and function to run the app like this (simplified just to focus on my question):
import sys
import tab1
import tab2
import PyQt5.QtWidgets as qtw
def main():
app = qtw.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
class MainWindow(qtw.QmainWindow):
def __init__(self):
super().__init__()
self.tabwidget = qtw.QTabWidget()
self.setCentralWidget(self.tabwidget)
self.tab1 = tab1.Tab_1()
self.tab2 = tab2.Tab_2()
# This is how I now passing the information from tab1 to tab2
self.tab1.path_line.textChanged.connect(self.tab2.path_widget.setText)
self.tabwidget.addTab(self.tab1, 'tab1')
self.tabwidget.addTab(self.tab2, 'tab2')
if __name__ == '__main__':
main()
In the tab1.py is a defined class for a tabwidget which will serve as an input data tab. There is a button to open filedialog, read filename, and write the path into the QLineEdit widget:
import PyQt5.QtWidgets as qtw
class Tab_1(qtw.QWidget):
def __init__(self):
super().__init__()
self.path_line = qtw.QLineEdit()
self.but = qtw.QPushButton('Open')
self.but.clicked.connect(self.openfile)
layout_1 = qtw.QVBoxLayout()
layout_1.addWidget(self.but)
self.setLayout(layout_1)
def openfile(self):
filename, _ = qtw.QFileDialog.getOpenFileName(self, 'Title', 'File types')
if filename:
self.path_line.setText(filename)
else:
self.path_line.setText('No file was selected!')
Now I want to in another file tab2.py use the path I got from qtw.OpenFileDialog. So the defined class Tab_2() looks like this:
import PyQt5.QtWidgets as qtw
class Tab_2(qtw.QWidget):
def __init__(self):
super().__init__()
# Retrieving the information by QLabel widget
self.path_widget = qtw.QLabel()
# Transform the information into string variable
self.path_string = self.path_widget.text()
layout_2 = qtw.QVBoxLayout()
layout_2.addWidget(self.path_widget) # The path from QFileDialog (Tab1) should appered on Tab2
self.setLayout(layout_2)
My question is, is this the proper way to do it? Should I use MainWindow class as a "getter" and "passer" of the information like that or should that be implemented in the tab classes themselves? It works but I do not want to learn to do something bad way and eventually get used to it. I understand classes and their inheritance to some point (a lot of examples of dog classes or employee classes which I understand how it works but in my case I am confused.). In combination with GUI, it messing up my head. Also, I want to have each tab as a separate class in a separate *.py file to make it clear and easy to add another one in the future. I see, that it might not be the right way to uses classes but each tab will have a different layout.
After some testing, I found out that the best way to pass variable across QTabWidgets is, in my case, by class inheritence. For this idea, thanks to #S. Nick from Code Review.
I have main.py for the QMainWindow of the app and then two another *.py files, each containing a TabWidget class. Since I am creating imported Tab widgets classes in the main.py, the best solution for my question was to save a variable into the QMainWindow and then access it via Tab classes through the "self-parent thing". I am really a programming beginner, therefore I do not know if this is a basic knowledge or not. But by storing a variable into the parent QMainWindow class I can access it from any widget class created in the QMainWindow by including the "self" parameter. Here is the variance of the final code I used:
import sys
import PyQt5.QtWidgets as qtw
#from tab1 import Tab_1
class Tab_1(qtw.QWidget):
""" In this class I obtain a file path """
def __init__(self, parent=None): # + parent
super().__init__()
self.parent = parent # + parent
self.but_1 = qtw.QPushButton('Open')
self.but_1.clicked.connect(self.open_file)
layout_1 = qtw.QVBoxLayout(self)
layout_1.addWidget(self.but_1)
self.setLayout(layout_1)
def open_file(self):
filename, _ = qtw.QFileDialog.getOpenFileName(self, 'Title', 'File types')
if filename:
self.parent.file_path = filename # + parent
else:
self.parent.file_path = 'No file was selected' # + parent
#from tab2 import Tab_2
class Tab_2(qtw.QWidget):
""" In this class I want to use the file path from Tab 1 """
def __init__(self, parent=None): # + parent
super().__init__()
self.parent = parent # + parent
self.but_2 = qtw.QPushButton('Load path')
self.but_2.clicked.connect(self._load_path)
self.path_widget = qtw.QLabel('') # will show path to file after the button-click
layout_2 = qtw.QVBoxLayout(self)
layout_2.addWidget(self.but_2)
layout_2.addWidget(self.path_widget)
self.setLayout(layout_2)
def _load_path(self):
self.path_widget.setText(self.parent.file_path)
class MainWindow(qtw.QMainWindow):
def __init__(self):
super().__init__()
self.file_path = '' # + file_path
self.tabwidget = qtw.QTabWidget()
self.setCentralWidget(self.tabwidget)
self.tab1 = Tab_1(self) # + self
self.tab2 = Tab_2(self) # + self
self.tabwidget.addTab(self.tab1, 'Tab 1')
self.tabwidget.addTab(self.tab2, 'Tab 2')
def main():
app = qtw.QApplication(sys.argv)
window = MainWindow()
window.resize(400, 300)
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Update PyQt progress from another thread running FTP download

I want to access progress bar's (which is in the Ui_MainWindow() class) setMaximum() from another class/thread (DownloadThread() class).
I tried making DownloadThread() class inherit from Ui_MainWindow:
DownloadThread(Ui_MainWindow). But when I try to set the maximum progress bar value:
Ui_MainWindow.progressBar.setMaximum(100)
I get this error:
AttributeError: type object 'Ui_MainWindow' has no attribute 'progressBar'
My code:
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
# ...
self.updateButton = QtGui.QPushButton(self.centralwidget)
self.progressBar = QtGui.QProgressBar(self.centralwidget)
self.updateStatusText = QtGui.QLabel(self.centralwidget)
# ...
self.updateButton.clicked.connect(self.download_file)
# ...
def download_file(self):
self.thread = DownloadThread()
self.thread.data_downloaded.connect(self.on_data_ready)
self.thread.start()
def on_data_ready(self, data):
self.updateStatusText.setText(str(data))
class DownloadThread(QtCore.QThread, Ui_MainWindow):
data_downloaded = QtCore.pyqtSignal(object)
def run(self):
self.data_downloaded.emit('Status: Connecting...')
ftp = FTP('example.com')
ftp.login(user='user', passwd='pass')
ftp.cwd('/some_directory/')
filename = '100MB.bin'
totalsize = ftp.size(filename)
print(totalsize)
# SET THE MAXIMUM VALUE OF THE PROGRESS BAR
Ui_MainWindow.progressBar.setMaximum(totalsize)
self.data_downloaded.emit('Status: Downloading...')
global localfile
with open(filename, 'wb') as localfile:
ftp.retrbinary('RETR ' + filename, self.file_write)
ftp.quit()
localfile.close()
self.data_downloaded.emit('Status: Updated!')
def file_write(self, data):
global localfile
localfile.write(data)
print(len(data))
The immediate problem is that Ui_MainWindow is a class, not an instance of the class. You would have to pass your "window" self to the DownloadThread. But that's not the right solution anyway. You cannot access PyQt widgets from another thread. Instead, use the same technique as you already do, to update the status text (FTP download with text label showing the current status of the download).
class Ui_MainWindow(object):
def download_file(self):
self.thread = DownloadThread()
self.thread.data_downloaded.connect(self.on_data_ready)
self.thread.data_progress.connect(self.on_progress_ready)
self.progress_initialized = False
self.thread.start()
def on_progress_ready(self, data):
# The first signal sets the maximum, the other signals increase a progress
if self.progress_initialized:
self.progressBar.setValue(self.progressBar.value() + int(data))
else:
self.progressBar.setMaximum(int(data))
self.progress_initialized = True
class DownloadThread(QtCore.QThread):
data_downloaded = QtCore.pyqtSignal(object)
data_progress = QtCore.pyqtSignal(object)
def run(self):
self.data_downloaded.emit('Status: Connecting...')
with FTP('example.com') as ftp:
ftp.login(user='user', passwd='pass')
ftp.cwd('/some_directory/')
filename = '100MB.bin'
totalsize = ftp.size(filename)
print(totalsize)
# The first signal sets the maximum
self.data_progress.emit(str(totalsize))
self.data_downloaded.emit('Status: Downloading...')
with open(filename, 'wb') as self.localfile:
ftp.retrbinary('RETR ' + filename, self.file_write)
self.data_downloaded.emit('Status: Updated!')
def file_write(self, data):
self.localfile.write(data)
# The other signals increase a progress
self.data_progress.emit(str(len(data)))
Other changes to your code:
global localfile is a bad practice. Use self.localfile instead.
There's no need for localfile.close(), with takes care of that.
Similarly ftp.quit() should be replaced with with.
There's no need for DownloadThread to inherit from Ui_MainWindow.
the thread class:
from PyQt5 import QtCore, QtGui, QtWidgets, QtPrintSupport,QtWebEngineWidgets
from PyQt5.QtWidgets import QDialog,QWidget,QApplication, QInputDialog, QLineEdit, QFileDialog,QProgressDialog, QMainWindow, QFrame,QSplashScreen
from PyQt5.QtCore import QThread , pyqtSignal,Qt
from PyQt5.QtGui import QIcon,QPainter,QPixmap
class threaded_class(QThread):
signal_to_send_at_progress_bar = pyqtSignal()
def __init__(self,parent=None):
QThread.__init__(self, parent=parent)
def run(self):
while self.isRunning:
##do the stuf you want here and when you want to change the progress bar
self.signal_to_send_at_progress_bar.emit()
in your main window:
class mainProgram(QtWidgets.QMainWindow, Ui_MainWindow): #main window
def __init__(self, parent=None):
super(mainProgram, self).__init__(parent)
self.setupUi(self)
###...........#####
self.thread_class_in_main_window = threaded_class()
self.thread_class_in_main_window .start()
self.thread_db.signal_to_send_at_progress_bar.connect(progressBar.setMaximum(100))
You can also emit string and number with signals.

How to process data after reading from QIODevice.read()?

This is my first time to ask question, if there are something I get wrong, please tell me, I will be appreciate.
I am using QWebEngineUrlSchemeHandler for a custom url, and I want to use QFile to open a javascript file for testing.
First, if I just use
QFile("ken.js")
The window could open the javascript, but if I use my custom QFile,
I have no idea how to process the data after I read from QIODevice.read().
I want to know what I need to do to make the window could open the javascript after I read the data from QIODevice.read().
Please give me some suggests, thank.
Here is my full code.
class TestQFile(QtCore.QFile):
def __init__(self, fileName):
super().__init__()
self.setFileName(fileName)
self.open(self.ReadOnly)
self.data = b''
while True:
receivedData = self.read(10)
self.data += receivedData
if receivedData == b'':
break
class TestHandler(QWebEngineUrlSchemeHandler):
def requestStarted(self, request):
self._dev = TestQFile("ken.js")
request.reply(b'text/javascript', self._dev)
class TestWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._view = QWebEngineView(self)
self._handler = TestHandler() # Must keep ref
self._view.page().profile().installUrlSchemeHandler(b'myuri', self._handler)
self._view.setHtml('<html><head><title>Test</title></head><body><div id="d1"></div><script src="myuri://test/ken.js"></script></body></html>')
self.setCentralWidget(self._view)
self.show()
self.raise_()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
win = TestWindow()
app.exec_()
Actually if the class inherits from QFile you do not have to do anything since it already has implemented the methods that QWebEngineUrlRequestJob requires since it will use the methods that every class that inherits from QIODevice must implement as readData(), writeData(), atEnd(), etc.
from PyQt5 import QtCore, QtWidgets, QtWebEngineCore,QtWebEngineWidgets
class TestQFile(QtCore.QFile):
def __init__(self, fileName):
super().__init__()
self.setFileName(fileName)
class TestHandler(QtWebEngineCore.QWebEngineUrlSchemeHandler):
def requestStarted(self, request):
self.file = TestQFile("ken.js")
request.reply(b'text/javascript', self.file)
class TestWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._view = QtWebEngineWidgets.QWebEngineView(self)
self._handler = TestHandler() # Must keep ref
self._view.page().profile().installUrlSchemeHandler(b'myuri', self._handler)
self._view.setHtml('<html><head><title>Test</title></head><body><div id="d1"></div><script src="myuri://test/ken.js"></script></body></html>')
self.setCentralWidget(self._view)
self.show()
self.raise_()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
win = TestWindow()
sys.exit(app.exec_())

Python - how to use the value of a variable inside a function that is called when a button is clicked

I have a piece of python code like this:
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
--snip--
self.ui.pushButton.clicked.connect(self.selectFile)
someParameter = someFunction(self.filename) # (1)
def selectFile(self):
self.ui.lineEdit.setText(QtGui.QFileDialog.getOpenFileName())
self.filename = self.ui.lineEdit.text() # I want to use "filename" variable in (1)
--snip--
I want to catch the name of the file which is selected by QFileDialog and do two things; firstly, show the address of the file in a lineEdit widget and secondly, store the address of the file in a variable so I can use it later on the rest of the process. How should I do that, and what is the proper way?
It seems you are not accustomed with object oriented programming.
In object oriented programming, there is a member and method in a Class.
In your case you should define member like this so that you can handle it later. So you should learn about what member is in object oriented programming.
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.filename = ""
self.someParameter = None
--snip--
self.ui.pushButton.clicked.connect(self.selectFile)
def setParameter(self):
self.someParameter = someFunction(self.filename)
def selectFile(self):
self.filename = QtGui.QFileDialog.getOpenFileName()
self.ui.lineEdit.setText(self.filename)
self.setParameter()
--snip--
Edited
Here is some sample code which use QFileDialog.getOpenFileName. Try this.
from PyQt5.QtWidgets import QWidget, QPushButton, QFileDialog, QVBoxLayout, QApplication
from PyQt5 import QtGui
class my_ui(QWidget):
def __init__(self, parent=None):
super(my_ui, self).__init__()
self.filename = ""
self.button1 = QPushButton("show dialog", parent)
self.button2 = QPushButton("do something", parent)
self.button1.clicked.connect(self.show_dialog)
self.button2.clicked.connect(self.do_something)
self.layout = QVBoxLayout()
self.layout.addWidget(self.button1)
self.layout.addWidget(self.button2)
self.setLayout(self.layout)
def show_dialog(self):
self.filename = QFileDialog.getOpenFileName()
print("filename updated: '%s'"%str(self.filename))
def do_something(self):
print("filename = '%s'"%str(self.filename))
app = QApplication([])
sample_ui = my_ui()
sample_ui.show()
app.exec_()

Inactive subwindows in PyQt4

I have been struggling with a problem recently and I cannot get around it. I have a PyQt QMainWindow which contains a subwindow :
As you can figure out, clicking on the GO! button will open a number of subwindows specified by the number in the QLineEdit :
And clicking on the QCheckBox inside each subwindow should display a text.
The problem is that this works only for the last spawned subwindow. The others appear to be inactive.
Is their a way to make them active?
Please find my code below:
from PyQt4 import QtGui
import mainWin
import subWin
import sys
class MainWindowGui():
def __init__(self):
self.w = QtGui.QMainWindow()
self.MainWindow = myWinCls(self)
self.MainWindow.setupUi(self.w)
self.w.showMaximized()
class myWinCls(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self)
self.parent = parent
def setupUi(self,Widget):
self.ui = mainWin.Ui_MainWindow()
self.ui.setupUi(Widget)
self.ui.mdiArea.addSubWindow(self.ui.subwindow)
self.ui.goBtn.clicked.connect(self.show_wins)
def show_wins(self):
N = int(self.ui.nbrEdit.text())
for self.k in xrange(N):
self.show_subwins()
def show_subwins(self):
self.win = QtGui.QWidget()
self.child_window = showSubWinCls(self)
self.child_window.setupUi(self.win)
self.subwin = self.ui.mdiArea.addSubWindow(self.win)
self.win.setWindowTitle("Subwin " + str(self.k))
self.subwin.show()
class showSubWinCls(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self)
self.parent = parent
def setupUi(self, Widget):
self.ui = subWin.Ui_Form()
self.ui.setupUi(Widget)
self.ui.checkBox.clicked.connect(self.show_msg)
def show_msg(self):
if self.ui.checkBox.isChecked():
self.ui.lineEdit.setText("Yiiiiiihaaaaaa !!!")
else:
self.ui.lineEdit.setText("")
def main():
app = QtGui.QApplication(sys.argv)
app.setStyle(QtGui.QStyleFactory.create('WindowsVista'))
ex = MainWindowGui()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I am sure this problem is somehow a classic trick but despite searching for some time now, I cannot figure it out.
Thanks for your help!
The problematic part:
def show_wins(self):
N = int(self.ui.nbrEdit.text())
for self.k in xrange(N):
self.show_subwins()
def show_subwins(self):
self.win = QtGui.QWidget()
self.child_window = showSubWinCls(self) #erase refererence to previous one
self.child_window.setupUi(self.win)
self.subwin = self.ui.mdiArea.addSubWindow(self.win)
self.win.setWindowTitle("Subwin " + str(self.k))
self.subwin.show()
You are only keeping reference to one subwindow in self.child_window, the last window openned.
In show_wins, you call show_subwin N time. Each time, you redefine self.child_window as a new instance of the class showSubWinCls. You lose the reference to the previous one.
You need to keep a reference to all the windows, otherwise signals and slots won't work. You can do something like this:
class myWinCls(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self)
self.parent = parent
self.subWindowList=[]
def show_subwins(self):
...
child_window = showSubWinCls(self)
child_window.setupUi(self.win)
self.subWindowList.append(child_window)

Categories

Resources