Signals and Missing Positional Arguments - python

I developed two windows in QtDesigner (SourceForm, DestinationForm) and used pyuic5 to convert their .ui pages. I am using a third class WController as a way to navigate between the two windows using a stacked widget. I have a button in SourceForm that populates treeWidget with some data and the method handle_treewidget_itemchange dictates what happens when a particular item in treeWidget gets checked or unchecked by using self.treeWidget.itemChanged.connect(self.handle_treewidget_itemchange). It was my understanding that itemChanged.connect would automatically send the row and column of what was changed to the slot but when handle_treewidget_itemchange(self,row,col) gets called for the first time, my script crashes with a TypeError:
TypeError: handle_treewidget_itemchange() missing 2 required positional arguments: 'row' and 'col'
If I take out the row and col args, the script runs fine. When I originally had both the method and the call in the SourceForm .py file itself, my code worked as intended...maybe this is just a scope issue? I am beginning to think attempting to use PyQt while still inexperienced with Python a bad idea :(
I've tried to strip the code down to the essentials:
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import pyqtSlot
from imp_sourceform import Ui_SourceForm
from imp_destform import Ui_DestinationForm
class WController(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(WController, self).__init__(parent)
self.central_widget = QtWidgets.QStackedWidget()
self.setCentralWidget(self.central_widget)
self.sourcewindow = SourceForm()
self.destinationwindow = DestinationForm()
self.central_widget.addWidget(self.sourcewindow)
self.central_widget.addWidget(self.destinationwindow)
self.central_widget.setCurrentWidget(self.sourcewindow)
self.sourcewindow.selectdestinationsbutton.clicked.connect(lambda: self.navigation_control(1))
self.destinationwindow.backbutton.clicked.connect(lambda: self.navigation_control(0))
def navigation_control(self, topage):
if topage == 1:
self.central_widget.setCurrentWidget(self.destinationwindow)
elif topage == 0:
self.central_widget.setCurrentWidget(self.sourcewindow)
class SourceForm(QtWidgets.QWidget, Ui_SourceForm):
def __init__(self):
super(SourceForm, self).__init__()
self.setupUi(self)
self.treeWidget.itemChanged.connect(self.handle_treewidget_itemchange)
#pyqtSlot()
def handle_treewidget_itemchange(self,row,col):
if row.parent() is None and row.checkState(col) == QtCore.Qt.Unchecked:
for x in range(0,row.childCount()):
row.child(x).setCheckState(0, QtCore.Qt.Unchecked)
elif row.parent() is None and row.checkState(col) == QtCore.Qt.Checked:
for x in range(0,row.childCount()):
row.child(x).setCheckState(0, QtCore.Qt.Checked)
else:
pass
class DestinationForm(QtWidgets.QWidget, Ui_DestinationForm):
def __init__(self):
super(DestinationForm, self).__init__()
self.setupUi(self)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = WController()
window.show()
sys.exit(app.exec_())

You need to be careful when using pyqtSlot, as it is only too easy to clobber the signature of the slot it is decorating. In your case, it has re-defined the slot as having no arguments, which explains why you are getting that error message. The simple fix is to simply remove it, as your example will work perfectly well without it.
The main purpose of pyqtSlot is to allow several different overloads of a slot to be defined, each with a different signature. It may also be needed sometimes when making cross-thread connections. However, these use-cases are relatively rare, and in most PyQt/PySide applications it is not necessary to use pyqtSlot at all. Signals can be connected to any python callable object, whether it is decorated as a slot or not.

There's already an accepted answer to this question but I'll give mine anyway.
The problematic slot is connected to the itemChanged(QTreeWidgetItem *item, int column) signal, so the pyqtSlot should look like #pyqtSlot(QTreeWidgetItem, int).
Now, as ekhumoro pointed out, PyQt accepts to connect a signal to any Python callable, be it a method, a lambda, or an functor having a __call__ method. But it's less safe to do so rather than use #pyqtSlot.
For example, Qt automatically disconnects when either the source QObject (who would emit the signal) is destroyed, or the target QObject (who has the Qt slot) is destroyed. For example, if you remove a widget, it's not necessary to signal to it that something happened elsewhere. If you use #pyqtSlot, a real Qt slot is created in your class, so this disconnection mechanism can apply. Also, Qt doesn't hold a strong reference to the target QObject, so it can be deleted.
If you use any callable, for example a non-decorated, bound method, Qt will have no way to identify the target QObject of the connection. Worse, since you pass a Python callable, it will hold a strong reference to it, and the callable (the bound method) will in turn hold a reference to the final QObject, so your target QObject will not be garbage collected, until you manually disconnect it, or remove the source QObject.
See this code, you can enable one connection or the other, and observe the difference in behavior, which shows whether the window can be garbage-collected or not:
from PyQt5.QtWidgets import QApplication, QMainWindow
app = QApplication([])
def create_win():
win = QMainWindow()
win.show()
# case 1
# app.aboutToQuit.connect(win.repaint) # this is a qt slot, so win can be deleted
# case 2
# app.aboutToQuit.connect(win.size) # this is not a qt slot, so win can't be deleted
# win should get garbage-collected here
create_win()
app.exec_()

Related

PyQt5: Closing / relaunching application causes seg fault [duplicate]

The following is a loop that I created:
import mainui
import loginui
from PyQt5 import QtWidgets
import sys
while True:
print('test')
app = QtWidgets.QApplication(sys.argv)
ui = loginui.Ui_MainWindow()
ui.setupUi()
ui.MainWindow.show()
app.exec_()
username=ui.username
app2 = QtWidgets.QApplication(sys.argv)
ui2 = mainui.Ui_MainWindow(username)
ui2.setupUi()
ui2.MainWindow.show()
app2.exec_()
if ui2.exitFlag=='repeat':#Repeat Condition
continue
else: #Exit Condition
sys.exit()
This is a loop containing a couple of PyQt5 windows, which are displayed in order. The windows run normally when they are not contained within a loop, and they also run pretty well in the first iteration of the loop.
But, when the repeat condition is satisfied, even though the loop does iterate (prints 'test' again) - the ui and ui2 windows do not get displayed again, and subsequently the program hits the exit condition and stops.
Any suggestions about why the windows do not get displayed, and how I can get them displayed would be very much appreciated.
An important premise: usually you need only one QApplication instance.
Proposed solutions
In the following examples I'm using a single QApplication instance, and switch between windows using signals.
Since you probably need to wait for the window to be closed in some way, you might prefer to use a QDialog instead of a QMainWindow, but if for some reason you need the features provided by QMainWindow (menus, dockbars, etc) this is a possible solution:
class First(QtWidgets.QMainWindow):
closed = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
self.setCentralWidget(central)
layout = QtWidgets.QHBoxLayout(central)
button = QtWidgets.QPushButton('Continue')
layout.addWidget(button)
button.clicked.connect(self.close)
def closeEvent(self, event):
self.closed.emit()
class Last(QtWidgets.QMainWindow):
shouldRestart = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
self.setCentralWidget(central)
layout = QtWidgets.QHBoxLayout(central)
restartButton = QtWidgets.QPushButton('Restart')
layout.addWidget(restartButton)
closeButton = QtWidgets.QPushButton('Quit')
layout.addWidget(closeButton)
restartButton.clicked.connect(self.restart)
closeButton.clicked.connect(self.close)
def restart(self):
self.exitFlag = True
self.close()
def showEvent(self, event):
# ensure that the flag is always false as soon as the window is shown
self.exitFlag = False
def closeEvent(self, event):
if self.exitFlag:
self.shouldRestart.emit()
app = QtWidgets.QApplication(sys.argv)
first = First()
last = Last()
first.closed.connect(last.show)
last.shouldRestart.connect(first.show)
first.show()
sys.exit(app.exec_())
Note that you can add menubars to a QWidget too, by using setMenuBar(menuBar) on their layout.
On the other hand, QDialogs are more indicated for these cases, as they provide their exec_() method which has its own event loop and blocks everything else until the dialog is closed.
class First(QtWidgets.QDialog):
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
button = QtWidgets.QPushButton('Continue')
layout.addWidget(button)
button.clicked.connect(self.accept)
class Last(QtWidgets.QDialog):
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
restartButton = QtWidgets.QPushButton('Restart')
layout.addWidget(restartButton)
closeButton = QtWidgets.QPushButton('Quit')
layout.addWidget(closeButton)
restartButton.clicked.connect(self.accept)
closeButton.clicked.connect(self.reject)
def start():
QtCore.QTimer.singleShot(0, first.exec_)
app = QtWidgets.QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
first = First()
last = Last()
first.finished.connect(last.exec_)
last.accepted.connect(start)
last.rejected.connect(app.quit)
start()
sys.exit(app.exec_())
Note that in this case I had to use a QTimer to launch the first dialog. This is due to the fact that in normal conditions signals wait for theirs slot to be completed before returning control to the emitter (the dialog). Since we're constantly recalling the same dialog, this leads to recursion:
First is executed
First is closed, emitting the finished signal, which causes the following:
Second is executed
at this point the finished signal has not returned yet
Second is accepted, emitting the accepted signal, which causes:
First hasn't returned its exec_() yet, but we're trying to exec it again
Qt crashes showing the error StdErr: QDialog::exec: Recursive call detected
Using a QTimer.singleShot ensures that the signal returns instantly, avoiding any recursion for exec_().
Ok, but why doesn't it work?
As said, only one Q[*]Application instance should usually exists for each process. This doesn't actually prevent to create more instances subsequently: in fact, your code works while it's in the first cycle of the loop.
The problem is related to python garbage collection and how PyQt and Qt deals with memory access to the C++ Qt objects, most importantly the application instance.
When you create the second QApplication, you're assigning it to a new variable (app2). At that point, the first one still exists, and will be finally deleted (by Qt) as soon as the process is completed with sys.exit.
When the cycle restarts, instead, you're overwriting app, which would normally cause python to garbage collect the previous object as soon as possible.
This represents a problem, as Python and Qt need to do "their stuff" to correctly delete an existing QApplication object and the python reference.
If you put the following line at the beginning, you'll see that the first time the instance is returned correctly, while the second returns None:
app = QtWidgets.QApplication(sys.argv)
print('Instance: ', QtWidgets.QApplication.instance())
There's a related question here on StackOverflow, and an important comment to its answer:
In principle, I don't see any reason why multiple instances of QApplication cannot be created, so long as no more than one exists at the same time. In fact, it may often be a requirement in unit-testing that a new application instance is created for each test. The important thing is to ensure that each instance gets deleted properly, and, perhaps more importantly, that it gets deleted at the right time.
A workaround to avoid the garbage collection is to add a persistent reference to the app:
apps = []
while True:
print('test')
app = QtWidgets.QApplication(sys.argv)
apps.append(app)
# ...
app2 = QtWidgets.QApplication(sys.argv)
apps.append(app2)
But, as said, you should not create a new QApplication instance if you don't really need that (which is almost never the case).
As already noted in the comments to the question, you should never modify the files generated with pyuic (nor try to mimic their behavior). Read more about using Designer.

How do I open (and close) a PyQt5 application inside a loop, and get that loop running multiple times

The following is a loop that I created:
import mainui
import loginui
from PyQt5 import QtWidgets
import sys
while True:
print('test')
app = QtWidgets.QApplication(sys.argv)
ui = loginui.Ui_MainWindow()
ui.setupUi()
ui.MainWindow.show()
app.exec_()
username=ui.username
app2 = QtWidgets.QApplication(sys.argv)
ui2 = mainui.Ui_MainWindow(username)
ui2.setupUi()
ui2.MainWindow.show()
app2.exec_()
if ui2.exitFlag=='repeat':#Repeat Condition
continue
else: #Exit Condition
sys.exit()
This is a loop containing a couple of PyQt5 windows, which are displayed in order. The windows run normally when they are not contained within a loop, and they also run pretty well in the first iteration of the loop.
But, when the repeat condition is satisfied, even though the loop does iterate (prints 'test' again) - the ui and ui2 windows do not get displayed again, and subsequently the program hits the exit condition and stops.
Any suggestions about why the windows do not get displayed, and how I can get them displayed would be very much appreciated.
An important premise: usually you need only one QApplication instance.
Proposed solutions
In the following examples I'm using a single QApplication instance, and switch between windows using signals.
Since you probably need to wait for the window to be closed in some way, you might prefer to use a QDialog instead of a QMainWindow, but if for some reason you need the features provided by QMainWindow (menus, dockbars, etc) this is a possible solution:
class First(QtWidgets.QMainWindow):
closed = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
self.setCentralWidget(central)
layout = QtWidgets.QHBoxLayout(central)
button = QtWidgets.QPushButton('Continue')
layout.addWidget(button)
button.clicked.connect(self.close)
def closeEvent(self, event):
self.closed.emit()
class Last(QtWidgets.QMainWindow):
shouldRestart = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
self.setCentralWidget(central)
layout = QtWidgets.QHBoxLayout(central)
restartButton = QtWidgets.QPushButton('Restart')
layout.addWidget(restartButton)
closeButton = QtWidgets.QPushButton('Quit')
layout.addWidget(closeButton)
restartButton.clicked.connect(self.restart)
closeButton.clicked.connect(self.close)
def restart(self):
self.exitFlag = True
self.close()
def showEvent(self, event):
# ensure that the flag is always false as soon as the window is shown
self.exitFlag = False
def closeEvent(self, event):
if self.exitFlag:
self.shouldRestart.emit()
app = QtWidgets.QApplication(sys.argv)
first = First()
last = Last()
first.closed.connect(last.show)
last.shouldRestart.connect(first.show)
first.show()
sys.exit(app.exec_())
Note that you can add menubars to a QWidget too, by using setMenuBar(menuBar) on their layout.
On the other hand, QDialogs are more indicated for these cases, as they provide their exec_() method which has its own event loop and blocks everything else until the dialog is closed.
class First(QtWidgets.QDialog):
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
button = QtWidgets.QPushButton('Continue')
layout.addWidget(button)
button.clicked.connect(self.accept)
class Last(QtWidgets.QDialog):
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
restartButton = QtWidgets.QPushButton('Restart')
layout.addWidget(restartButton)
closeButton = QtWidgets.QPushButton('Quit')
layout.addWidget(closeButton)
restartButton.clicked.connect(self.accept)
closeButton.clicked.connect(self.reject)
def start():
QtCore.QTimer.singleShot(0, first.exec_)
app = QtWidgets.QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
first = First()
last = Last()
first.finished.connect(last.exec_)
last.accepted.connect(start)
last.rejected.connect(app.quit)
start()
sys.exit(app.exec_())
Note that in this case I had to use a QTimer to launch the first dialog. This is due to the fact that in normal conditions signals wait for theirs slot to be completed before returning control to the emitter (the dialog). Since we're constantly recalling the same dialog, this leads to recursion:
First is executed
First is closed, emitting the finished signal, which causes the following:
Second is executed
at this point the finished signal has not returned yet
Second is accepted, emitting the accepted signal, which causes:
First hasn't returned its exec_() yet, but we're trying to exec it again
Qt crashes showing the error StdErr: QDialog::exec: Recursive call detected
Using a QTimer.singleShot ensures that the signal returns instantly, avoiding any recursion for exec_().
Ok, but why doesn't it work?
As said, only one Q[*]Application instance should usually exists for each process. This doesn't actually prevent to create more instances subsequently: in fact, your code works while it's in the first cycle of the loop.
The problem is related to python garbage collection and how PyQt and Qt deals with memory access to the C++ Qt objects, most importantly the application instance.
When you create the second QApplication, you're assigning it to a new variable (app2). At that point, the first one still exists, and will be finally deleted (by Qt) as soon as the process is completed with sys.exit.
When the cycle restarts, instead, you're overwriting app, which would normally cause python to garbage collect the previous object as soon as possible.
This represents a problem, as Python and Qt need to do "their stuff" to correctly delete an existing QApplication object and the python reference.
If you put the following line at the beginning, you'll see that the first time the instance is returned correctly, while the second returns None:
app = QtWidgets.QApplication(sys.argv)
print('Instance: ', QtWidgets.QApplication.instance())
There's a related question here on StackOverflow, and an important comment to its answer:
In principle, I don't see any reason why multiple instances of QApplication cannot be created, so long as no more than one exists at the same time. In fact, it may often be a requirement in unit-testing that a new application instance is created for each test. The important thing is to ensure that each instance gets deleted properly, and, perhaps more importantly, that it gets deleted at the right time.
A workaround to avoid the garbage collection is to add a persistent reference to the app:
apps = []
while True:
print('test')
app = QtWidgets.QApplication(sys.argv)
apps.append(app)
# ...
app2 = QtWidgets.QApplication(sys.argv)
apps.append(app2)
But, as said, you should not create a new QApplication instance if you don't really need that (which is almost never the case).
As already noted in the comments to the question, you should never modify the files generated with pyuic (nor try to mimic their behavior). Read more about using Designer.

Qt canonical way of retrieving values from Wizard / Dialog on accepted / finished signal

I'm using PyQt, but I guess the same questions also applies to Qt C++.
Assume that I have a main window with a button that opens a wizard that collects data and that data needs to be used in the main window after the wizard has closed. standard procedure.
So there are multiple ways to do this. either I can pass a reference to the main window to the Wizard and it does all the work using the main window reference, but I'd say that breaks modularity. I can also wire up a callback to the wizard accepted rejected or finished signal, but in that callback, I don't have a reference to the wizard itself, so I cannot get to the data in the wizards fields. Unless I store a reference to the wizard as instance variable in order to access it again from the callback.
Another option is (even though I haven't fully understood it yet) to get a reference to the emitter of the signal (i.e. the wizard) in the callback using https://doc.qt.io/qt-5/qobject.html#sender. But that seems not recommended.
So whats the canonical way?
Premise: this is a bit of an opinion based question, as there is not one and only "good" way to do that. I just wanted to comment (opinion based answer/questions are discouraged here in SO), but the limited formatting isn't very helpful.
"Passing a reference" doesn't necessarily break modularity.
Instead, that's exactly what QDialog usually are initialized: the parent is the "calling" window, which is also how a QDialog can be "modal" to the parent or the whole application (meaning that no interaction outside the dialog is allowed as long as it is active).
AFAIK, I don't know if this is actually considered canonical, but the following is the most commonly suggested approach.
The idea is that you have a child object (a QDialog, usually) which might or might not be initialized everytime you need it, that's up to you; the important part is that you need a reference to it at least for the time required to update its result, which can even happen within the scope of a single method/slot.
from PyQt5 import QtWidgets
class MyWizard(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self.checkBox = QtWidgets.QCheckBox('check')
layout.addWidget(self.checkBox)
self.input = QtWidgets.QLineEdit()
layout.addWidget(self.input)
buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Cancel)
layout.addWidget(buttonBox)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
def setData(self, **data):
self.checkBox.setChecked(data.get('check', False))
self.input.setText(data.get('text', ''))
def getData(self):
return {'check': self.checkBox.isChecked(), 'text': self.input.text()}
def exec_(self, **data):
self.setData(**data)
return super().exec_()
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
centralWidget = QtWidgets.QWidget()
self.setCentralWidget(centralWidget)
layout = QtWidgets.QHBoxLayout()
centralWidget.setLayout(layout)
self.showWizBtn = QtWidgets.QPushButton('Show wizard')
layout.addWidget(self.showWizBtn)
self.showWizBtn.clicked.connect(self.getDataFromWizard)
self.data = {}
def getDataFromWizard(self):
wiz = MyWizard(self)
if wiz.exec_(**self.data):
self.data.update(wiz.getData())
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
Another possibility is to create a persistent child dialog (but keep in mind that if the data can be changed by the parent, you'll have to find a way to update it, at least when executed); the concept here is that you can exec the dialog whenever you need, and you have the accepted signal connected to a slot that can get the data from the dialog. This is not a common use (nor very suggested IMHO) and should be used only for very specific scenarios.
As you already found out, using sender is not suggested: signals are asynchronous, and while in normal conditions the sender is reliable it's better to avoid using it unless absolutely necessary.

What effect will exist when using any 'return' statement inside Qt's slot

For instance, I connect the 'clicked' signal of QPushButton to a function named 'func_with_return'. Assumes that there are just three statements in this function: The first one is 'print('start')', the second one is 'return 1' and the last one is 'print('end')'. There is my python code based on PyQt5.
import sys
from PyQt5.QtWidgets import QApplication, QFrame, QPushButton
class MyWindow(QFrame):
def __init__(self):
super(MyWindow, self).__init__()
self.layout_init()
self.layout_manage()
def layout_init(self):
self.setFixedSize(800, 600)
self.button01 = QPushButton('click!', self)
self.button01.setFixedSize(100, 100)
self.button01.clicked.connect(self.func_with_return)
def layout_manage(self):
pass
def func_with_return(self):
print('---------func_with_return starts---------')
return 1
print('---------func_with_return ends---------')
if __name__ == '__main__':
app = QApplication(sys.argv)
mywindow = MyWindow()
mywindow.show()
sys.exit(app.exec_())
Basically, there is no error after clicking on this button. What I am curious about is the interruption caused by 'return' inside a 'slot'. Will this interruption have collision with the signal&slot mechanism?
None. The signals only invoke the function, if the function returns Qt will not use it.
On the other hand in Qt/PyQt it is said that a function is a slot if you use the decorator #QtCore.pyqtSlot(). In your case it is a simple function. Even so for a signal will not serve the data that returns the slot or function invoked.
Will this interruption have collision with the signal&slot mechanism?
No, it does not have a collision. Returning to the beginning, middle or end is irrelevant, remember every function returns something (if you do not use return the function will implicitly return None at the end).
On the other hand in a GUI the tasks of the functions must be light, if they are heavy you must execute it in another thread.

PyQt Multiple Windows - how to pass functions between modules Qt Designer

I'll start with the question and then try to explain:
Is there a way for an imported module to call a function in the module that imports it?
I am just learning to use Qt and am starting with Qt Designer to get some fundamentals worked out.
I have figured out how to create more than one ".ui" file in order to get the code for multiple windows and have managed to work out how to call the multiple windows from a main application by importing the code for the two windows.
For example, starting with win1.ui and win2.ui I create win1.py and win2.py - from my main application I import win1 and win2...
Note - I got this far by following this simple tutorial : http://www.youtube.com/watch?v=bHsC6WJsK-U&list=PLF4575388795F2531&index=10&feature=plpp_video
OK - now the question. If I have a button in win2, I know how to link that button to a function in the win2.py code. What I don't know how to do is link the button in win2 to a function in my main application.
My only thought would be to add a function as an argument to the class that sets up the second window but if I do that then any changes to win2.ui will wreck the code that I have changed.
Thus, Is there a way for an imported module to call a function in the module that imports it?
I hope this is clear without adding a bunch of code that isn't really relevant...
Qt is based on event-driven programming. Generally when you start building up your widgets, what you are going to be wanting to do is providing information to receiver widgets via signals that are then processed. You don't want to explicitly have a child widget know or require to call methods on a parent widget (this is not always the case, but it is good to avoid when possible).
I'm gonna post some examples that don't have UI files for ease here, but just assume you can build the same widget with designer and have it work the same way...
testwidget.py
from PyQt4 import QtGui, QtCore
class TestWidget(QtGui.QWidget):
textSaved = QtCore.pyqtSignal(str)
def __init__( self, parent = None ):
super(TestWidget, self).__init__(parent)
# create the ui (or load it)
self.__edit = QtGui.QTextEdit(self)
self.__button = QtGui.QPushButton(self)
self.__button.setText('Save')
layout = QtGui.QVBoxLayout()
layout.addWidget(self.__edit)
layout.addWidget(self.__button)
self.setLayout(layout)
# create connections
self.__button.clicked.connect(self.emitTextSaved)
def emitTextSaved( self ):
# allow Qt's blocking of signals paradigm to control flow
if ( not self.signalsBlocked() ):
self.textSaved.emit(self.__edit.toPlainText())
testwindow.py
from PyQt4 import QtGui, QtCore
import testwidget
class TestWindow(QtGui.QMainWindow):
def __init__( self, parent == None ):
super(TestWindow, self).__init__(parent)
# create the ui (or load it)
self.__editor = testwidget.TestWidget(self)
self.setCentralWidget(self.__editor)
# create connections
self.__editor.textSaved.connect(self.showMessage)
def showMessage( self, message ):
QtGui.QMessageBox.information(self, 'Message', message)
So, here you can see that instead of thinking about it like - "when I click the button in TestWidget, I want to show a message in TestWindow" and explicitly link the two methods, you expose a signal that the TestWidget will emit out when the user performs an action, then connect that signal to the showMessage slot of the TestWindow. This way, your smaller widgets become more independent, and its more a matter of how you connect to each event that drives your application.
I could have done something like self.parent().showMessage(self.__edit.toPlainText()) within the TestWidget's emitTextSaved method to call the method directly - but this is not a good design.

Categories

Resources