PyQt4: замена объектов QWidget во время выполнения

В моем приложении я должен заменить все элементы QLineEdit настроенными QLineEdit. Для этого есть разные решения:

  1. Измените сгенерированный py-файл из pyuic4 и замените все объекты QLineEdit на мой единственный LineEdit. Это решение на самом деле не самое лучшее, потому что каждый раз, когда я запускаю pyuic4, я теряю внесенную мной модификацию в сгенерированный выходной файл.
  2. Напишите новый класс, который рекурсивно ищет в моем окне или диалоговом окне типы виджетов QLineEdit, и замените их на мой единственный LineEdit. Это решение намного лучше. Это позволяет мне делать то же самое и с другими объектами или расширять его так, как я хочу, не заботясь о диалоге или окне.

До сих пор я мог написать модуль (WidgetReplacer), который рекурсивно ищет элементы QGridLayout и ищет, есть ли потомки QLineEdit. Если да, то замени его на мой.

Проблема в том, что когда я пытаюсь получить доступ к одному из моих объектов LineEdit, я получаю следующую ошибку:

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

И если я посмотрю на свои выходные данные, то обнаружу, что модифицированный объект QLineEdit имеет старый идентификатор:

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>

Вопрос

Почему это случилось? Как я могу заменить QWidgets во время выполнения?


Обновить

Вот некоторые дополнительные детали, касающиеся обёртывания объектов: Внутри модуля WidgetReplace, если я дам дамп объектов QEditLine и LineEdit, я получу:

<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

а также

<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

Вместо этого, если я дам дамп редактирования строки из основного, я получаю follow, которые представляют удаленный объект QLineEdit:

<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

Вот мой код

Диалог Пользовательский интерфейс и сгенерированный файл могут быть загружены из DropBox dialog.ui и dialog.py

главный

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

2 ответа

Решение

Не заменяйте виджеты во время выполнения: продвигайте виджеты в Qt Designer, чтобы редактирование строк автоматически заменялось вашим пользовательским классом при создании модуля GUI.

Вот как можно продвигать виджет для использования собственного класса:

В Qt Designer выберите все правки строк, которые вы хотите заменить, затем щелкните их правой кнопкой мыши и выберите "Повышать до…". В диалоговом окне установите "Имя продвигаемого класса" на "LineEdit", а в "Файл заголовка" укажите путь импорта python для модуля, который содержит этот класс (например, myapp.LineEdit). Затем нажмите "Добавить" и "Продвинуть", и вы увидите изменение класса с "QLineEdit" на "LineEdit" на панели "Инспектор объектов".

Теперь, когда вы заново сгенерируете свой модуль пользовательского интерфейса с помощью pyuic, вы должны увидеть, что он использует ваш собственный класс LineEdit, и в нижней части файла будет дополнительная строка, например:

    from myapp.LineEdit import LineEdit

Я не прошел весь ваш код...

Я думаю, что, хотя вы заменили виджет в макете, window.txt_line_1 все еще указывает на удаленный объект. Таким образом, в вашей процедуре замены вам также придется обновить этот атрибут.

Итак, добавляя

setattr(parent, obj_name, qt_obj);

в _replace_qlineedit_from_gridlayout может сделать трюк.

Другие вопросы по тегам