blocking signals to reach QObjects is fairly simple with using the QSignalBlocker Class
like
# functionality
self.clickbuton.clicked.connect(self.printsomething)
self.clickbuton.clicked.connect(self.blockprint)
def printsomething(self):
print("dude")
def blockprint(self):
self.clickbuton.blockSignals(True)
what about custom slots like def printsomething(self): ?
trying the same operation but with blocking def printsomething(self): from printing
def blockprint(self):
self.printsomething.blockSignals(True)
will give a AttributeError: 'function' object has no attribute 'blockSignals'
it looks like this method works only for QObjects
how can I block def printsomething(self): from printing without using disconnect while its connected to the clicked signal?
code example
"""
Testing Template for throw away experiment
"""
import sys
import os
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# widget
self.clickbuton = qtw.QPushButton("click me")
# set the layout
layout = qtw.QVBoxLayout()
layout.addWidget(self.clickbuton)
self.setLayout(layout)
# functionality
self.clickbuton.clicked.connect(self.printsomething)
self.clickbuton.clicked.connect(self.blockprint)
def printsomething(self):
print("dude")
def blockprint(self):
self.printsomething.blockSignals(True)
# self.m_blocker = qtc.QSignalBlocker(self.clickbuton)
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
What you're trying to do is to block a slot which is impossible.
You can only block signals of objects inherited from QObject.
As a solution you can instead of
def blockprint(self):
self.printsomething.blockSignals(True)
do this
def blockprint(self):
self.blockSignals(True)
that would block mainwindow signals until you turn blockSignals flag to false again.
Related
I have this Python 3.5.1 program with PyQt5 and a GUI created from a QtCreator ui file where the pyqtSlot decorator causes "TypeError: connect() failed between textChanged(QString) and edited()".
In the sample code to reproduce the problem I have 2 custom classes: MainApp and LineEditHandler. MainApp instantiates the main GUI (from the file "mainwindow.ui") and LineEditHandler handles a QLineEdit object. LineEditHandler reason to exist is to concentrate the custom methods that relate mostly to the QLineEdit object in a class. Its constructor needs the QLineEdit object and the MainApp instance (to access other objects/properties when needed).
In MainApp I connect the textChanged signal of the QLineEdit to LineEditHandler.edited(). If I don't decorate LineEditHandler.edited() with pyqtSlot() everything works fine. If I do use #pyqtSlot() for the method, the code run will fail with "TypeError: connect() failed between textChanged(QString) and edited()". What am I doing something wrong here?
You can get the mainwindow.ui file at: https://drive.google.com/file/d/0B70NMOBg3HZtUktqYVduVEJBN2M/view
And this is the sample code to generate the problem:
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import pyqtSlot
Ui_MainWindow, QtBaseClass = uic.loadUiType("mainwindow.ui")
class MainApp(QMainWindow, Ui_MainWindow):
def __init__(self):
# noinspection PyArgumentList
QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
# Instantiate the QLineEdit handler.
self._line_edit_handler = LineEditHandler(self, self.lineEdit)
# Let the QLineEdit handler deal with the QLineEdit textChanged signal.
self.lineEdit.textChanged.connect(self._line_edit_handler.edited)
class LineEditHandler:
def __init__(self, main_window, line_edit_obj):
self._line_edit = line_edit_obj
self._main_window = main_window
# FIXME The pyqtSlot decorator causes "TypeError: connect() failed between
# FIXME textChanged(QString) and edited()"
#pyqtSlot(name="edited")
def edited(self):
# Copy the entry box text to the label box below.
self._main_window.label.setText(self._line_edit.text())
def main():
app = QApplication(sys.argv)
window = MainApp()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Why do you want to use #pyqtSlot?
The reason it fails is that LineEditHandler is not a QObject. What #pyqtSlot does is basically creating a real Qt slot instead of internally using a proxy object (which is the default behavior without #pyqtSlot).
I don't know what was wrong, but I found a workaround: Connect the textChanged signal to a pyqtSlot decorated MainApp method that calls LineEditHandler.edited():
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import pyqtSlot
Ui_MainWindow, QtBaseClass = uic.loadUiType("mainwindow.ui")
class MainApp(QMainWindow, Ui_MainWindow):
def __init__(self):
# noinspection PyArgumentList
QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
# Instantiate the QLineEdit handler.
self._line_edit_handler = LineEditHandler(self, self.lineEdit)
self.lineEdit.textChanged.connect(self._line_edited)
#pyqtSlot(name="_line_edited")
def _line_edited(self):
self._line_edit_handler.edited()
class LineEditHandler:
def __init__(self, main_window, line_edit_obj):
self._line_edit = line_edit_obj
self._main_window = main_window
def edited(self):
# Copy the entry box text to the label box below.
self._main_window.label.setText(self._line_edit.text())
def main():
app = QApplication(sys.argv)
window = MainApp()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I am trying to get my stdout displayed on a QTextEdit made via Qt Designer (PyQt5). Actually I made it work yet it doesn't show the info at the same time it was made. Instead it waits for the process to completely end and only then it shows all the information at once. I understand that this should be solved via threading. Also since the QTextEdit (itself) is a GUI element i need a different approach. I found the answer I was looking for here:
This question is referenced to:
Redirecting stdout and stderr to a PyQt4 QTextEdit from a secondary thread
#three_pineapples provided the answer.
My question is pretty much exactly the same, thus the answer is also correct. But my scenario is a little different and I'm having trouble making it work.
In all the threading answers I only see them using Classes. But the thing is in my main class I have a function that does all the stuff that would be printed on the QTextEdit. Sometimes it takes minutes to complete. I am looking for a way for the example code below to work using the answer provided by #three_pineapples.
Here is the example code:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
class EmittingStream(QObject): # test
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten) # test
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_text.clicked.connect(self.test_write)
def __del__(self): # test
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text): # test
"""Append text to the QTextEdit."""
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def test_write(self): # this is a long, complicated function. its nested in this class. I don't have a way to get it out as a different class.
print("something written")
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Is there a way to get the provided solution to work -directly- on the (test_write) function in my main class? How would I implement it?
To make it more clear, from the reference link "LongRunningThing" class is not available for me. The function that needs to run on a separate thread is within the main class (named Form() in the example code). Perhaps a nested class could be used that encapsulates the test_write function inside my main class? Is that even possible?
For this case you can use the native threading of python:
import sys
import threading
import time
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
class EmittingStream(QObject): # test
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
finished = pyqtSignal()
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten) # test
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_text.clicked.connect(self.start_task)
self.finished.connect(lambda: self.ui.pushButton_text.setEnabled(True))
def start_task(self):
var = self.ui.lineEdit.text()
self.thread = threading.Thread(target=self.test_write, args=(args, ))
self.thread.start()
self.ui.pushButton_text.setEnabled(False)
def __del__(self): # test
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text): # test
"""Append text to the QTextEdit."""
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def test_write(self, *args):
var1 = args[0]
print("something written")
time.sleep(5) # simulate expensive task
print("something written ----")
self.finished.emit()
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I have multiple windows in a Python GUI application using PyQt5.
I need to hide current window when a button is clicked and show the next window.
This works fine from WindowA to WindowB but I get an error while going from WindowB to WindowC.
I know there is some problem in initialization as the initialization code in WindowB is unreachable, but being a beginner with PyQt, i can't figure out the solution.
WindowA code:
from PyQt5 import QtCore, QtGui, QtWidgets
from WindowB import Ui_forWindowB
class Ui_forWindowA(object):
def setupUi(self, WindowA):
# GUI specifications statements here
self.someButton = QtWidgets.QPushButton(self.centralwidget)
self.someButton.clicked.connect(self.OpenWindowB)
# More GUI specifications statements here
def retranslateUi(self, WindowA):
# More statements here
def OpenWindowB(self):
self.window = QtWidgets.QMainWindow()
self.ui = Ui_forWindowB()
self.ui.setupUi(self.window)
WindowA.hide()
self.window.show()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
WindowA = QtWidgets.QMainWindow()
ui = Ui_forWindowA()
ui.setupUi(WindowA)
MainWindow.show()
sys.exit(app.exec_())
WindowB code:
from PyQt5 import QtCore, QtGui, QtWidgets
from WindowB import Ui_forWindowB
class Ui_forWindowB(object):
def setupUi(self, WindowB):
# GUI specifications statements here
self.someButton = QtWidgets.QPushButton(self.centralwidget)
self.someButton.clicked.connect(self.OpenWindowC)
# More GUI specifications statements here
def retranslateUi(self, WindowB):
# More statements here
def OpenWindowB(self):
self.window = QtWidgets.QMainWindow()
self.ui = Ui_forWindowC()
self.ui.setupUi(self.window)
WindowB.hide() # Error here
self.window.show()
# The below code doesn't get executed when Ui_forWindowB is called from A
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
WindowB = QtWidgets.QMainWindow()
ui = Ui_forWindowB()
ui.setupUi(WindowB)
MainWindow.show()
sys.exit(app.exec_())
It works fine from A to B where
WindowA.hide() # Works Properly
While calling WindowC from WindowB
WindowB.hide() # Shows error: name 'WindowB' is not defined
I understand that the initialization isn't done as the "if" statement doesn't get executed.
How to get this working?
I have many more windows to connect in this flow
When you run a Python script, the first file executed will be assigned the name __main__, therefore, if you first execute WindowA the code inside the block if __name__ == "__main__" gets executed and the application is started using WindowA as the main window, similarly if you execute your WindowB script first, the application will be started usingWindowB as the main window.
You cannot start two applications within the same process so you have to choose which one you want to be the main window, all the others will be secondary windows (even if they inherit from QMainWindow).
Nevertheless, you should be able to instantiate new windows from a method in your main window.
As a good practice, you could create a main script to handle the initialization of your application and start an empty main window that will then handle your workflow, also, you may want to wrap your UI classes, specially if they are generated using Qt creator, here is an example:
main.py
import PyQt5
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication
from views.main_window import MainWindow
class App(QApplication):
"""Main application wrapper, loads and shows the main window"""
def __init__(self, sys_argv):
super().__init__(sys_argv)
# Show main window
self.main_window = MainWindow()
self.main_window.show()
if __name__ == '__main__':
app = App(sys.argv)
sys.exit(app.exec_())
main_window.py
This is the main window, it doesn't do anything, just control the workflow of the application, i.e. load WindowA, then WindowB etc., notice that I inherit from Ui_MainWindow, by doing so, you can separate the look and feel from the logic and use Qt Creator to generate your UI:
from PyQt5.QtWidgets import QWidget, QMainWindow
from views.window_a import WindowA
from views.window_b import WindowB
from widgets.main_window import Ui_MainWindow
class MainWindow(Ui_MainWindow, QMainWindow):
"""Main application window, handles the workflow of secondary windows"""
def __init__(self):
Ui_MainWindow.__init__(self)
QMainWindow.__init__(self)
self.setupUi(self)
# start hidden
self.hide()
# show window A
self.window_a = WindowA()
self.window_a.actionExit.triggered.connect(self.window_a_closed)
self.window_a.show()
def window_a_closed(self):
# Show window B
self.window_b = WindowB()
self.window_b.actionExit.triggered.connect(self.window_b_closed)
self.window_b.show()
def window_b_closed(self):
#Close the application if window B is closed
self.close()
window_a.py
from PyQt5.QtWidgets import QWidget, QMainWindow
from widgets.main_window import Ui_forWindowA
class WindowA(Ui_forWindowA, QMainWindow):
"""Window A"""
def __init__(self):
Ui_forWindowA.__init__(self)
QMainWindow.__init__(self)
self.setupUi(self)
# Do some stuff
window_b.py
from PyQt5.QtWidgets import QWidget, QMainWindow
from widgets.main_window import Ui_forWindowB
class WindowA(Ui_forWindowB, QMainWindow):
"""Window B"""
def __init__(self):
Ui_forWindowB.__init__(self)
QMainWindow.__init__(self)
self.setupUi(self)
# Do some stuff
Hopefully should give you an idea to get you going.
I've been trying to make this code work, but I still can't see where the flaw is.
I'm trying to emit the signal from a new thread, so the main receives the signal and executes a function.
If I try to do it within the same thread, everything works fine - but with this code, the thread is created, but the signal is never connected.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
print("From thread")
self.emit(QtCore.SIGNAL("trying"))
return
class Foo(QObject):
def handle_trigger(self):
print ("trigger signal received")
def new_thread(self):
self.get_thread = WorkThread()
self.connect(self.get_thread, QtCore.SIGNAL("trying"), self.handle_trigger)
self.get_thread.start()
a = Foo()
a.new_thread()
Edited based on comments.
There is one main problem with your code. You're not actually starting the Qt application, so there is no main event loop running. You must add the following lines:
app = QApplication(sys.argv)
app.exec_()
Also, you should use the new-style Qt signals/slots if possible, or if you stick with the old-style, know that the Qt Signal should be in the form of a C function if you want it to also work with PySide. To change this to work with PySide, it would be QtCore.SIGNAL("trying()"), not QtCore.SIGNAL("trying"). See the comments (specifically mine and #ekhumoro's comments) for details.
Here's a working version of your code (using the old-style signals/slots), I tried to change the least amount possible so that you could see the small changes. I had to use PySide instead, but it should work with PyQt as well:
from PySide.QtCore import *
from PySide.QtGui import *
from PySide import QtCore
import sys
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
print("From thread")
self.emit(QtCore.SIGNAL("trying()"))
class Foo(QObject):
def handle_trigger(self):
print ("trigger signal received")
self.get_thread.quit()
self.get_thread.wait()
QApplication.quit()
def new_thread(self):
self.get_thread = WorkThread()
self.connect(self.get_thread, QtCore.SIGNAL("trying()"), self.handle_trigger)
self.get_thread.start()
a = Foo()
a.new_thread()
app = QApplication(sys.argv)
app.exec_()
And here's a version using the new signal/slot style (see #three_pineapples comment). This is the recommended way to implement signals/slots in PyQt/PySide.
from PySide.QtCore import *
from PySide.QtGui import *
from PySide import QtCore
import sys
class WorkThread(QtCore.QThread):
ranThread = QtCore.Signal()
# for PyQt, use QtCore.pyqtSignal() instead
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
print("From thread")
self.ranThread.emit()
class Foo(QObject):
def handle_trigger(self):
print ("trigger signal received")
self.get_thread.quit()
self.get_thread.wait()
QApplication.quit()
def new_thread(self):
self.get_thread = WorkThread()
self.get_thread.ranThread.connect(self.handle_trigger)
self.get_thread.start()
a = Foo()
a.new_thread()
app = QApplication(sys.argv)
app.exec_()
I muddled through this myself and I think this will work for you.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
tryThis = QtCore.Signal(str) #added this variable
def run(self):
print("From thread")
x = "trying"
self.tryThis.emit(QtCore.SIGNAL(x)) #pass your new variable like this
return
class Foo(QObject):
def handle_trigger(self):
print ("trigger signal received")
def new_thread(self):
self.get_thread = WorkThread()
self.get_thread.start()
self.get_thread.tryThis.connect(self.handle_trigger,QtCore.Qt.QueuedConnection) #passed it here
a = Foo()
a.new_thread()
I want to use signals for communicating between my view and my application controller. I have following approach but since I'm beginner in PyQt I don't know if that is the right one. Can anyone tell me If I am on the right path or are there better solutions?
EDIT: I have changed the example to a fully working example.
import sys
from PyQt4 import QtGui, QtCore
class View(QtGui.QMainWindow):
sigFooChanged = QtCore.pyqtSignal()
sigBarChanged = QtCore.pyqtSignal()
def __init__(self):
QtGui.QMainWindow.__init__(self)
central_widget = QtGui.QWidget()
central_layout = QtGui.QHBoxLayout()
self.__cbFoo = QtGui.QComboBox()
self.__cbBar = QtGui.QComboBox()
self.__cbFoo.currentIndexChanged[str].connect(lambda x: self.sigFooChanged.emit())
self.__cbBar.currentIndexChanged[str].connect(lambda x: self.sigBarChanged.emit())
central_layout.addWidget(QtGui.QLabel("Foo:"))
central_layout.addWidget(self.__cbFoo)
central_layout.addWidget(QtGui.QLabel("Bar:"))
central_layout.addWidget(self.__cbBar)
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)
def setFooModel(self, model):
self.__cbFoo.setModel(model)
def setBarModel(self, model):
self.__cbBar.setModel(model)
class Controller:
def __init__(self, view):
self.__view = view
# Connect all signals from view with according handlers
self.__view.sigFooChanged.connect(self.handleFooChanged)
self.__view.sigBarChanged.connect(self.handleBarChanged)
self.__fooModel = QtGui.QStringListModel(["Foo1", "Foo2", "Foo3"])
self.__barModel = QtGui.QStringListModel(["Bar1", "Bar2", "Bar3"])
self.__view.setFooModel(self.__fooModel)
self.__view.setBarModel(self.__barModel)
def handleFooChanged(self):
print("Foo Changed")
def handleBarChanged(self):
print("Bar Changed")
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
view = View()
controller = Controller(view)
view.show()
sys.exit(app.exec_())
Personally, I don't create a separate generic controller class like that. It could be my own preference, but I tend to consider the actual QWidget class my controller, and the view is usually the GUI-only definitions that I generate from QtDesigner (Ui_Dialog for example), or create manually. And I make all my connections in the relevant QWidget.
Now on to your code, I don't know if you are just considering this snippet a general pseudocode example of the direction you are taking...but it has errors... I would normally suggest posting working code so people don't get confused as to whether you are having errors because of it, or just asking if its generally a correct direction to laying out code.
You are forgetting to call __init__() on the QMainWindow superclass.
I'm not sure what controller.show() would do (fail as of right now) because I don't see an example of how you intend to forward that show() command to your main window object? Again I don't really see why its even necessary to have that separate class.
Here is how I would see a more realistic example, again considering the QWidget classes themselves to be the controllers:
View
## mainUI.py ##
from PyQt4 import QtCore, QtGui
class Ui_MyWidget(object):
def setupUi(self, obj):
obj.layout = QtGui.QVBoxLayout(obj)
obj.cbFoo = QtGui.QComboBox()
obj.cbBar = QtGui.QComboBox()
obj.layout.addWidget(obj.cbFoo)
obj.layout.addWidget(obj.cbBar)
Non-Gui Library Module (Controller)
## nonGuiModule.py ##
class LibModule(object):
def handleBarChanged(self, *args):
print("Bar Changed: %s" % args)
Controller (any entry point)
## main.py ##
import sys
from PyQt4 import QtCore, QtGui
from mainUI import Ui_MyWidget
from nonGuiModule import LibModule
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.resize(640,480)
self._lib = LibModule()
self.myWidget = MyWidget(self)
self.setCentralWidget(self.myWidget)
self.myWidget.sigFooChanged.connect(self.handleFooChanged)
self.myWidget.sigBarChanged.connect(self._lib.handleBarChanged)
def handleFooChanged(self, *args):
print("Foo Changed: %s" % args)
class MyWidget(QtGui.QFrame, Ui_MyWidget):
sigFooChanged = QtCore.pyqtSignal(str)
sigBarChanged = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
# this is where you set up from the view
self.setupUi(self)
self.cbFoo.addItems(['Foo1', 'Foo2'])
self.cbBar.addItems(['Bar1', 'Bar2'])
self.layout.addWidget(self.cbFoo)
self.layout.addWidget(self.cbBar)
# going to forward private signals to public signals
self.cbFoo.currentIndexChanged[str].connect(self.sigFooChanged)
self.cbBar.currentIndexChanged[str].connect(self.sigBarChanged)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv[1:])
view = Main()
view.show()
sys.exit(app.exec_())