In my application I have to replace all QLineEdit elements with customized QLineEdit. To do that there are different solutions:
So far, I could write a module (WidgetReplacer) which search recursively for QGridLayout items and search if there are QLineEdit children. If yes, then replace it with my one.
The problem is that when I try to access one of my LineEdit objects I get the following error:
RuntimeError: wrapped C/C++ object of type QLineEdit has been deleted
And if I look at my output I notice that the modified QLineEdit object has sitll the old id:
old qt_obj <PyQt4.QtGui.QLineEdit object at 0x0543C930>
Replaced txt_line_1 with LineEdit
new qt_obj <LineEdit.LineEdit object at 0x0545B4B0>
----------------------------------
...
----------------------------------
<PyQt4.QtGui.QLineEdit object at 0x0543C930>
Why happen this? How can I replace QWidgets at runtime?
Here some additional details regarding the ojects wrapping: Inside the module WidgetReplace if I dump the QEditLine and LineEdit objects I get:
<PyQt4.QtGui.QLineEdit object at 0x05378618>
Reference count: 7
Address of wrapped object: 051FAC40
Created by: Python
To be destroyed by: C/C++
Parent wrapper: <PyQt4.QtGui.QGridLayout object at 0x05378588>
Next sibling wrapper: NULL
Previous sibling wrapper: <PyQt4.QtGui.QLabel object at 0x05378930>
First child wrapper: NULL
and
<LineEdit.LineEdit object at 0x05378978>
Reference count: 3
Address of wrapped object: 051FAC40
Created by: Python
To be destroyed by: C/C++
Parent wrapper: <__main__.Dialog object at 0x0535FBB8>
Next sibling wrapper: <PyQt4.QtGui.QGridLayout object at 0x05378780>
Previous sibling wrapper: NULL
First child wrapper: NULL
Instead if I dump my line edit from the main, I get follow, which represent the deleted QLineEdit object:
<PyQt4.QtGui.QLineEdit object at 0x05378618>
Reference count: 2
Address of wrapped object: 00000000
Created by: Python
To be destroyed by: C/C++
Parent wrapper: NULL
Next sibling wrapper: NULL
Previous sibling wrapper: NULL
First child wrapper: NULL
Dialog The ui and generate file can be dowloaded from DropBox dialog.ui and dialog.py
main
import sys
from PyQt4.QtGui import QDialog, QApplication
from dialog import Ui_Form
from WidgetReplacer import WidgetReplacer
class Dialog(QDialog, Ui_Form):
def __init__(self, parent = None):
super(Dialog, self).__init__(parent)
# Set up the user interface from Designer.
self.setupUi(self)
WidgetReplacer().replace_all_qlineedit(self)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Dialog()
window.show()
print window.txt_line_1
window.txt_line_1.setText("Hello Text 1")
sys.exit(app.exec_())
LineEdit
from PyQt4.QtGui import QLineEdit, QValidator, QPalette
from PyQt4 import QtCore
class LineEdit(QLineEdit):
def __init__(self, parent=None):
super(LineEdit, self).__init__(parent)
self.color_red = QPalette()
self.color_red.setColor(QPalette.Text, QtCore.Qt.red)
self.color_black = QPalette()
self.color_black.setColor(QPalette.Text, QtCore.Qt.red)
# Make connections
self.textChanged.connect(self.verify_text)
def verify_text(self, text):
validator = self.validator()
is_valid = QValidator.Acceptable
if validator is not None:
is_valid = validator.validate(text, 0)
if is_valid == QValidator.Acceptable:
self.setPalette(self.color_black)
elif is_valid in [QValidator.Invalid, QValidator.Intermediate]:
self.setPalette(self.color_red)
WidgetReplacer
import sip
from LineEdit import LineEdit
from PyQt4.QtCore import QRegExp
from PyQt4 import QtGui
class WidgetReplacer():
def __init__(self):
pass
def replace_all_qlineedit(self, qt_dlg):
children = self._get_all_gridlayout(qt_dlg)
items = []
for child in children:
new_items = self._find_all_obj_in_layout(child, QtGui.QLineEdit)
items.extend(new_items)
for item in items:
layout = item[0]
row = item[1]
column = item[2]
qt_obj = item[3]
self._replace_qlineedit_from_gridlayout(qt_dlg, qt_obj,
layout, row, column)
def _get_all_gridlayout(self, qt_dlg):
return qt_dlg.findChildren(QtGui.QGridLayout, QRegExp("gridLayout_[0-9]"))
def _find_all_obj_in_layout(self, layout, qt_type):
# Output list format:
# layout, row, col, qt_obj,
objects = []
if type(layout) == QtGui.QGridLayout:
layout_cols = layout.columnCount()
layout_rows = layout.rowCount()
for i in range(layout_rows):
for j in range(layout_cols):
item = layout.itemAtPosition(i, j)
# print "item(",i, j, "):", item
if type(item) == QtGui.QWidgetItem:
if type(item.widget()) == qt_type:
new_obj = [layout, i, j, item.widget()]
objects.append(new_obj)
elif type(layout) in [QtGui.QHBoxLayout, QtGui.QVBoxLayout]:
raise SyntaxError("ERROR: Find of Qt objects in QHBoxLayout or QVBoxLayout still not supported")
# for i in range(layout.count()):
# item = layout.itemAt(i)
return objects
def _replace_qlineedit_from_gridlayout(self, parent, qt_obj, layout, row, column):
print "old qt_obj", qt_obj
obj_name = qt_obj.objectName()
layout.removeWidget(qt_obj)
sip.delete(qt_obj)
qt_obj = LineEdit(parent)
qt_obj.setObjectName(obj_name)
layout.addWidget(qt_obj, row, column)
print "Replaced", obj_name, "with LineEdit"
print "new qt_obj", qt_obj
print "----------------------------------"
Don't replace the widgets at runtime: promote the widgets in Qt Designer so that the line-edits are automatically replaced by your custom class when the GUI module is generated.
Here's how to promote a widget to use a custom class:
In Qt Designer, select all the line-edits you want to replace, then right-click them and select "Promote to...". In the dialog, set "Promoted class name" to "LineEdit", and set "Header file" to the python import path for the module that contains this class (e.g. myapp.LineEdit
). Then click "Add", and "Promote", and you will see the class change from "QLineEdit" to "LineEdit" in the Object Inspector pane.
Now when you re-generate your ui module with pyuic, you should see that it uses your custom LineEdit class and there will be an extra line at the bottom of the file like this:
from myapp.LineEdit import LineEdit