I am experience with Qt4, but now try to get into the programming of Qt with python.
It works mainly but now I come across a basic python program I did not figure out:
TypeError: setupSignalSlots() takes 1 positional argument but 2 were given
from PyQt4 import QtGui, uic
from PyQt4 import QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
uic.loadUi('MainWindow.ui', self)
self.show()
self.setupSignalSlots(self)
def setupSignalSlots(self):
self.ui.actionQuit.clicked.connect(OnQuitMainWindow)
#QtCore.pyqtSlot()
def OnQuitMainWindow():
print('quit')
QApplication.quit()
Besides that problem I wonder if the signal slot code is correct.
There are several things wrong with the code you posted.
Firstly, the OnQuitMainWindow slot needs a self argument. However, you do not need to pass this argument explicitly as python will do it automatically. Secondly, when you connect to the slot, you need to access it via self. Finally, quit is not a static method of QApplication, so you need to call it via an instance (e.g. qApp.quit()).
(And one other nit-pick: in python (and Qt, for that matter), it goes against convention to start attribute names with a capital letter).
After making these corrections, your code should look like this:
from PyQt4 import QtGui, uic
from PyQt4 import QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
uic.loadUi('MainWindow.ui', self)
self.show()
self.setupSignalSlots()
def setupSignalSlots(self):
self.actionQuit.triggered.connect(self.onQuitMainWindow)
#QtCore.pyqtSlot()
def onQuitMainWindow(self):
print('quit')
QtGui.qApp.quit()
UPDATE:
And one more thing I missed: the way you're using uic.loadUi means that the objects added in Qt Designer will end up as direct attributes of the instance of MainWindow. So it should be self.actionQuit, rather than self.ui.actionQuit. Also, since it appears that this object is a QAction, the signal should be triggered, rather than clicked.
You don't need to pass self to a method of a class; it is automatically done for you. Just do self.setupSignalSlots().
Related
How do I create GUI classes for windows which have subwindows/classes that can access the main GUI's functions?
I have the below code which modifies the compiled .ui code from designer. What I want it to do is, when clicking the top-right "X", or using File -> Exit function, to close the window comprising the Window_SecondWindow class, and show the main window again--effectively calling the main window's show() from the subclass. I want to show only one window at a time.
When the code is run as-is, the Window_SecondWindow class hides, but immediately shows again, leading me to believe super is acting as self.
from PyQt5 import QtWidgets
from GUI import compiled_MainWindow
from GUI import compiled_SecondWindow
class Window_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.ui = compiled_MainWindow.Ui_MainWindow()
self.ui.setupUi(self)
self.ui.closeEvent = self.clicked_EXIT
# connect widgets
self.ui.Btn.clicked.connect(self.clicked_Btn)
self.ui.actionExit.triggered.connect(self.clicked_EXIT)
# add windows
self.SecondWindow = SecondWindow()
# more windows attached to main window
def clicked_Btn(self):
self.hide()
self.SecondWindow.show()
def clicked_EXIT(self):
self.close()
class Window_SecondWindow(Window_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.ui = compiled_SecondWindow.Ui_MainWindow()
self.ui.setupUi(self)
self.ui.closeEvent = self.clicked_EXIT
self.ui.actionExit.triggered.connect(self.clicked_EXIT)
def clicked_EXIT(self):
self.hide()
super().show()
Before answering your question, I'd like to address some important aspects.
First of all, never edit the generated code from pyuic to create your programs. They are intended to be used as imported modules, mostly as "resources": you import and integrate them into your code, but you should always leave them as they are. See the documentation on using Designer for more insight about this.
Be careful in overriding functions within the __init__: some functions are not "virtual" (thus, cannot be overwritten in such a way) and in some cases Qt always calls the base class function name anyway; just overwrite the method and call the base class implementation with super() if required. Also, closeEvent has the close event as a mandatory argument, and you have to add that to your overridden function, even if you don't use it (in the following examples I'm just using *args). That said, you should never use an overridden function as a slot that has a different argument, or viceversa.
Finally, you should not use capitalized names for attribute and variable names, as it is confusing and prone to errors (capitalization is mostly used for class names only, not their instancies).
Now, the answer
You are almost right, super() acts "as self", in the sense that it just calls the inherited show() method of the class against the instance. So, it calls the show method of Window_MainWindow, but since the instance is the second window, it's the same as doing Window_MainWindow.show(self), with self being the Window_SecondWindow instance; it is exactly as doing self.show().
There are two (and a half) possibilities.
The first, more obvious solution, is to give a reference of the main window instance to the second one:
class Window_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.secondWindow = Window_SecondWindow()
self.secondWindow.mainWindow = self
class Window_SecondWindow(Window_MainWindow):
# ...
def clicked_EXIT(self, *args):
self.hide()
self.mainWindow.show()
Be aware that while, as #noras points out in the comment, you could set the main window as a parent in the init argument, but this only works as expected with QMainWindow and QDialog descendants; if the child widget is of any other kind, it will be shown inside the parent, not as a separate window.
The second (and more "Qt-wise correct") is to create a signal for the second class that is emitted when it's closed, and connect it in the main window so that it's shown again when that happens:
class Window_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.secondWindow = Window_SecondWindow()
self.secondWindow.closed.connect(self.show)
class Window_SecondWindow(Window_MainWindow):
closed = QtCore.pyqtSignal()
def clicked_EXIT(self, *args):
self.hide()
self.closed.emit()
The second-and-a-half solution is to use an event filter:
class Window_MainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.secondWindow = Window_SecondWindow()
self.secondWindow.installEventFilter(self)
def eventFilter(self, source, event):
if source == self.secondWindow and event.type() == QtCore.QEvent.Close:
self.show()
return super().eventFilter(source, event)
I have a QPushButton, called 'StartButton' in my MainWindow. The Button's name, position and everything else is defined in the Qt Designer, so I don't define anything in my programm.
I want to replace self.StartButton.clicked.... with something like QtGui.MyMainWindow.StartButton.clicked....
Is that possible at all and what should I write instead of self?
class MainWindow(QtGui.QMainWindow, QtGui.QFileDialog):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
uic.loadUi('MyMainWindow.ui', self)
self.StartButton.clicked.connect(MainWindow.do_something())
Your line should read:
self.StartButton.clicked.connect(self.do_something)
I made two changes:
First, I removed the parentheses. You were calling do_something, rather than passing it as an argument to connect.
The second change is replacing MainWindow with self. I realize that you want to avoid this, but MainWindow is merely the class. self is the instance of MainWindow you are trying to connect to.
Note: you are also inheriting from two different widgets (QMainWindow and QFileDialog), which leads to undefined behavior in Qt. Unfortunately, you have to pick one or the other or things will break.
I am trying to convert a Qt4 custom widget written in C++ to a Python 2.7 custom widget. However, I have not been able to figure out how QLabel(parent) would be written in Python. This is the original C++ code from the ".ccp" file:
DocumentWidget::DocumentWidget(QWidget *parent)
: QLabel(parent)
{
currentPage = -1;
setAlignment(Qt::AlignCenter);
}
The QLabel(parent) seems to be some sort of initializer list. I've tried using multiple inheritance in Python in parallel, but this leads to the following error: Cannot create a consistent method resolution order (MRO) for bases QLabel, QWidget.
I'm trying to port the code instead of creating a wrapper for the C++ widget, because I don't know C++ and think I will have to customize the widget further in the future.
I'm not trained as a programmer and this is the first day I ran into C++, so feel free to correct me even if I'm doing something silly. I will not feel embarrassed.
The code defines a constructor for the DocumentWidget class, which inherits QLabel and requires a QWidget as parent.
The equivalent PyQt code would be:
from PyQt4 import QtCore, QtGui
class DocumentWidget(QtGui.QLabel):
def __init__(self, parent):
super(DocumentWidget, self).__init__(parent)
# or QtGui.QLabel.__init__(self, parent)
self.currentPage = -1
self.setAlignment(QtCore.Qt.AlignCenter)
Multiple inheritance worked, but the base classes had to be called in the correct order (i.e., DocumentWidget(QLabel, QWidget) instead of DocumentWidget(QLabel, QWidget)).
In full:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class DocumentWidget(QLabel, QWidget):
def __init__(self, parent=None):
super(DocumentWidget, self).__init__()
self.currentPage = -1
self.setAlignment(Qt.AlignCenter)
In PyQt, you can use QtCore.pyqtSignal() to create custom signals.
I tried making my own implementation of the Observer pattern in place of pyqtSignal to circumvent some of its limitations (e.g. no dynamic creation).
It works for the most part, with at least one difference.
Here is my implementation so far
class Signal:
def __init__(self):
self.__subscribers = []
def emit(self, *args, **kwargs):
for subs in self.__subscribers:
subs(*args, **kwargs)
def connect(self, func):
self.__subscribers.append(func)
def disconnect(self, func):
try:
self.__subscribers.remove(func)
except ValueError:
print('Warning: function %s not removed from signal %s'%(func,self))
The one thing noticed was a difference in how QObject.sender() works.
I generally stay clear of sender(), but if it works differently then so may other things.
With regular pyqtSignal signals, the sender is always the widget closest in a chain of signals.
In the example at the bottom, you'll see two objects, ObjectA and ObjectB. ObjectA forwards signals from ObjectB and is finally received by Window.
With pyqtSignal, the object received by sender() is ObjectA, which is the one forwarding the signal from ObjectB.
With the Signal class above, the object received is instead ObjectB, the first object in the chain.
Why is this?
Full example
# Using PyQt5 here although the same occurs with PyQt4
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
object_a = ObjectA(self)
object_a.signal.connect(self.listen)
layout = QBoxLayout(QBoxLayout.TopToBottom, self)
layout.addWidget(object_a)
def listen(self):
print(self.sender().__class__.__name__)
class ObjectA(QWidget):
signal = Signal()
# signal = pyqtSignal()
def __init__(self, parent=None):
super(ObjectA, self).__init__(parent)
object_b = ObjectB()
object_b.signal.connect(self.signal.emit)
layout = QBoxLayout(QBoxLayout.TopToBottom, self)
layout.addWidget(object_b)
class ObjectB(QPushButton):
signal = Signal()
# signal = pyqtSignal()
def __init__(self, parent=None):
super(ObjectB, self).__init__('Push me', parent)
self.pressed.connect(self.signal.emit)
if __name__ == '__main__':
import sys
app = QApplication([])
win = Window()
win.show()
sys.exit(app.exec_())
More reference
Edit:
Apologies, I should have provided a use-case.
Here are some of the limitations of using pyqtSignals:
pyqtSignal:
..only works with class attributes
..cannot be used in an already instantiated class
..must be pre-specified with the data-types you wish to emit
..produces signals that does not support keyword arguments and
..produces signals that cannot be modified after instantiation
Thus my main concern is using it together with baseclasses.
Consider the following.
6 different widgets of a list-type container widget share the same interface, but look and behave slightly different. A baseclass provides the basic variables and methods, along with signals.
Using pyqtSignal, you would have to first inherit your baseclass from at least QObject or QWidget.
The problem is neither of these can be use in as mix-ins or in multiple inheritance, if for instance one of the widgets also inherits from QPushButton.
class PinkListItem(QPushButton, Baseclass)
Using the Signal class above, you could instead make baseclasses without any previously inherited classes (or just object) and then use them as mix-ins to any derived subclasses.
Careful not to make the question about whether or not multiple inheritance or mix-ins are good, or of other ways to achieve the same thing. I'd love your feedback on that as well, but perhaps this isn't the place.
I would be much more interested in adding bits to the Signal class to make it work similar to what pyqtSignal produces.
Edit 2:
Just noticed a down-vote, so here comes some more use cases.
Key-word arguments when emitting.
signal.emit(5)
Could instead be written as
signal.emit(velocity=5)
Use with a Builder or with any sort of dependency injection
def create(type):
w = MyWidget()
w.my_signal = Signal()
return w
Looser coupling
I'm using both PyQt4 and PyQt5. With the Signal class above, I could produce baseclasses for both without having it depend on either.
You can do this with a metaclass that inherits from pyqtWrapperType. Inside __new__, call pyqtSignal() as needed and set the attributes on the result class.
During regular intervals of my program, a block (of 3 stacked) widgets need to be added to a horizontal layout. Since the widgets within each block are important to eachother, I wish to encapsulate each stack as it's own widget (making the layout adding business much easier).
I'm having trouble getting PyQt4 to recognise my 'stack' as a widget.
I made the widget stack in Qt Designer (as form: widget) and converted it to a .py via
'pyuic4 DesignerFile.ui > ClassFile.py'.
Now I can't seem to add this 'stack' (parent widget of 3 child widgets) to the layout via .addWidget( Class ).
I tried constructing a super class of the stack class (because I need to add more functionality to the stack) but the instance of the class is either...
Not recognised as a widget
Invisible
defective because I've no idea on how to structure the super class.
Here's what I'm failing with at the moment (though it's about the 8th class structure I've tried):
from ClassFile import ClassCode
class Stack(ClassCode):
def __init__(self,parent= None):
QtGui.QWidget.__init__(self,parent)
Could somebody help me structure this or lead me to some good examples?
(I've mimicked the code in both the following sources but with no avail!!
http://lateral.netmanagers.com.ar/stories/27.html#what-you-need-to-follow-the-tutorial
http://zetcode.com/tutorials/pyqt4/customwidgets/ )
Thanks!
Specs:
python 2.7.2
PyQt4
Windows 7
When you compile a python module from a ui file with the default options, it will (amongst other things) generate a simple "setup" class. In outline, the setup class will look like this:
class Ui_ClassCode(object):
def setupUi(self, ClassCode):
ClassCode.setObjectName("ClassCode")
# bunch of boiler-plate ui code goes here
self.retranslateUi(ClassCode)
QtCore.QMetaObject.connectSlotsByName(ClassCode)
def retranslateUi(self, ClassCode):
pass
There are a couple of issues to notice here that are relevant to the question.
Firstly, the setup class is designed to be used as a mixin rather than as a direct subclass. It's task is to "inject" ui into a host widget that is passed to the setupUI method.
Secondly, the setup class is given an ugly, unpythonic identifier that is created by prepending "Ui_" to the objectName property that was set in Designer.
Fortunately, pyuic4 provides a way to bypass these two issues. All that's required is to use the -w option when compiling the python module from the ui file:
pyuic4 -w designerfile.ui > classfile.py
This will add a wrapper class that (1) can be easily subclassed, and (2) has the class-name that you damn well gave it in Qt Designer.
The wrapper class will look something like this:
class ClassCode(QtGui.QWidget, Ui_ClassCode):
def __init__(self, parent=None, f=QtCore.Qt.WindowFlags()):
QtGui.QWidget.__init__(self, parent, f)
self.setupUi(self)
As you can see, it doesn't do anything special: you could easily replicate what it does in your own code. But, IMO, it does make the compiled modules much more intuitive to use.
For example:
from PyQt4 import QtGui, QtCore
from classfile import ClassCode
class Stack(ClassCode):
def __init__(self, parent=None):
ClassCode.__init__(self, parent)
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.stack = Stack(self)
self.setCentralWidget(self.stack)
First, it's more appropriate to call the parent __init__ with the use of super. That will ensure the method in the proper super class is invoked. Second, when using a class constructed with pyuic, you need to call self.setupUi(self) from your __init__ method. And lastly, you need to make sure and multiple inherit from both the proper Qt class and the pyuic generated class (which is really more of a mixin).
So, something like this:
from ClassFile import ClassCode
class Stack(QtGui.QWidget, ClassCode):
def __init__(self,parent= None):
super(Stack, self).__init__(parent)
self.setupUi(self)