How to get all child components of QWidget in pyside/pyqt/qt? - python

I am developing a desktop application using pyside(qt), I want to access(iterate) all line edit components of QWidget. In qt I found two methods findChild and findChildren but there is no proper example found and My code shows error, 'form' object has no attribute 'findChild'.
Here 'form' is Qwidget form consist components lineEdit, comboboxes, Qpushbuttons etc.
Code:
lineEdits = form.findChild<QLineEdit>() //This is not working
lineEdits = form.findChild('QLineEdit) //This also not working

The signatures of findChild and findChildren are different in PySide/PyQt4 because there is no real equivalent to the C++ cast syntax in Python.
Instead, you have to pass a type (or tuple of types) as the first argument, and an optional string as the second argument (for matching the objectName).
So your example should look something like this:
lineEdits = form.findChildren(QtGui.QLineEdit)
Note that findChild and findChildren are methods of QObject - so if your form does not have them, it cannot be a QWidget (because all widgets inherit QObject).

Use this method QObject::findChildren(onst QString & name = QString()) with no parameters.
Omitting the name argument causes all object names to be matched.
Here is C++ example code:
QList<QLineEdit*> line_edits = form.findChildren<QLineEdit*>();

Related

pyqt5: QPushButton class ,clicked method and the connect [duplicate]

This question already has an answer here:
Declaration of the custom Signals
(1 answer)
Closed 1 year ago.
i am trying to learn pyqt5 take look at the following code
qbtn = QPushButton('Quit', self)
qbtn.clicked.connect(QApplication.instance().quit)
clicked signature in qtwidgets.py
def clicked(self, checked: bool = ...) -> None: ...
where is the implementation of the clicked method ?
how it is not callable i mean it's method right ?
print (qbtn.clicked())
output:
TypeError: native Qt signal is not callable
and finally if it doesn't have any implementation how it effects the QPushButton
?
print(qbtn)
print (qbtn.clicked)
print (qbtn.clicked.connect)
output:
<PyQt5.QtWidgets.QPushButton object at 0x7f963ca88160>
<bound PYQT_SIGNAL clicked of QPushButton object at 0x7f963ca88160>
<built-in method connect of PyQt5.QtCore.pyqtBoundSignal object at 0x7f963ca91cc0>
There are two important aspects that should always be kept in mind:
PyQt (similarly to PySide) is a binding: it is an interface to the Qt framework, and as such provides access to it using standard python functions and methods; in order to create it, a special tool is used, SIP, which actually creates the binding from objects exposed to python to those of Qt and viceversa (for PySide, the tool used is called Shiboken);
signals are not methods, they are interfaces to which callable objects can connect to, and those objects will be called whenever the signal is emitted, provided they have a compatible signature;
The file you're referring to is a pyi file. From What does ā€œiā€ represent in Python .pyi extension?:
The *.pyi files are used by PyCharm and other development tools to provide more information, such as PEP 484 type hints, than it is able to glean from introspection of extension types and methods. They are not intended to be imported, executed or used for any other purpose other than providing info to the tools. If you don't use use a tool that makes use of .pyi files then you can safely ignore this file.
You mentioned the following line:
def clicked(self, checked: bool = ...) -> None: ...
which is only found in those files, and is just that: an information.
Signals in C++ are declared in headers similarly to functions, having arguments that indicate the signal signature(s), and are then "transformed" into signals when Qt (or the program that declares its own signals) is compiled.[1]
Since PyQt and PySide are created using the aforementioned automated tools, the result is that signals might be listed as methods; notably, the official PySide docs list signals even including def: in PySide2 a specific "Signal" section is used, while in PySide6 they are not even identified as such.
In the python bindings, signals are unbound attributes for classes, but when a signal is referenced as a QObject instance, PyQt automatically binds the instance to the signal to create a bound signal.
>>> hasattr(QtWidgets.QPushButton.clicked, 'emit')
False
>>> hasattr(QtWidgets.QPushButton().clicked, 'emit')
True
You can see that the signal is dynamically bound by using a simple id (which should return an unique and constant value for an object):
>>> b = QtWidgets.QPushButton()
>>> id(b.clicked)
2971296616
>>> id(b.clicked)
2971299208
# or even:
>>> b.clicked == b.clicked
False
So, what you see from some documentation or reference file, is primarily the signature used to create the signal, but also the expected signature for the signal emission/receiver, similarly to what can be done in Python when declaring a new signal (with the difference that it's not possible to define default values, like the checked=False of QAbstractButton).
[1] this is a major oversimplification, I don't have enough knowledge of C++ to explain how exactly signal creation works, but it's just for the sake of explanation.

PyQt5 dbus: Force type signature of signal argument to be array of string

I'm writing an MPRIS player, which communicates with clients over
dbus. I need to emit a signal when my playback state changes. However,
the signal requires a format of (sa{sv}as), and my code is producing
(sa{sv}av). Here's the important part:
self.signal = QDBusMessage.createSignal(
"/org/mpris/MediaPlayer2",
"org.freedesktop.DBus.Properties",
"PropertiesChanged"
)
self.signal.setArguments(
[interface, {property: values}, ['']]
)
The problem is the third item in the list given to setArguments. It is
an empty string in a list because I need to produce a type of 'array
of string' (as) but pyqt5 translates that into 'array of variant' (av).
I never need to put any actual data in that list, I just need the type
signature to be correct.
Is there any way to do this in PyQt5? Perhaps using QDBusArgument?
I... I got it working. Wow. That was a campaign.
I don't know what is going wrong exactly, I wasn't able to dig out of the PyQt5 source where exactly the conversion happens. However, I did look into QDbusArgument(). There is no documentation for this in python, and the C++ docs are worthless due to major differences, so I took to the source code. in sip/QtDbus/qdbusargument.sip, I discovered a completely undocumented new method called qdbusargument_add. This maps to QDbusArgument().add() in python. It is used to add arguments with an explicit type id to a QDbusArgument. And it has a special case for QStringList!
From then I just bruteforced every possibility I could think of with arguments to QDbusArgument(), and finally got the following:
def PropertiesChanged(self, interface, property, values):
"""Sends PropertiesChanged signal through sessionBus.
Args:
interface: interface name
property: property name
values: current property value(s)
"""
emptyStringListArg = QDBusArgument()
emptyStringListArg.add([""], QMetaType.QStringList)
self.signal.setArguments([interface, {property: values}, emptyStringListArg])
QDBusConnection.sessionBus().send(self.signal)
It'd be great if the add() function could be documented, but I can't seem to send messages to the PyQt5 mailing list. Do I have to register first?

Select Directory with Python

I want to select 5 images with Python so that I can use these imges in my python program. I tried to do this with QFileDialog() (PyQt5) but I only succeed to single select a file.
And how to select a folder is also not really comprehensive.
I just want to select 5 images and select a folder so that I can save files in that folder. But it seems to be not so easy to do that.
I really like Python because its so easy but PyQt5 makes me everytime I use it just aggressive, all other libraries are just nice and easy to understand.
Maybe there is a good alternative to pyqt? tkinter maybe?
thanks.
In order to select a folder you can use this code:
widget = QWidget()
dialog = QFileDialog(
widget, "Select Directory of the Desired Files", os.path.curdir
)
dialog.setFileMode(QFileDialog.DirectoryOnly)
Qt supplies a bunch of static methods to get standardized file dialogs, two of them already satisfy your needs: getOpenFileNames() (stress on the final "s") and getExistingDirectory().
The first will return a list of absolute paths of selected file[s], the last will return the selected directory.
I know that reading the official documentation might be a bit overwhelming if you don't know anything about C++ (they are explained in detail, though), but they're not as hard as one could think.
Every function is listed in a very simple way:
returned_type : function_name(arguments) [const -> you can usually ignore this]
The returned_type is the type of the value the function is expected to return. In "c++ slang", void is the same as return (or return None or no return at all, as Python implicitly returns None if no other value/object is returned at the end of a function), if the type is a QString it's automatically converted to a Python str, while qreal is the same as Python's floats,. This is very important for "private" functions (methods), which are internally used by Qt: if you are subclassing and want to override a private method of a Qt class, you have to return the type Qt expects. You could theoretically ignore the returned_type for public functions if you know what you're doing, but it's usually better to stick with the original type.
There are some small "exceptions" that require some consideration. In some cases Qt expects some argument that will be modified within the function and would normally return whether the function was successful or not, while in Python it might return the reference to the argument (sorry, I can't remember them right now). Some other functions return a tuple instead of a single value, and that's the case of some static QFileDialog functions such as getOpenFileName[s] which return both the selected file[s] and the selected filter.

How to detect the type of widget?

This should be a stupid question. I am just curious and could not find the answer on my own.
E.g. I define in PyQt5 some widgets:
self.lbl = QLabel("label")
self.btn = QPushButton("click")
self.txt = QLineEdit("text")
Is there any method to detect what kind of widget the self.lbl, self.btn, or self.txt are?
I could imagine: by detecting the widget type, the input is self.lbl, the output should be QLabel... Or something like this.
I have only found the isWidgetType() method. But it is not what I want to have.
There are several ways to get the name of the widget:
using __class__:
print(self.lbl.__class__.__name__)
using QMetaObject:
print(self.lbl.metaObject().className())
These previous methods return a string with the name of the class, but if you want to verify if an object belongs to a class you can use isinstance():
is_label = isinstance(self.lbl, QLabel)
Another option is to use type() but it is not recommended, if you want to get more information about isinstance() and type() read the following: What are the differences between type() and isinstance()?
You can just use the standard Python means of checking an object type:
print(type(self.lbl))
print(isinstance(self.lbl, QLabel)

PyQt Runtime Error of QTreeWidgetItem

I'm trying to avoid the well-known PyQt Runtime Error when the underlying C/C++ object is deleted:
http://www.riverbankcomputing.com/pipermail/pyqt/2009-April/022809.html
PyQt4 - "RuntimeError: underlying C/C object has been deleted"
PyQt4 nested classes - "RuntimeError: underlying C/C++ object has been deleted"
PyQt: RuntimeError: wrapped C/C++ object has been deleted
Every one of my subclasses calls the super() method and therefore the base classes are properly constructed.
Still, I get this error and am wondering if it is due to the fact that I'm adding a QComboBox widget to a QTreeWidgetItem (using the setItemWidget() method of a QTreeWidget) but I cannot set the parent as the QTreeWidgetItem that contains it. When I try, I get the following error:
TypeError: QComboBox(QWidget parent=None): argument 1 has unexpected type 'QTreeWidgetItem'
Of course, I can either omit the parent in the constructor or pass the QTreeWidget as the parent, but I think I need to reference the correct parent.
I have subclassed the QComboBox and in my subclass it runs some basic operations on the QTreeWidget, but as soon as I enter the methods of my subclassed QComboBox, the underlying C object for the parent QTreeWidgetItem containing the QComboBox is deleted (which is why I'm thinking its something to do with setting the parent of the QComboBox).
I understand 9 times out of 10 the runtime error is due to not constructing the base class. But with that ruled out, how else can the error occur? Could it be due to not referencing the correct parent?
EDIT
I'm using the QComboBox to signal when a new combobox selection was made. Upon a new selection, it adds that selected value to a PyXB XML node. Interestingly, this issue only occurs if I append the value to the PyXB class binding storing the information permanently in an XML file. In otherwords, if that part of the code doesn't run I dont get the error - its only when the code runs the PyXB operation for appending a value to an XML node binding...
I usually avoid that kind of errors keeping a reference on my class to all the objects susceptible of being deleted like your QComboBox so try something like self.comboBoxHolder = QComboBox(...) when you create it.

Categories