Getting Maximum Recursion Error when trying to override QTreeView.edit() - python

The title says most of it. I have to be something dumb here but for some reason anytime I try to override the edit function of a QTreeView when I right click on the Treeview I get a Maximum recursion depth error.
This is specifically when I try to call the super's edit function. Here is an example of the problem. What am I messing up here?
from PyQt5 import QtWidgets, QtGui, QtCore
class EditTreeView(QtWidgets.QTreeView):
editingRequested = QtCore.pyqtSignal(QtCore.QModelIndex)
def __init__(self, parent=None):
super(EditTreeView, self).__init__(parent)
def edit(self, index, QAbstractItemView_EditTrigger=None, QEvent=None):
super(EditTreeView, self).edit(index)
class testTreeview(QtWidgets.QWidget):
def __init__(self, parent=None):
super(testTreeview, self).__init__(parent)
self.mainTree = EditTreeView()
self.lo = QtWidgets.QVBoxLayout()
self.lo.addWidget(self.mainTree)
self.setLayout(self.lo)
self.model = QtGui.QStandardItemModel()
self.mainTree.setModel(self.model)
def populate(self):
row = [QtGui.QStandardItem('teststuff'), ]
root = self.model.invisibleRootItem()
root.appendRow(row)
if __name__ == "__main__":
from PyQt5 import QtCore, QtGui, QtWidgets
app = QtWidgets.QApplication([])
volume = testTreeview()
volume.show()
app.exec_()

Explanation:
QTreeView inherits from QAbstractItemView, and if you review the methods of that class, you can see that there are 2 methods with the same name:
bool QAbstractItemView::edit(const QModelIndex &index,
QAbstractItemView::EditTrigger trigger, QEvent *event)
Starts editing the item at index, creating an editor if necessary, and
returns true if the view's State is now EditingState; otherwise
returns false.
The action that caused the editing process is described by trigger,
and the associated event is specified by event.
Editing can be forced by specifying the trigger to be
QAbstractItemView::AllEditTriggers.
void QAbstractItemView::edit(const QModelIndex &index)
Starts editing the item corresponding to the given index if it is
editable.
Note that this function does not change the current index. Since the
current index defines the next and previous items to edit, users may
find that keyboard navigation does not work as expected. To provide
consistent navigation behavior, call setCurrentIndex() before this
function with the same model index.
It is observed that the first method is more general than the second one, so it gives us a suspicion that the second one will use the first one and indeed this happens if the source code is reviewed:
void QAbstractItemView::edit(const QModelIndex & index)
{
Q_D(QAbstractItemView);
if (Q_UNLIKELY(!d->isIndexIsValid(index)))
qWarning("edit: index was invalid");
if (Q_UNLIKELY(!edit(index, AllEditTriggers, 0)))
qWarning("edit: editing failed");
}
So in your case clearly explain the error: you are overriding the first method and invoking the second, but the second uses the first according to the source code, and returned to the infinite cycle.
Solution
The solution is to use the super of the same method with all the parameters:
class EditTreeView(QtWidgets.QTreeView):
editingRequested = QtCore.pyqtSignal(QtCore.QModelIndex)
def edit(self, index, trigger, event):
self.editingRequested.emit(index)
return super(EditTreeView, self).edit(index, trigger, event)
But keep in mind that the override is about the first method, in C++ it is allowed to have methods with the same name but in python if there are several methods with the same name the last one will erase the previous ones.

Related

How to access self object within class from outside

