在运行时替换QWidget对象
在我的应用程序中,我需要把所有的QLineEdit元素替换成我自己定制的QLineEdit。为此,有几种不同的解决方案:
- 修改pyuic4生成的py文件,把所有的QLineEdit对象替换成我自己的LineEdit。这种方法并不是最好的,因为每次我运行pyuic4时,我对生成的输出文件所做的修改都会丢失。
- 写一个新类,递归地在我的窗口或对话框中查找QLineEdit控件类型,并用我自己的LineEdit替换它们。这种方法要好得多。它让我可以对其他对象也做同样的事情,或者根据需要扩展,而不必担心对话框或窗口的具体情况。
到目前为止,我已经写了一个模块(WidgetReplacer),它可以递归地查找QGridLayout中的项目,并检查是否有QLineEdit子控件。如果有,就用我的LineEdit替换掉。
问题是,当我尝试访问我的LineEdit对象时,出现了以下错误:
RuntimeError: wrapped C/C++ object of type QLineEdit has been deleted
而且如果我查看输出,我会发现被修改的QLineEdit对象仍然保留着旧的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>
问题
为什么会这样?我该如何在运行时替换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
如果我从主程序中打印我的LineEdit,我得到的是代表已删除的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
这是我的代码
对话框 ui和生成的文件可以从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 个回答
我没有仔细看你所有的代码……
不过我猜,即使你在布局中替换了小部件,window.txt_line_1
仍然指向已经删除的对象。
所以,在你的替换过程中,你也需要更新这个属性。
因此,添加
setattr(parent, obj_name, qt_obj);
到 _replace_qlineedit_from_gridlayout
可能就能解决问题。
在运行时不要替换小部件:在Qt Designer中提升小部件,这样在生成图形界面模块时,文本框会自动被你的自定义类替换。
下面是如何提升小部件以使用自定义类的方法:
在Qt Designer中,选择你想要替换的所有文本框,然后右键点击它们,选择“提升到...”。在弹出的对话框中,把“提升的类名”设置为“LineEdit”,然后把“头文件”设置为包含这个类的模块的Python导入路径(例如 myapp.LineEdit
)。接着点击“添加”,再点击“提升”,你会看到在对象检查器中,类名从“QLineEdit”变成了“LineEdit”。
现在,当你用pyuic重新生成你的ui模块时,你应该会看到它使用了你自定义的LineEdit类,文件底部会多出一行,类似这样:
from myapp.LineEdit import LineEdit