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)
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 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
I am having trouble with passing a keyPressEvent in my custom QGraphicsItem, and I am not sure if I understand well how it works on this kind of objects. I know it has been asked already and I have found a lot of similar questions in C++ and a few in Python, but I could not make it work in my script...
In Qt's documentation it is said:
Note that key events are only received for items that set the ItemIsFocusable flag,
and that have keyboard input focus.
My guess is that something is wrong in my code related to this sentence.
Basically, my class looks like that:
class myItem(QtWidgets.QGraphicsItem):
def __init__(self, **kwargs):
super(myItem, self).__init__(**kwargs)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable)
def keyPressEvent(self, event):
if event == QtCore.Qt.Key_Delete:
self.scene().removeItem(self)
return super(myItem, self).keyPressEvent(event)
Am I missing a flag, or do I need to set up something in my scene class, or something like that?
Cheers :)
I am using signal with 2 overloads
buttonClicked = pyqtSignal([int],[str])
I want to connect only one overload(int) with a slot. Whenever I call the emit the other overload(str) I want nothing to happen. How to achieve this?
class Example(QWidget):
buttonClicked = pyqtSignal([int],[str])
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.btn = QPushButton('Button',self)
self.btn.clicked.connect(self.doAction)
self.make_conn()
self.setWindowTitle('Yo')
self.show()
def make_conn(self):
self.buttonClicked.connect(self.showDialog) #How to make specific connection here . Using self.buttonClicked[int].connect(self.showDialog) doesnt work.
def showDialog(self):
print('here')
def doAction(self):
self.buttonClicked.emit('soru') #should NOT call showDialog
self.buttonClicked.emit(23) #should call showDialog
Ok I searched the web and I somehow found a solution and some interesting things.
First, when using emit() I have to specify the overload by specifying the type.
For example in my above example if I want to emit the signal for str version I have to call self.buttonClicked[str].emit('soru') .
Secondly, I have to specify the overloaded version detail by telling it if its str or int when connecting the signal with the slot. Like
self.buttonClicked[str].connect(showDialog).
So if now I emit 2 signals specifically:
self.buttonClicked[str].emit('soru')
self.buttonClicked[int].emit(23)
Then only str version will call showDialog.
Now I do not specify the overloaded version when connecting like:
self.buttonClicked.connect(showDialog)
Then only the overloaded version which was specified first when creating the pyqtSignal([int],[str]) will be called. So here, only 'int' version will be connected to the slot.
Source: source
Here is the situation. I have a class which is derived from a QListView that adds some convenience functions, a custom widget if you like. I do not want to wrestle with the Designer to use my widget. I simply want to use a plain QlistView in the Designer (as a placeholder) and compile it with pyuic4. At runtime I want to replace that normal QListView with my own version.
How can you do this?
I was hoping something like this in the init would do the trick:
self.lstView1 = MyListView
but it doesn't...
The problem is that you are actually simply replacing the object pointed by lstView1 but not adding it to the widget. That is, when you create you object you simply tell python to point to your new object using lstView1 but the actual UI is using the old pointer since it as already added.
I'm going to assume that you have use py4uci to convert the ui files to python and you set up the gui as:
class ExambleUI(QtGUi.QDialog, UI_Example):
def __init__(self, parent):
QtGui.QDiialog.__init__(self, parent)
self.setupUI(self)
self.lstView1 = MyListView
Because setupUi is executing before you change the value of lstView, you are not getting the new widget. You just have to swap the lines:
class ExambleUI(QtGUi.QDialog, UI_Example):
def __init__(self, parent):
QtGui.QDiialog.__init__(self, parent)
self.lstView1 = MyListView
self.setupUI(self)
On the other hand I recommend you to follow this tutorial and create and use you widget in the designer, it is easy and faster.
Use QLayout replace function:
ui->main_layout->replace(oldWidget, newWidget);