Update PyQt progress from another thread running FTP download - python

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.

Related

PySide2 use QProgressBar as signal argument

I am trying to solve a problem with PySide2, QThread and the Signal/Slot mechanism.
What I want to achieve is updating a specific progressbar which is passed as reference via signal/slot mechanism.
The problem is the call of self.gui_connection.signal_progress_value.emit(lambda: self.progressbar, value) in my ProgressBarThread class.
The program stops after emitting the signal with a QProgressBar and an int value as arguments and causes a Seg Fault. If I just pass an int value to the signal and call emit in my ProgressBarThread everything is working fine.
Is it just not possible to pass an Object via Signal/Slot mechanism?
(Reduced) Code Samples:
GUISignal.py
from PySide2.QtCore import QObject, Signal
from PySide2.QtWidgets import QProgressBar
class GUISignal(QObject):
signal_progress_value = Signal(QProgressBar, int)
main.py
# File: main.py
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication, QPushButton
from PySide2.QtCore import QFile
from config.configManager import ConfigManager
from layout.srcConfigLayout import SrcConfigLayout
from layout.ingestLayout import IngestLayout
if __name__ == "__main__":
app = QApplication(sys.argv)
ui_file = QFile("main_window.ui")
ui_file.open(QFile.ReadOnly)
loader = QUiLoader()
window = loader.load(ui_file)
ui_file.close()
window.show()
src_config_layout = SrcConfigLayout(window)
ingestLayout = IngestLayout(window)
src_config_layout.load_config()
ingestLayout.load_config()
sys.exit(app.exec_())
ingestLayout.py
from PySide2.QtWidgets import QTableWidget, QTableWidgetItem, QPushButton, QProgressBar
from PySide2.QtCore import QObject, Slot
from util.ingestManager import IngestManager
from config.configManager import ConfigManager
from util.progressBarThread import ProgressBarThread
from functools import partial
class IngestLayout(QObject):
def __init__(self, parent):
super().__init__()
self.parent = parent
def handleIngestClicked(self, srcpath, destpath, progressbar):
ingestManager = IngestManager()
ingestManager.copy_to_destination(self, srcpath, destpath)
progressThread = ProgressBarThread(srcpath, destpath, progressbar, self.parent)
progressThread.gui_connection.signal_progress_value.connect(self.updateProgressbar)
progressThread.start()
#Slot(QProgressBar, int)
def updateProgressbar(self, progressbar: QProgressBar, value: int):
pass
# progressbar.setValue(value)
progressBarThread.py
from PySide2.QtCore import QThread
import os
import threading
import time
from signals.GUISignal import GUISignal
class ProgressBarThread(QThread):
def __init__(self, srcpath, destpath, progressbar, parent):
super(ProgressBarThread, self).__init__(parent)
self.gui_connection = GUISignal()
self.srcpath = srcpath
self.destpath = destpath
self.src_size = self.size(srcpath)
self.progressbar = progressbar
def run(self):
self.periodically_compare_folder_size()
def periodically_compare_folder_size(self):
dest_size = self.size(self.destpath)
while self.src_size > dest_size:
value = self.calc_progress(self.src_size, dest_size)
self.gui_connection.signal_progress_value.emit(lambda: self.progressbar, value)
dest_size = self.size(self.destpath)
time.sleep(2)
else:
self.gui_connection.signal_progress_value.emit(lambda: self.progressbar, 100)
print(100)
return
def size(self, path, *, follow_symlinks=False):
try:
with os.scandir(path) as it:
return sum(self.size(entry, follow_symlinks=follow_symlinks) for entry in it)
except NotADirectoryError:
return os.stat(path, follow_symlinks=follow_symlinks).st_size
def calc_progress(self, src_size, dest_size):
return dest_size / src_size * 100
The problem is that the program stops after emitting the signal with a QProgressBar and a value as arguments and causes a Seg Fault. If I just pass an int value to the signal and call emit in my ProgressBarThread everything is working fine.
Is it just not possible to pass Object via Signal/Slot mechanism?
It is not necessary for the signal to send as QProgressBar dates, in addition to the GUI is not thread-safe, on the other hand it is not necessary to use a lambda.
Considering the above, the solution is:
class GUISignal(QObject):
signal_progress_value = Signal(int)
class ProgressBarThread(QThread):
def __init__(self, srcpath, destpath, parent):
super(ProgressBarThread, self).__init__(parent)
self.gui_connection = GUISignal()
self.srcpath = srcpath
self.destpath = destpath
self.src_size = self.size(srcpath)
def run(self):
self.periodically_compare_folder_size()
def periodically_compare_folder_size(self):
dest_size = self.size(self.destpath)
while self.src_size > dest_size:
value = self.calc_progress(self.src_size, dest_size)
self.gui_connection.signal_progress_value.emit(value)
dest_size = self.size(self.destpath)
time.sleep(2)
else:
self.gui_connection.signal_progress_value.emit(100)
print(100)
return
# ...
def handleIngestClicked(self, srcpath, destpath, progressbar):
ingestManager = IngestManager()
ingestManager.copy_to_destination(self, srcpath, destpath)
progressThread = ProgressBarThread(srcpath, destpath, self.parent)
progressThread.gui_connection.signal_progress_value.connect(progressbar.setValue)
progressThread.start()
Update:
In the previous part of my answer I separate the GUI from the business logic as this will have a more scalable SW. But the OP seems to not want that so the simple solution is not to use the lambda method since it is unnecessary:
self.gui_connection.signal_progress_value.emit(self.progressbar, value)
self.gui_connection.signal_progress_value.emit(self.progressbar, 100)
SOLVED:
The problem was the lambda function in the emit function inside the QThread class. It seems like the object was not passed by the lambda function. Just using .emit(self,progressbar, 100) is working.

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

