I am sending a signal from another class to update a PySide QTableWidget but nothing is coming though. I have made this very simple for this demonstration:
This is in the controller module called Records.py
class Records(QDialog, randomDialog.Ui_watchingDialog):
signal = 1
atSig = Signal(int)
def add_button_clicked(self):
# Do some stuff
self.signal = 1
self.atSig.emit(self.signal)
# Do some other Stuff
This sits out side the controller called main.py
from controller import Records
class main(QMainWindow, pyMainWindow.Ui_mainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.setupUi(self)
signal_records = Records.Records()
signal_records.atSig.connect(self.showNewData)
def showNewData(self, signal):
if signal == 1:
print "It worked!"
else:
print "Problem"
How come this signal is not coming through? No error messages are being thrown and neither of the print statements aren't being called. How can I fix this?
signal_records falls out of scope as soon as main.__init__() returns and is garbage collected. You need to make it a member of main so that it persists for the lifetime of the class.
self.signal_records = Records.Records()
self.signal_records.atSig.connect(self.showNewData)
Alternatively, you could assign main as the parent of signal_records
signal_records = Records.Records(self)
Both methods ensure a reference to signal_records sticks around.
If It's possibly, please define this before caller to receive;
.
.
#Slot (int)
def showNewData(self, signal):
.
.
Or it not, Please check your caller def add_button_clicked(self). I work in pyqt4 (same pySide) and (I cut some part out and put some path for test in your) code, It's work.
import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
class QRecordsDialog (QtGui.QDialog):
addButtonClickedSignal = QtCore.pyqtSignal(int)
def __init__ (self, parent = None):
super(QRecordsDialog, self).__init__(parent)
self.myQPushButton = QtGui.QPushButton('Test Signal', self)
self.myQHBoxLayout = QtGui.QHBoxLayout()
self.myQHBoxLayout.addWidget(self.myQPushButton)
self.setLayout(self.myQHBoxLayout)
self.myQPushButton.clicked.connect(self.addButtonClicked)
def addButtonClicked (self):
self.addButtonClickedSignal.emit(1)
class QMainWindow (QtGui.QMainWindow):
def __init__ (self, parent = None):
super(QMainWindow, self).__init__(parent)
myQRecordsDialog = QRecordsDialog(self)
myQRecordsDialog.addButtonClickedSignal.connect(self.showNewData)
myQRecordsDialog.show()
#QtCore.pyqtSlot(int)
def showNewData (self, signal):
if signal == 1:
print "It worked !"
else:
print "Problem ?"
myQApplication = QtGui.QApplication(sys.argv)
myQMainWindow = QMainWindow()
myQMainWindow.show()
sys.exit(myQApplication.exec_())
If you want to modify your PyQt code to use the PySide naming scheme, that can be done using a simple definition:
QtCore.Signal = QtCore.pyqtSignal
QtCore.Slot = QtCore.pyqtSlot
Reference this
Regards,
Related
I am a beginner with PyQt5 and I am having trouble to use QtCore.signal
I'd like to send a signal when I press my buttons and switch the current widget displayed.
I don't have any errors when I run my code but when I press the buttons nothing happen and I guess it is because I am doing something wrong with the QtCore.Signal
Here is my code :
from PySide2 import QtCore, QtWidgets
from ui_Page_accueil import Ui_MainWindow
from ui_NouvelleVerif import Ui_Dialog as Ui_NouvelleVerif
from ui_NouvelleVerifEssieux import Ui_Dialog as Ui_NouvelleVerifEssieux
import sys
class MainWindowUi(Ui_MainWindow):
to_NouvelleVerif = QtCore.Signal()
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.setupUi(self)
#self.pushButton.clicked.connect(self.pushbutton_handler1)
self.pushButton_2.clicked.connect(self.pushbutton_handler2)
#def pushbutton_handler1(self):
# self.to_MainWindow.emit()
def pushbutton_handler2(self):
self.to_NouvelleVerif.emit()
class NouvelleVerifUi(QtWidgets.QWidget, Ui_NouvelleVerif):
to_MainWindow = QtCore.Signal()
to_NouvelleVerifEssieux = QtCore.Signal()
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.setupUi(self)
self.pushButton.clicked.connect(self.pushbutton_handler1)
#self.pushButton_2.clicked.connect(self.pushbutton_handler2)
self.pushButton_3.clicked.connect(self.pushbutton_handler3)
def pushbutton_handler1(self):
self.to_MainWindow.emit()
#def pushbutton_handler2(self):
# self.switch_window.emit()
def pushbutton_handler3(self):
self.to_NouvelleVerifEssieux.emit()
class NouvelleVerifEssieuxUi(QtWidgets.QWidget, Ui_NouvelleVerifEssieux):
to_NouvelleVerif = QtCore.Signal()
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.setupUi(self)
self.pushButton.clicked.connect(self.pushbutton_handler1)
def pushbutton_handler1(self):
self.to_NouvelleVerif.emit()
class Controller :
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QStackedWidget()
MainWindow = MainWindowUi()
NouvelleVerif = NouvelleVerifUi()
NouvelleVerifEssieux = NouvelleVerifEssieuxUi()
def __init__(self):
self.widget.addWidget(self.MainWindow) # create an instance of the first page class and add it to stackedwidget
self.widget.addWidget(self.NouvelleVerif) # adding second page
self.widget.addWidget(self.NouvelleVerifEssieux)
self.widget.setCurrentWidget(self.MainWindow) # setting the page that you want to load when application starts up. you can also use setCurrentIndex(int)
def show_MainWindow(self):
self.NouvelleVerif = NouvelleVerifUi()
self.NouvelleVerif.to_MainWindow.connect(self.show_MainWindow)
self.widget.setCurrentWidget(self.MainWindow)
def show_NouvelleVerif(self):
self.MainWindow = MainWindowUi()
self.NouvelleVerifEssieux = NouvelleVerifEssieuxUi()
self.MainWindow.to_NouvelleVerif.connect(self.show_NouvelleVerif)
self.NouvelleVerifEssieux.to_NouvelleVerif.connect(self.show_NouvelleVerif)
self.widget.setCurrentWidget(self.NouvelleVerif)
def show_NouvelleVerifEssieux(self):
self.NouvelleVerif = NouvelleVerifUi()
self.NouvelleVerif.to_NouvelleVerifEssieux.connect(self.show_NouvelleVerifEssieux)
self.widget.setCurrentWidget(self.NouvelleVerifEssieux)
def main():
controller = Controller()
controller.widget.show()
sys.exit(controller.app.exec_())
if __name__ == '__main__':
main()
#musicamante I don't know why I though the QtCore.Signal could be call even if he was in a function which has not been called. I did what you said and I realise I made another mistake with the widgets.
In the controller class they are create and add to the QStackedWidget in the __init__, but I was creating new ones and trying to set them as CurrentWidget without adding them to the QStackedWidget.
#alexpdev I wanted to navigate through my three differents with pushButton UI this way :
Start with MainWindowUI
MainWindowUI pushButton_2.clicked --> set the current widget display to NouvelleVerifUI
NouvelleVerifUI pushButton_1.clicked --> set the current widget display back to MainWindowUI
NouvelleVerifUI pushButton_3.clicked --> set the current widget display to NouvelleVerifEssieuxUI
NouvelleVerifEssieuxUI pushButton_1.clicked --> set the current widget display back to NouvelleVerifUI
Now everything work I did what you said #musicamante and I also use the UI created at first.
class Controller :
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QStackedWidget()
MainWindow = MainWindowUi()
NouvelleVerif = NouvelleVerifUi()
NouvelleVerifEssieux = NouvelleVerifEssieuxUi()
def __init__(self):
self.widget.addWidget(self.MainWindow) # create an instance of the first page class and add it to stackedwidget
self.widget.addWidget(self.NouvelleVerif) # adding second page
self.widget.addWidget(self.NouvelleVerifEssieux)
self.widget.setCurrentWidget(self.MainWindow) # setting the page that you want to load when application starts up. you can also use setCurrentIndex(int)
self.NouvelleVerif.to_MainWindow.connect(self.show_MainWindow)
self.MainWindow.to_NouvelleVerif.connect(self.show_NouvelleVerif)
self.NouvelleVerifEssieux.to_NouvelleVerif.connect(self.show_NouvelleVerif)
self.NouvelleVerif.to_NouvelleVerifEssieux.connect(self.show_NouvelleVerifEssieux)
def show_MainWindow(self):
self.widget.setCurrentWidget(self.MainWindow)
def show_NouvelleVerif(self):
self.widget.setCurrentWidget(self.NouvelleVerif)
def show_NouvelleVerifEssieux(self):
self.widget.setCurrentWidget(self.NouvelleVerifEssieux)
Thank you all for your time
I'm working in a data processing desktop application with Python 3.7 and PySide2 on which requires me to load data from several large (approx 250k rows) excel files into the program's processing library. For this I've set up in my application a simple popup (called LoadingPopup) which includes a rotating gif and a simple caption, and also some code that loads the data from the excel files into a global object using pandas. Both of these things work as intended when run on their own, but if I happen to create a loading dialog and a QRunnable worker in the same scope of my codebase, the widgets contained in loading widget (a gif and a simple caption) will simply not show.
I've tried changing the parent type for my widget from QDialog to QWidget, or initializing the popup (the start() function) both outside and inside the widget. I'm not very experienced with Qt5 so I don't know what else to do.
import sys, time, traceback
from PySide2.QtWidgets import *
from PySide2.QtCore import *
from PySide2.QtGui import *
from TsrUtils import PathUtils
class WorkerSignals(QObject):
finished = Signal()
error = Signal(tuple)
result = Signal(object)
class TsrWorker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(TsrWorker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
#Slot()
def run(self):
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(result)
finally:
self.signals.finished.emit()
class LoadingPopup(QWidget):
def __init__(self):
super().__init__()
self.message = "Cargando"
self.setMinimumSize(350,300)
self.setWindowIcon(\
QIcon(PathUtils.ruta_absoluta('resources/icons/tsr.png')))
self.setWindowTitle(self.message)
self.layout = QVBoxLayout(self)
self.setLayout(self.layout)
self.movie = QMovie(self)
self.movie.setFileName("./resources/img/spinner.gif")
self.movie.setCacheMode(QMovie.CacheAll)
self.movie.start()
self.loading = QLabel(self)
self.loading.setMovie(self.movie)
self.loading.setAlignment(Qt.AlignCenter)
self.layout.addWidget(self.loading)
self.lbl = QLabel(self.message, self)
self.lbl.setAlignment(Qt.AlignCenter)
self.lbl.setStyleSheet("font: 15pt")
self.layout.addWidget(self.lbl)
class MyMainApp(QApplication):
def __init__(self, args):
super().__init__()
self.l = LoadingPopup()
self.l.show()
w = TsrWorker(time.sleep, 5)
w.signals.finished.connect(self.terminado)
w.run()
def terminado(self):
print('timer finished')
self.l.hide()
if __name__ == '__main__':
app = MyMainApp(sys.argv)
sys.exit(app.exec_())
I've changed the actual data loading part of the application in the example with a time.sleep function. MY expected results are that I should be able to have the LoadingPopup show up with a gif moving, and then it should close once the QRunnable finishes.
You should not call the run method directly since you will have the heavy task run on the GUI thread freezing it. You must launch it using QThreadPool:
class MyMainApp(QApplication):
def __init__(self, args):
super().__init__()
self.l = LoadingPopup()
self.l.show()
w = TsrWorker(time.sleep, 5)
w.signals.finished.connect(self.terminado)
# w.run()
QThreadPool.globalInstance().start(w) # <---
def terminado(self):
print('timer finished')
self.l.hide()
I have developed a QGIS plugin using Pyqt. I now have a need within this plugin to have a QDialog popup have its fields populated with data from our database.
My problem is how to pass a variable (in this case it might be a table and row reference) into a class and have it used by the static method.
If I print a passed in variable from within the class it will return the variable in addition to None?? If I take the same variable and try and populate a qplaintextedit it won't work because its complaining of being type 'None'.
Here is some test code I'm trying out just to get the concept down...
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import pdb
class mydi(QDialog):
def __init__(self, pass_var, parent=None):
super(mydi, self).__init__(parent)
layout = QVBoxLayout(self)
self.pass_var = pass_var
print pass_var
self.txt_comments = QPlainTextEdit(self)
self.txt_comments.appendPlainText(pass_var)
layout.addWidget(self.txt_comments)
self.buttons = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
Qt.Horizontal, self)
layout.addWidget(self.buttons)
self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject)
def something(self):
return self.somevar
def comments(self):
return self.txt_comments.toPlainText()
#staticmethod
def getData(parent=None):
dialog = mydi(None)
dialog.exec_()
return (dialog.comments())
def main():
app = QApplication([])
pass_in_var = "test"
dia = mydi(pass_in_var)
data = dia.getData()
print data
I am trying to load some data which takes 30+ seconds. During this time I wish the user to see a small GUI which says "Loading .", then "Loading ..", then "Loading ...", then "Loading ." etc. I have done some reading and I think I have to put this in a separate thread. I found someone who had a similar problem suggesting the solution was this in the right spot:
t = threading.Thread(target=self.test)
t.daemon = True
t.start()
In a lower part of the file I have the test function
def test(self):
tmp = InfoMessage()
while True:
print(1)
and the InfoMessage function
from PyQt5 import uic, QtCore, QtGui, QtWidgets
import sys
class InfoMessage(QtWidgets.QDialog):
def __init__(self, msg='Loading ', parent=None):
try:
super(InfoMessage, self).__init__(parent)
uic.loadUi('ui files/InfoMessage.ui',self)
self.setWindowTitle(' ')
self.o_msg = msg
self.msg = msg
self.info_label.setText(msg)
self.val = 0
self.timer = QtCore.QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.update_message)
self.timer.start()
self.show()
except BaseException as e:
print(str(e))
def update_message(self):
self.val += 1
self.msg += '.'
if self.val < 20:
self.info_label.setText(self.msg)
else:
self.val = 0
self.msg = self.o_msg
QtWidgets.QApplication.processEvents()
def main():
app = QtWidgets.QApplication(sys.argv) # A new instance of QApplication
form = InfoMessage('Loading ') # We set the form to be our MainWindow (design)
app.exec_() # and execute the app
if __name__ == '__main__': # if we're running file directly and not importing it
main() # run the main function
When I run the InfoMessage function alone it works fine and it updates every 0.5 seconds etc. However, when I fun this as part of the loading file the GUI is blank and incorrectly displayed. I know it is staying in the test function because of the print statement in there.
Can someone point me in the right direction? I think I am missing a couple of steps.
First, there are two ways of doing this. One way is to use the Python builtin threading module. The other way is to use the QThread library which is much more integrated with PyQT. Normally, I would recommend using QThread to do threading in PyQt. But QThread is only needed when there is any interaction with PyQt.
Second, I've removed processEvents() from InfoMessage because it does not serve any purpose in your particular case.
Finally, setting your thread as daemon implies your thread will never stop. This is not the case for most functions.
import sys
import threading
import time
from PyQt5 import uic, QtCore, QtWidgets
from PyQt5.QtCore import QThread
def long_task(limit=None, callback=None):
"""
Any long running task that does not interact with the GUI.
For instance, external libraries, opening files etc..
"""
for i in range(limit):
time.sleep(1)
print(i)
if callback is not None:
callback.loading_stop()
class LongRunning(QThread):
"""
This class is not required if you're using the builtin
version of threading.
"""
def __init__(self, limit):
super().__init__()
self.limit = limit
def run(self):
"""This overrides a default run function."""
long_task(self.limit)
class InfoMessage(QtWidgets.QDialog):
def __init__(self, msg='Loading ', parent=None):
super(InfoMessage, self).__init__(parent)
uic.loadUi('loading.ui', self)
# Initialize Values
self.o_msg = msg
self.msg = msg
self.val = 0
self.info_label.setText(msg)
self.show()
self.timer = QtCore.QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.update_message)
self.timer.start()
def update_message(self):
self.val += 1
self.msg += '.'
if self.val < 20:
self.info_label.setText(self.msg)
else:
self.val = 0
self.msg = self.o_msg
def loading_stop(self):
self.timer.stop()
self.info_label.setText("Done")
class MainDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
# QThread Version - Safe to use
self.my_thread = LongRunning(limit=10)
self.my_thread.start()
self.my_loader = InfoMessage('Loading ')
self.my_thread.finished.connect(self.my_loader.loading_stop)
# Builtin Threading - Blocking - Do not use
# self.my_thread = threading.Thread(
# target=long_task,
# kwargs={'limit': 10}
# )
# self.my_thread.start()
# self.my_loader = InfoMessage('Loading ')
# self.my_thread.join() # Code blocks here
# self.my_loader.loading_stop()
# Builtin Threading - Callback - Use with caution
# self.my_loader = InfoMessage('Loading ')
# self.my_thread = threading.Thread(
# target=long_task,
# kwargs={'limit': 10,
# 'callback': self.my_loader}
# )
# self.my_thread.start()
def main():
app = QtWidgets.QApplication(sys.argv)
dialog = MainDialog()
app.exec_()
if __name__ == '__main__':
main()
Feel free to ask any follow up questions regarding this code.
Good Luck.
Edit:
Updated to show how to run code on thread completion. Notice the new parameter added to long_task function.
I'm using Qt's Graphics View Framework to display a large number of images, and implementing it using PyQt4 and Python 2.7. I instantiate a number of QPixmapItem objects and add them to my QGraphicsScene. It all works as I'd expect it to until I exit the application, and the program crashes instead of exiting normally.
class ImgDisplay(QtGui.QWidget):
NUM_ITEMS = 5000
VIEW_WIDTH,VIEW_HEIGHT = 400, 400
def __init__(self):
super(ImgDisplay, self).__init__()
self.scene = QtGui.QGraphicsScene(QtCore.QRectF(0,0,ImgDisplay.VIEW_WIDTH,ImgDisplay.VIEW_HEIGHT))
self.view = QtGui.QGraphicsView(self.scene)
self.view.setParent(self)
#Load the texture
self.texture = QtGui.QPixmap('image.png')
self.populateScene()
def populateScene(self):
for i in range(0, ImgDisplay.NUM_ITEMS-1):
item = QtGui.QGraphicsPixmapItem(self.texture)
self.scene.addItem(item)
I'm thinking that all those PixMapItems I'm creating aren't being cleaned up properly, or maybe I need to free the texture that I load (there doesn't seem to be a method to free it, so I assumed it happened in the background).
I've tried calling self.scene.clear in a destructor to delete the PixmapItems, but it didn't help.
Any suggestions on how I can fix this problem?
*I'm aware that the posted code just puts the images all on top of each other, my actual program assigns them random positions and rotations, but I wanted to reduce this to the minimal problem.
OK, understood. Problem is QtGui.QGraphicsPixmapItem can't clear itself your have to manual just like you says, but not destructor. I recommend doing after have signal close program by using closeEvent, like this;
def closeEvent (self, eventQCloseEvent):
self.scene.clear() # Clear QGraphicsPixmapItem
eventQCloseEvent.accept() # Accept to close program
and this implemented you code;
import sys
from PyQt4 import QtCore, QtGui
class ImgDisplay (QtGui.QWidget):
NUM_ITEMS = 5000
VIEW_WIDTH,VIEW_HEIGHT = 400, 400
def __init__ (self):
super(ImgDisplay, self).__init__()
self.scene = QtGui.QGraphicsScene(QtCore.QRectF(0,0,ImgDisplay.VIEW_WIDTH,ImgDisplay.VIEW_HEIGHT))
self.view = QtGui.QGraphicsView(self.scene)
self.view.setParent(self)
#Load the texture
self.texture = QtGui.QPixmap('image.png')
self.populateScene()
def populateScene (self):
for i in range(0, ImgDisplay.NUM_ITEMS-1):
item = QtGui.QGraphicsPixmapItem(self.texture)
self.scene.addItem(item)
def closeEvent (self, eventQCloseEvent):
self.scene.clear()
eventQCloseEvent.accept()
app = QtGui.QApplication([])
window = ImgDisplay()
window.show()
sys.exit(app.exec_())
I use PyQt4 (Windows 7).
And this useful to implement close event, Hope is helps;
QWidget Close Event Reference : http://pyqt.sourceforge.net/Docs/PyQt4/qwidget.html#closeEvent
LAST EDITED : 8 / 11 / 2014 01:51
If your want to control your parent & child widget to delete together, I have to implement destructor method (As your say). By use safe delete method QObject.deleteLater (self), Like this;
import sys
from PyQt4 import QtCore, QtGui
class ImgDisplay (QtGui.QWidget):
NUM_ITEMS = 5000
VIEW_WIDTH, VIEW_HEIGHT = 400, 400
def __init__ (self, parent = None):
super(ImgDisplay, self).__init__(parent)
self.scene = QtGui.QGraphicsScene(QtCore.QRectF(0,0,ImgDisplay.VIEW_WIDTH,ImgDisplay.VIEW_HEIGHT), parent = self)
self.view = QtGui.QGraphicsView(self.scene, parent = self)
#Load the texture
self.texture = QtGui.QPixmap('image.png')
self.populateScene()
def populateScene (self):
for i in range(0, ImgDisplay.NUM_ITEMS-1):
item = QtGui.QGraphicsPixmapItem(self.texture)
self.scene.addItem(item)
def __del__ (self):
self.deleteLater() # Schedules this object for deletion
app = QtGui.QApplication([])
window = ImgDisplay()
window.show()
sys.exit(app.exec_())
Warning : Don't forget set parent in your widget ! (Because some time It can't be delete itself.)
deleteLater Reference : http://pyqt.sourceforge.net/Docs/PyQt4/qobject.html#deleteLater
Regards,
I would like to highlight Josh's answer in the comments.
If you create the scene by setting the QGraphicsView as parent the crash will automatically go away.
self.view = QtGui.QGraphicsView(parent = self)
self.scene = QtGui.QGraphicsScene(QtCore.QRectF(0,0,ImgDisplay.VIEW_WIDTH,ImgDisplay.VIEW_HEIGHT), parent = self.view)
self.view.setScene(self.scene)