I am writing an application that uses a Qt GUI and am now trying to multithread it (first time learner). A longer task will be contained within the worker thread eventually but output logs will be required to be written as it goes. The GUI class has a method to output these logs to a plain text widget. Up until now, everything has been running within the GUI class.
I currently have the following code (included the important bits for brevity):
class Worker(QRunnable):
#pyqtSlot()
def run(self):
Ui.logentry(self, "Test")
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi(resourcepath('./pycdra.ui'), self)
self.outputlog = self.findChild(QtWidgets.QPlainTextEdit, 'outputlog')
self.button = self.findChild(QtWidgets.QPushButton, 'button')
self.button.clicked.connect(self.Button)
self.threadpool = QThreadPool()
self.logentry("Available threads: %d" % self.threadpool.maxThreadCount())
def Button(self):
worker = Worker()
self.threadpool.start(worker)
def logentry(self, returntext):
self.outputlog.appendPlainText(self.timestamp() + " " + str(returntext))
self.outputlog.repaint()
def timestamp(self):
import datetime
ts = datetime.datetime.now(tz=None).strftime("%Y-%m-%d %H:%M:%S"))
return ts
def applic():
app = QApplication(sys.argv)
window = Ui()
window.show()
sys.exit(app.exec_())
applic()
When I try running this, the GUI loads perfectly, but upon pushing button the Ui.logentry part of Worker returns the error: AttributeError: 'Worker' object has no attribute 'outputlog'
I attempted making logentry() and timestamp() global so they can access and be accessed by both classes, but the closest I got with the line Ui.self.outputlog.appendPlainText(self.timestamp() + " " + str(returntext)) was that 'Ui' object has no attribute 'self'.
What am I missing? Am I right in making them global or are there other means to do this?
There are various problems with your code. I'll try to address them in order of importance.
logentry is an instance method of Ui, which means that it requires an instance of Ui in order to be run; what you're trying to achieve would never work since that self refers to an instance of Worker, which clearly doesn't have any outputlog attribute (which is what logentry expects instead). Remember: the self argument is not there just for fun, it always refers to the object (the instance, for instance methods) the function is called from; self.logentry means "call the function logentry using self as first argument. Since, in your case, self refers to an instance of Ui, that explains your main issue, since that instance doesn't have that attribute.
None is exactly what its name says: nothing. The timestamp function will throw another AttributeError, since there's no strftime attribute in None. That is part of a datetime object, so you have to get a now object, then call its strftime function against it. In any case, the tz argument requires a tzinfo subclass.
pyqtSlot only works for Qt classes that inherit from QObject (such as QWidget). QRunnable isn't one of those classes. You can check the whole inheritance tree in the header of the documentation in each class: if there's a "Inherits:" field, go up until there's none. If you get a QObject inheritance at some point, then you can use slots and signals for that object, otherwise not. Consider that: QWidget also inherits from QObject; all Qt widgets inherit from QWidget; there are Qt classes that inherit from QObject but are not widgets.
Even ignoring all the above, access to UI elements is always forbidden from external threads (including QRunnable objects); while you can theoretically (but unreliably) get their properties, trying to set them will most likely cause a crash; in order to change something in a UI element, using signals is mandatory. Note that "access" also includes creation, and always results in a crash.
Calling repaint is a common (and wrong) attempt to solve the above issue; that's unnecessary, as properly setting widget properties (like using appendPlainText()) already results in a scheduled repaint on its own; there are very few cases for which repaint is actually necessary, and the rule of thumb is that if you're calling it you probably don't know what your doing or why your doing it. In any case, calling update() is always preferred, and it must always be called from the main UI thread anyway.
Using imports in a function is rarely required, as import statements should always be in the very beginning of the script; while there are cases for which imports can (or should) be done later or in a specific function, doing them in a function that is likely to be called often, makes using them there completely pointless. Also, datetime is part of the standard library, so importing it on demand will hardly affect performance (especially considering its "performance weight" against what a big library like Qt is compared to it).
When the ui is loaded from a .ui file (or a pyuic generated file), PyQt alread creates all widgets as instance attributes, so there's no need for findChild. Unfortunately there are a lot of tutorials that suggest that approach, and they are just completely and plain wrong. You can already access those widgets as self.outputlog and self.button right after uic.loadUi.
Function names (like variables and attributes) should always begin with a lower case letter, as only classes and constants should begin with upper cases (see the official Style Guide for Python Code). Also, object names should always explain what those object do (see "self-documenting code"): a function that does an "action" should have a verb; if it's named "Button" it doesn't tell me that it's going to do some processing, and that's not a very good thing.
A "main" function (like your applic) usually makes sense within the common if __name__ == '__main__': block, which ensures that that function doesn't get called in case the file gets imported instead of being directly run. (See this answer and the related question).
Since QRunnable doesn't inherit from QObject, we can create a QObject subclass that acts as a signal "proxy", and make it a member of the QRunnable instance. Then we must connect to that signal everytime we create a new Worker object.
Here is a revised version of your code, based on the above points.
import datetime
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.uic import loadUi
class WorkerSignals(QObject):
mySignal = pyqtSignal(str)
class Worker(QRunnable):
def __init__(self):
super().__init__()
self.signalProxy = WorkerSignals()
self.mySignal = self.signalProxy.mySignal
def run(self):
self.mySignal.emit("Test")
class Ui(QMainWindow):
def __init__(self):
super(Ui, self).__init__()
loadUi('./pycdra.ui', self)
self.button.clicked.connect(self.startWorker)
self.threadpool = QThreadPool()
self.logentry("Available threads: %d" % self.threadpool.maxThreadCount())
def startWorker(self):
worker = Worker()
worker.mySignal.connect(self.logentry)
self.threadpool.start(worker)
def logentry(self, returntext):
self.outputlog.appendPlainText(self.timestamp() + " " + str(returntext))
def timestamp(self):
ts = datetime.datetime.now()
return ts.strftime("%Y-%m-%d %H:%M:%S")
def applic():
import sys
app = QApplication(sys.argv)
window = Ui()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
applic()
I suggest you to carefully study the provided code and the differences with yours, and then do some careful, patient research both on the links given above and the following topics:
classes and instances, and the meaning of self;
python types (including None);
what is event driven programming and how it relates with graphical interfaces;
the most important Qt classes, QObject and QWidget, and all their properties and functions (yes, they are a lot);
general PyQt related topics
(most importantly, signals/slots and properties);
code styling and good practices;
Use the class' initialised form
class foo:pass
selfuotsideclass=foo()
Use variable selfoutsideclass as self