Slot as method of a locally instantiated class is never called

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

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

how to emit signal from a non PyQt class?

i'm programming an application in python using twisted and PyQt . the problem that i'm facing is that when a function in my twisted code is executed i have to print a line in the GUI, i'm trying to achieve this by emiting a signal (Non PyQt class). This does not seem to work, i have a doubt that the twisted event loop is screwing things for PyQt. Because the closeEvent signal is not being trapped by the program.
Here's the code snippet:
from PyQt4 import QtGui, QtCore
import sys
from twisted.internet.protocol import Factory, Protocol
from twisted.protocols import amp
import qt4reactor
class register_procedure(amp.Command):
arguments = [('MAC',amp.String()),
('IP',amp.String()),
('Computer_Name',amp.String()),
('OS',amp.String())
]
response = [('req_status', amp.String()),
('ALIGN_FUNCTION', amp.String()),
('ALIGN_Confirmation', amp.Integer()),
('Callback_offset',amp.Integer())
]
class Ui_MainWindow(QtGui.QMainWindow):
def __init__(self,reactor, parent=None):
super(Ui_MainWindow,self).__init__(parent)
self.reactor=reactor
self.pf = Factory()
self.pf.protocol = Protocol
self.reactor.listenTCP(3610, self.pf) # listen on port 1234
def setupUi(self,MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(903, 677)
self.centralwidget = QtGui.QWidget(MainWindow)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
self.centralwidget.setSizePolicy(sizePolicy)
self.create_item()
self.retranslateUi(MainWindow)
self.connect(self, QtCore.SIGNAL('triggered()'), self.closeEvent)
QtCore.QObject.connect(self,QtCore.SIGNAL('registered()'),self.registered)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.pushButton_4.setText(_translate("MainWindow", "Delete System ", None))
self.pushButton.setText(_translate("MainWindow", "Add System", None))
self.label_2.setText(_translate("MainWindow", "SYSTEM STATUS", None))
self.label.setText(_translate("MainWindow", "Monitoring Output", None))
def registered(self):# this function is not being triggered
print "check"
self.textbrowser.append()
def closeEvent(self, event):#neither is this being triggered
print "asdf"
self.rector.stop()
MainWindow.close()
event.accept()
class Protocol(amp.AMP):
#register_procedure.responder
def register_procedure(self,MAC,IP,Computer_Name,OS):
self.bridge_conn=bridge()
cursor_device.execute("""select * FROM devices where MAC = ?;""",[(MAC)])
exists_val=cursor_device.fetchone()
cursor_device.fetchone()
print "register"
if not exists_val== "":
cursor_device.execute("""update devices set IP= ? , Computer_name= ? , OS = ? where MAC= ?;""",[IP,Computer_Name,OS,MAC])
QtCore.QObject.emit( QtCore.SIGNAL('registered')) # <--emits signal
return {'req_status': "done" ,'ALIGN_FUNCTION':'none','ALIGN_Confirmation':0,'Callback_offset':call_offset(1)}
else:
cursor_device.execute("""INSERT INTO devices(Mac,Ip,Computer_name,Os) values (?,?,?,?);""",[MAC,IP,Computer_Name,OS])
QtCore.QObject.emit( QtCore.SIGNAL('registered'))#<--emits signal
return {'req_status': "done" ,'ALIGN_FUNCTION':'main_loop()','ALIGN_Confirmation':0,'Callback_offset':0}
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
try:
import qt4reactor
except ImportError:
from twisted.internet import qt4reactor
qt4reactor.install()
from twisted.internet import reactor
MainWindow = QtGui.QMainWindow() # <-- Instantiate QMainWindow object.
ui = Ui_MainWindow(reactor)
ui.setupUi(MainWindow)
MainWindow.show()
reactor.run()
This is what I use in my one code for sending signals from QGraphicsItems (because they are not derived from QObject and cannot send/receive signals by default). It's basically a simplified version of Radio-'s answer.
from PyQt4 import QtGui as QG
from PyQt4 import QtCore as QC
class SenderObject(QC.QObject):
something_happened = QC.pyqtSignal()
SenderObject is a tiny class derived from QObject where you can put all the signals you need to emit. In this case only one is defined.
class SnapROIItem(QG.QGraphicsRectItem):
def __init__(self, parent = None):
super(SnapROIItem, self).__init__(parent)
self.sender = SenderObject()
def do_something_and_emit(self):
...
self.sender.something_happened.emit()
In the non-QObject class you add a SenderObject as a sender variable. Anywhere where the non-QObject class is used you can connect the signal from the sender to anything you need.
class ROIManager(QC.QObject):
def add_snaproi(self, snaproi):
snaproi.sender.something_happened.connect(...)
UPDATE
The full code is this and should print out "Something happened...":
from PyQt4 import QtGui as QG
from PyQt4 import QtCore as QC
class SenderObject(QC.QObject):
something_happened = QC.pyqtSignal()
class SnapROIItem(QG.QGraphicsItem):
def __init__(self, parent = None):
super(SnapROIItem, self).__init__(parent)
self.sender = SenderObject()
def do_something_and_emit(self):
self.sender.something_happened.emit()
class ROIManager(QC.QObject):
def __init__(self, parent=None):
super(ROIManager,self).__init__(parent)
def add_snaproi(self, snaproi):
snaproi.sender.something_happened.connect(self.new_roi)
def new_roi(self):
print 'Something happened in ROI!'
if __name__=="__main__":)
roimanager = ROIManager()
snaproi = SnapROIItem()
roimanager.add_snaproi(snaproi)
snaproi.do_something_and_emit()
UPDATE 2
Instead of
QtCore.QObject.connect(self,QtCore.SIGNAL('registered()'),self.registered)
you should have:
protocol.sender.registered.connect(self.registered)
This means you also need to get hold of the protocol instance in self.pf (by the way, do you import Protocol and then define it yourself as well?)
In the Protocol class instead of
QtCore.QObject.emit( QtCore.SIGNAL('registered')
you need to, first, instantiate a SenderObject in the Protocol.
class Protocol(amp.AMP):
def __init__( self, *args, **kw ):
super(Protocol, self).__init__(*args, **kw)
self.sender = SenderObject()
and then, in register_procedure emit the signal through sender:
self.sender.registered.emit()
For all of this to work you'll have to have defined SenderObject as:
class SenderObject(QC.QObject):
registered = QC.pyqtSignal()
This is an old post, but it helped me. Here is my version. One item that is not a QObject signaling two other non QObject's to run their methods.
from PyQt4 import QtGui, QtCore
class Signal(object):
class Emitter(QtCore.QObject):
registered = QtCore.pyqtSignal()
def __init__(self):
super(Signal.Emitter, self).__init__()
def __init__(self):
self.emitter = Signal.Emitter()
def register(self):
self.emitter.registered.emit()
def connect(self, signal, slot):
signal.emitter.registered.connect(slot)
class item(object):
def __init__(self, name):
self.name = name
self.signal = Signal()
def something(self):
print self.name, ' says something'
>>> itemA = item('a')
>>> itemB = item('b')
>>> itemC = item('c')
>>> itemA.signal.connect(itemA.signal, itemB.something)
>>> itemA.signal.connect(itemA.signal, itemC.something)
>>> itemA.signal.register()
b says something
c says something
The two basic problems are that:
1) Something has to know the sender and receiver of the signals
Consider a more frequent case in Qt where you may have multiple buttons, each with a 'clicked' signal. Slots need to know which button was clicked, so catching a generic signal does not make much sense.
and 2) The signals have to originate from a QObject.
Having said that, I'm not sure what the canonical implementation is. Here is one way to do it, using the idea of a Bridge that you had in one of your earlier posts, and a special Emitter class inside of Protocol. Running this code will simply print 'Working it' when protocol.register() is called.
from PyQt4 import QtGui, QtCore
import sys
class Ui_MainWindow(QtGui.QMainWindow):
def __init__(self):
super(Ui_MainWindow, self).__init__()
def work(self):
print "Working it"
class Protocol(object):
class Emitter(QtCore.QObject):
registered = QtCore.pyqtSignal()
def __init__(self):
super(Protocol.Emitter, self).__init__()
def __init__(self):
self.emitter = Protocol.Emitter()
def register(self):
self.emitter.registered.emit()
class Bridge(QtCore.QObject):
def __init__(self, gui, protocol):
super(Bridge, self).__init__()
self.gui = gui
self.protocol = protocol
def bridge(self):
self.protocol.emitter.registered.connect(self.gui.work)
app = QtGui.QApplication(sys.argv)
gui = Ui_MainWindow()
protocol = Protocol()
bridge = Bridge(gui, protocol)
bridge.bridge()
#protocol.register() #uncomment to see 'Working it' printed to the console
I improved the solution of #Ryan Trowbridge a bit to deal with a generic type. I hoped to use Generic[T] but turned out too complicated.
class GenSignal:
def __init__(self,typ):
Emitter = type('Emitter', (QtCore.QObject,), {'signal': Signal(typ)})
self.emitter = Emitter()
def emit(self,*args,**kw):
self.emitter.signal.emit(*args,**kw)
def connect(self, slot):
self.emitter.signal.connect(slot)
To use, x=GenSignal(int) and continue as usual.

Categories

Resources