Issue
In my application I have to replace all QLineEdit elements with customized QLineEdit. To do that there are different solutions:
- Modify the generated py file from pyuic4 and replace there all QLineEdit objects with my one LineEdit. This solution is not really the best, because every time I run pyuic4 I lose the modification I did into the generated output file.
- Write a new class that search recursively inside my window or dialog for QLineEdit widget types and replace them with my one LineEdit. This solution is much better. It allows to me to do the same also with other objects or extend it as I want without to care about the dialog or window.
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>
Question
Why happen this? How can I replace QWidgets at runtime?
Update
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
Here my code
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 "----------------------------------"
Solution
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
Answered By - ekhumoro
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.