How do I create GUI classes for windows, which have subwindows/classes that can access to the main GUI's functions?

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)

Can QFrame be triggered by its childs elements

When an item (spinBox, LineEdit etc) changes its value in GUI (via designer) I set a certain button's enable status. For example:
self.ui.lineEdit_1.textChanged.connect(self.pushButton_status)
self.ui.checkBox_1.stateChanged.connect(self.pushButton_status)
self.ui.spinBox_1.valueChanged.connect(self.pushButton_status)
self.ui.spinBox_2.valueChanged.connect(self.pushButton_status)
self.ui.spinBox_3.valueChanged.connect(self.pushButton_status)
self.ui.spinBox_4.valueChanged.connect(self.pushButton_status)
This works fine. Though there are lots of lines here (and even more in the actual code). I have all of these items inside a frame (QFrame). So I was wondering if it is possible to do something like:
self.ui.frame_1.childValueChanged.connect(self.pushButton_status)
which could perhaps stand for all the items inside of it. Is there any way within this logic that could do what I am looking for? If so.. how?
There is no direct way to do what you want, but there is a maintainable way to do it, in this case you just have to filter the type of widget and indicate which signal you will use by adding more options to the function, in your case:
def connectToChildrens(parentWidget, slot):
# get all the children that are widget
for children in parentWidget.findChildren(QtWidgets.QWidget):
# filter if the class that belongs to the object is QLineEdit
if isinstance(children, QtWidgets.QLineEdit):
# Connect the signal with the default slot.
children.textChanged.connect(slot)
elif isinstance(children, QtWidgets.QCheckBox):
children.stateChanged.connect(slot)
elif isinstance(children, QtWidgets.QSpinBox):
children.valueChanged.connect(slot)
And then you use it in the following way:
class MyDialog(QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.ui = Ui_MyDialog()
self.ui.setupUi(self)
connectToChildrens(self.ui.frame_1, self.pushButton_status)

Python/Qt Designer: Adress a QPushButton without self

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.

Event - catch only Qt.Key_Delete

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.

Categories