I noticed that in PyQt5, when initializing a QObject, you can pass it keywords to establish certain connections, dependent on the object. I think this is very useful, as it not only saves you a second statement, so you also don't need to reference the object explicitly if you don't have to, but more importantly, there may be situations in which it would be very cumbersome to do so. E.g. if you want to create a list of objects it could save you a loop and so on.
So in the case of QActions for example, you could do this:
from PyQt5.QtWidgets import QAction
from PyQt5.QtCore import pyqtSlot
#pyqtSlot()
def do_something():
pass
#pyqtSlot()
def do_something_else():
pass
actions = [QAction(triggered=do_something), QAction(triggered=do_something_else)]
But I only found this behaviour by chance. I couldn't find this being documented anywhere. E.g. in the official Qt documentation, no keywords are mentioned (QObject, QAction). Are there more things like this that you can do with QObjects during the initialization?
I don't understand why this is not documented. Is it not recommended maybe? I use PyCharm as an editor, and even though I know there are problems with it when it comes to syntax inspection, especially with regards to PyQt, it also highlights these keywords as 'unexpected arguments'.
In section Support for Qt Properties of PyQt5 docs indicates what type of data supports the keyword arguments:
PyQt5 does not support the setting and getting of Qt properties as if
they were normal instance attributes. This is because the name of a
property often conflicts with the name of the property’s getter
method.
However, PyQt5 does support the initial setting of properties using
keyword arguments passed when an instance is created. For example:
act = QAction("&Save", self, shortcut=QKeySequence.Save,
statusTip="Save the document to disk", triggered=self.save)
The example also demonstrates the use of a keyword argument to connect
a signal to a slot.
PyQt5 also supports setting the values of properties (and connecting a
signal to a slot) using the pyqtConfigure() method. For example, the
following gives the same results as above:
act = QAction("&Save", self)
act.pyqtConfigure(shortcut=QKeySequence.Save,
statusTip="Save the document to disk", triggered=self.save)
(emphasis mine)
In conclusion, you can set the kwargs:
The initialization of the qproperties, and
The slot with which the signals are connected
Related
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
I am trying to get drag and drop (with images or files) working for my listwidget in pyqt5. I can find a lot of examples with pyqt4, but there is one part that does not work in the newer version:
In the "dropevent":
self.emit(QtCore.SIGNAL("dropped"), links)
and in the MainForm:
self.connect(self.view, QtCore.SIGNAL("dropped"), self.pictureDropped)
I read a lot of posts but can't seem to find an answer how this should be written down with pyqt5. Could anyone please help me with this?
link to the entire example:
PyQT4: Drag and drop files into QListWidget
Simple.
The signal must come first
The slot come as parameter.
You must connect only after the signal was created
You must emit only after the connection was made.
Here we go with a small example:
self.signalOwner.mySignal.connect(self.slotFunction)
In your example let's say and consider that the view owns the signal and that pictureDropped is your slot function, so:
self.view.dropped.connect(self.pictureDropped)
Remember, your signal must emit certain type(s) or nothing at all andddd your #pyqtSlot function must receive same type(s) with the function receiving the same amount of parameter that your signal emits.
I have made a post some short time ago about drag and drop with images, had many difficulties to figure it out how to accept events and how the classes behave, it's a bit deeper than only signals so, if you need here is the POST :D
Your linked example uses the old-style signal and slot syntax, whereas you need to use the new-style syntax. In PyQt4 (versions 4.5 or later), you could use either style, but PyQt5 only supports the new style.
The old-style syntax allowed custom signals to be emitted on-the-fly, but the new-style syntax demands that the signal is declared beforehand on the class:
class ListWidget(QtWidgets.QListWidget):
dropped = QtCore.pyqtSignal(list)
def __init__(self, parent=None):
...
def dropEvent(self, event):
...
self.dropped.emit(list_of_files)
The signal connection is then made like this:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
...
self.listWidget = ListWidget()
self.listWidget.dropped.connect(self.handleDropped)
def handleDropped(self, list_of_files):
print('dropped:', list_of_files)
I am writing a widget for PyQt4 and the project style guide dictates that I pass static properties of Qt elements through kwargs in the constructor.
Unfortunately, I am having trouble finding the correct kwarg name for the property I want to pass.
My current implementation looks like this:
self.scene = QtGui.QGraphicsScene(self)
self.view = QtGui.QGraphicsView(self.scene, self.mainArea)
# Flip y axis for sane drawing
matrix = QtGui.QMatrix()
matrix.scale(1, -1)
self.view.setMatrix(matrix)
I want to find a way to pass the matrix through the QGraphicsView constructor, if that's even possible.
I have tried the following
self.view = QtGui.QGraphicsView(self.scene, self.mainArea, matrix=matrix)
self.view = QtGui.QGraphicsView(self.scene, self.mainArea, transform=matrix)
but both of these instances tell me that they are unknown keyword arguments. I am not sure if this is even possible through the constructor, but I haven't been able to find a definitive answer anywhere.
I am also fuzzy on what the "static properties" of Qt elements are exactly.
Are you sure you've understood the style guide properly? I doubt that it expects you to set every possible property of every conceivable class using kwargs. The QGraphicsView class has eighty properties. Do you think it's plausible that Qt would define constructors with eighty optional arguments?
I think it's more likely that the guide is simply asking you to use kwargs wherever they are available. Otherwise, you should just use the property setter functions.
PS:
The matrix of a QGraphicsView is not a property anyway, so for your specific example, the point is moot.
I often find myself in this situation:
class A:...
B=class
a=Instance(A,())
#on_trait_change('a')##I would really like to be able to do this
def do_something(...)
I think that this currently triggers if you were to reset the entirety of the class. e.g. b=B(). b.a=A() should trigger it. But I would like to control when my custom class signals that it has been 'changed'. Per haps one might like A to signal 'changed' if merely a member of A is changed e.g. b.a.x+=1
If both A and B derive from HasTraits, then changing your decorator to #on_trait_change('a.+') will do what you want. If you change the signature of your do_something to two or more arguments, you'll even be able to detect which attributes of a changed. (See http://traits.readthedocs.org/en/latest/traits_user_manual/notification.html#notification-handler-signatures.)
In the project that I'm building, I'd like to have a method called when I paste some text into a specific text field. I can't seem to get this to work, but here's what I've tried
I implimented a custom class (based on NSObject) to be a delegate for my textfield, then gave it the method: textDidChange:
class textFieldDelegate(NSObject):
def textDidChange_(self, notification):
NSLog("textdidchange")
I then instantiated an object of this class in interface builder, and set it to be the delegate of the NSTextField. This however, doesn't seem to do anything. However, when I build the example code from http://www.programmish.com/?p=30, everything seems to work perfectly fine. How do I impliment this delegate code so that it actually works?
The reason this isn't working for you is that textDidChange_ isn't a delegate method. It's a method on the NSTextField that posts the notification of the change. If you have peek at the docs for textDidChange, you'll see that it mentions the actual name of the delegate method:
This method causes the receiver’s delegate to receive a controlTextDidChange: message. See the NSControl class specification for more information on the text delegate method.
The delegate method is actually called controlTextDidChange_ and is declared on the NSTextField superclass, NSControl.
Change your delegate method to:
def controlTextDidChange_(self, notification):
NSLog("textdidchange")
and it should work for you.