Reemplazar objetos QWidget en tiempo de ejecución

En mi aplicación, tengo que reemplazar todos los elementos de QLineEdit con QLineEdit personalizado. Para ello hay diferentes soluciones:

  1. Modifique el archivo py generado desde pyuic4 y reemplace todos los objetos QLineEdit con mi One LineEdit. Esta solución no es realmente la mejor, porque cada vez que ejecuto pyuic4 pierdo la modificación que hice en el archivo de salida generado.
  2. Escriba una nueva clase que busque recursivamente dentro de mi ventana o cuadro de diálogo para los tipos de widget de QLineEdit y reemplácelos con mi One LineEdit. Esta solución es mucho mejor. Me permite hacer lo mismo con otros objetos o extenderlo como quiera sin preocuparme por el diálogo o la ventana.

Hasta ahora, podría escribir un módulo (WidgetReplacer) que busque recursivamente los elementos de QGridLayout y busque si hay hijos de QLineEdit. Si es así, entonces reemplázalo con el mío.

El problema es que cuando bash acceder a uno de mis objetos LineEdit, aparece el siguiente error:

RuntimeError: wrapped C/C++ object of type QLineEdit has been deleted 

Y si miro mi salida, me doy cuenta de que el objeto QLineEdit modificado tiene la ID antigua:

 old qt_obj  Replaced txt_line_1 with LineEdit new qt_obj  ---------------------------------- ... ----------------------------------  

Pregunta

¿Por qué sucede esto? ¿Cómo puedo reemplazar QWidgets en tiempo de ejecución?


Actualizar

Aquí hay algunos detalles adicionales con respecto a la envoltura de objetos: Dentro del módulo WidgetReplace si vuelco los objetos QEditLine y LineEdit que obtengo:

  Reference count: 7 Address of wrapped object: 051FAC40 Created by: Python To be destroyed by: C/C++ Parent wrapper:  Next sibling wrapper: NULL Previous sibling wrapper:  First child wrapper: NULL 

y

  Reference count: 3 Address of wrapped object: 051FAC40 Created by: Python To be destroyed by: C/C++ Parent wrapper:  Next sibling wrapper:  Previous sibling wrapper: NULL First child wrapper: NULL 

En su lugar, si vuelco mi edición de línea de la línea principal, obtengo el siguiente, que representa el objeto QLineEdit eliminado:

  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 

Aqui mi codigo

Diálogo El archivo ui y el archivo generado pueden descargarse desde DropBox dialog.ui y dialog.py

principal

 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 "----------------------------------" 

No reemplace los widgets en tiempo de ejecución: promocione los widgets en Qt Designer para que las ediciones de línea sean reemplazadas automáticamente por su clase personalizada cuando se genere el módulo GUI.

Aquí se explica cómo promocionar un widget para usar una clase personalizada:

En Qt Designer, seleccione todas las ediciones de línea que desea reemplazar, luego haga clic con el botón derecho sobre ellas y seleccione “Promocionar a …”. En el cuadro de diálogo, establezca “Nombre de clase promocionado” en “LineEdit” y configure “Archivo de encabezado” en la ruta de importación de Python para el módulo que contiene esta clase (por ejemplo, myapp.LineEdit ). Luego haga clic en “Agregar” y “Promocionar”, y verá el cambio de clase de “QLineEdit” a “LineEdit” en el panel del Inspector de objetos.

Ahora, cuando vuelva a generar su módulo ui con pyuic, debería ver que usa su clase personalizada de LineEdit y habrá una línea adicional en la parte inferior del archivo como esta:

  from myapp.LineEdit import LineEdit 

No revisé todo tu código …

Sin embargo, creo que aunque haya reemplazado el widget en el diseño, window.txt_line_1 todavía apunta al objeto eliminado. Por lo tanto, en su procedimiento de reemplazo también deberá actualizar este atributo.

Entonces, añadiendo

 setattr(parent, obj_name, qt_obj); 

a _replace_qlineedit_from_gridlayout podría hacer el truco.