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.
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'm learning to use PyQt5 and have run across a problem. My code is attempting to just draw a simple black box in the QMainWindow object by writing a second class PaintWidget which inherits from QWidget. I've posted my code first, and the correct one below it.
class PaintWidget(QWidget):
def __init__(self):
super().__init__()
self.qp = QPainter()
self.initUI()
def initUI(self):
self.qp.fillRect(1,1,100,100, Qt.black)
Correct:
class PaintWidget(QWidget):
def paintEvent(self, event):
qp = QPainter(self)
qp.fillRect(1, 1, 100, 100, Qt.black)
This is what confuses me. In order to create this class, we need to inherint from the super class QWidget, inorder to do so we use the function super().__init__() under __init__(self). We then set up the QPaint object which we will use in our method initUI() which actually does the work. Now this doesn't work when I run it.
The second, correct class, doesn't even seem to inherent, since it has no super().__init__(), even worse, it is setting up a method that is never even called (paintevent(self, event)), which takes an argument that seemingly comes from nowhere. Can someone point out why I'm wrong?
There is absolutely no difference to inheritance between the two cases. In both cases you say class PaintWidget(QWidget), so you are inheriting the QWidget.
The difference is in where you draw. In constructor (__init__), the widget is not yet mapped to the screen, so if you try to draw there, it won't have effect.
When the widget is actually displayed on screen, the system will invoke the paintEvent, which is a virtual method of the QWidget, and that is where you must draw the content. You only define that method in the second example.
Note that you need fresh QPainter in each invocation of the paintEvent. Creating one in the constructor and then using it in paintEvent would not work.
Also, most windowing systems don't remember the content of the widget when it is not actually visible on screen and rely on being able to call the paintEvent whenever the widget becomes visible again. So the method will likely be called many times. In contrast, the constructor, __init__, is only called once when creating the object.
Let's say I have a MainWindow.ui that defines the layout of MainWindow.py, where MainWindow.py looks a little like this:
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = uic.loadUi('MainWindow.ui')
MainWindow.ui holds two (actually three) widgets. A simple QLabel text_lbl for argument's sake, and an empty QWidget sub_widget. These two widgets are held in central_widget.
We also have a SubWidget.ui. SubWidget.ui can be anything, really, but let's say it holds a lot of labels and spinboxes. SubWidget.py looks a lot like MainWindow.py, except it holds a lot of signals and slots:
class SubWidget(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = uic.loadUi('SubWidget.ui')
# A lot of interesting stuff here.
What I want to do, is put an instance of SubWidget in MainWindow's sub_widget. The obvious thing for me to do would be to add the following lines of code to MainWindow.py's __init__:
from SubWidget import SubWidget # Though really this shouldn't be in
# __init__, but you get the idea.
self.ui.sub_widget = SubWidget()
Which simply doesn't do anything. I eventually achieved rendering SubWidget over the main window's contents and complaining about MainWindow already having a layout, but I lost that code in the midst of all fiddling.
How do I achieve this?
edit: I forgot to mention. self.ui.central_layout.addWidget(SubWidget()) visually achieves what I'm trying to do, but if I ever decide to add UI elements beneath that in the .ui file, that simply won't work.
If you can't, or for whatever reason just don't want to, use widget promotion, then the most obvious solution is to make the SubWidget a child of the place-holder widget.
Since the place-holder widget doesn't yet have a child, you can't give it layout in Qt Designer, so you will have to do it all programmatically:
layout = QtGui.QVBoxLayout(self.ui.empty_widget)
layout.setContentsMargins(0, 0, 0, 0)
self.ui.sub_widget = SubWidget()
layout.addWidget(self.ui.sub_widget)
I use a QStackedWidget and switch between different views. Each pane is basically a class and should reside in it's own file.
The problem is I want to access a QTableView from the other file without passing it's reference into the constructor.
Main File:
class MyApp(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MyApp, self).__init__(parent)
self.setupUi(self)
2nd File
from ui_MainWindow import Ui_MainWindow
class LimitsEditor(QMainWindow):
def __init__(self, session):
QMainWindow.__init__(self)
self.tblCommonLimits_horizheader = QHeaderView(Qt.Horizontal)
self.tblCommonLimits_horizheader = QMainWindow.tblLimits.horizontalHeader()
I get this error:
AttributeError: type object 'QMainWindow' has no attribute 'tblLimits'
As you can see, I want to access a table called "tblLimits" that I created in QtDesigner, but I'm unable to get access to it in my second file.
Any suggestions?
(edit for formatting)
I am editing this to show what I am doing that works:
In the main, when instantiating the class:
self.LimitsEditor = LimitsEditor(self.ui.tblLimits)
In the LimitsEditor file:
class LimitsEditor():
def __init__(self, tblLimits):
self.tblLimits = tblLimits
Now in the main I can access the table as either 'self.ui.tlbLimits' OR 'self.LimitsEditor.tblLimits'
This is how I have been doing it, and it works fine. It just didn't seem quite right to me, so I was wondering if there was a way to just inherit the knowledge of all the UI elements into another class/subclass.
If what you are trying to do is to have LimitsEditor be able to access the exact same table instance being used by MyApp, then this approach you are trying is impossible. The .ui file is a description of a layout of widgets which you apply to your class. When you call setupUi() inside your QMainWindow class, it creates instances. The actual UI module does not contain any references to these instances that you can then access in another file.
You must share a reference to the instance from MyApp to LimitsEditor. This comes down to a design decision. Here are a few suggestions (since I don't know how your app is really organized)
Wherever you are managing your stacked widget, you can pass a reference of the table instance to your LimitsEditor
Let your LimitsEditor be unaware of the table widget, and emit signals, to which your parent objects will listen and manage the table instance in response
Set up your LimitsEditor with an eventsFilter for the table (in your parent object). Your LimitsEditor wont directly have a reference to the table, but rather will receive events for it that it can handle.
Referring to the exception that you are seeing, QMainWindow is the PyQt4 class and does not contain your custom child widgets. Those are located on the instance of your custom class which contains the setupUi() call. You would then need to access it via self.tblLimits, but again this would be only for the class that actually used the Ui file, not other random classes.
When working with the QtDesigner in PyQt you usually follow the same patter:
Create the designer .ui file.
Create a class to use it.
Ensure that the ui file sets the widgets of your class.
For example:
class MyApp(QMainWindow):
def __init__(self, parent=None):
super(MyApp, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
class LimitsEditor(QMainWindow):
def __init__(self, session):
super(QMainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.tblCommonLimits_horizheader = QHeaderView(Qt.Horizontal)
self.tblCommonLimits_horizheader = self.ui.tblLimits.horizontalHeader()
Make sure you do access the widget through the instance ui element.
I would like to change behavior of Key_Delete for QTableWidget, but because of using Designer I don't want to make any changes in py file with form. So instead of reimplementing QTableWidget like it is this answer: How to implement MousePressEvent for a Qt-Designer Widget in PyQt I do something like this:
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.tableWidget.__class__.keyPressEvent = self.test
def test(self,event):
if event.key() == Qt.Key_Delete:
print "test"
return QTableWidget.keyPressEvent(self, event)
The problem is that I don't know how to keep original behavior of other keys than Qt.Key_Delete.
I have already changed last line like this:
return QtGui.QTableWidget.keyPressEvent(self, event)
return QtGui.QTableWidget.event(self, event)
but it doesn't work.
First: "but it doesn't work" is usually not descriptive enough. What behavior did you expect? What was the behavior you saw instead? Were there any error messages?
I can easily see a few mistakes in here, though.
You're overriding the method QTableWidget.keyPressEvent, which expects 2 arguments: (QTableWidget instance, event). But in the code you show above, the function you are using to override it only takes 1 argument (the first argument, 'self', does not count since it is automatically supplied).
Since you have set QTableWidget.keyPressEvent = self.test, and you are also trying to call this function from within self.test(), you have created an infinitely recursive function.
When you call QTableWidget.keyPressEvent, the first argument you have passed in (self) is a QMainWindow object. However as I mentioned above, this function expects a QTableWidget as its first argument.
Since you are overriding this method at the class level, ALL QTableWidgets will be forced to use the same keyPressEvent function (this would be very problematic). Instead, you should override just your specific widget's method: self.ui.tableWidget.keyPressEvent = self.test (also note that the signature for tableWidget.keyPressEvent is different from QTableWidget.keyPressEvent)
Example:
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
win = QtGui.QMainWindow()
win.show()
table = QtGui.QTableWidget()
win.setCentralWidget(table)
def test(event):
if event.key() == QtCore.Qt.Key_Delete:
print "delete"
return QtGui.QTableWidget.keyPressEvent(table, event)
table.keyPressEvent = test
Finally, another (possibly cleaner) approach would be to create a subclass of QTableWdget and, within Designer, 'promote' the table widget to your new class.