尝试复制pyside对象时遇到问题
我在使用 pyside 的时候遇到了一个相当让人沮丧的问题,希望能得到一些建议。
首先,给点背景
我用 Qt Designer 创建了一个简单的图形用户界面(GUI),然后用 pyside-uic.exe
工具把我的 .ui
文件转换成了相应的 Python 文件。
我使用的是 Python 3.3 和 pyside 1.2.1,搭配 Qt Designer 4(Qt 4.8.5)。
我用以下代码来启动我的 GUI:
class my_dialog(QMainWindow, my_gui.Ui_main_window):
def __init__(self, parent=None):
super(my_dialog, self).__init__(parent)
self.setupUi(self)
if ("__main__" == name):
app = QApplication(sys.argv)
main_dialog = my_dialog()
# (1)
main_dialog.show()
sys.exit(app.exec_())
我想要实现的目标
我的 GUI 有几个标签页,标签页的数量不是事先确定的,而是在运行时决定的。因此,我决定在 Qt Designer 中创建一个标签页,作为模板。
第一次需要添加标签页时,我会修改这个模板,如果需要额外的标签页,我打算 复制这个标签页,然后 适当地修改这个副本。
我遇到的问题
我的问题是,我似乎找不到复制标签页控件的方法。经过一些研究,我认为 copy
模块(或者 pickle
模块,见编辑部分)可能可以解决这个问题(以下代码插入在 (1) 处):
new_tab = copy.deepcopy(main_dialog.my_tab)
main_dialog.my_tabs.addTab(new_tab, "")
但是这引发了以下错误:
main_dialog.my_tabs.addTab(new_tab, "")
RuntimeError: Internal C++ object (Pyside.QtGui.QWidget) already deleted
我自己找到的一些信息
我在 Stack Overflow 和其他网站上看到,使用 pyside 时,可能会出现对象被回收的问题,因为在 Python 中没有对它们的引用。
然而,事实是,即使我把这段代码移动到 setupUi()
方法中,仍然会出现完全相同的错误。
值得注意的是,我可以毫无问题地访问 my_tab
对象并修改它的内容。
我可以在代码中从头创建另一个标签页,并且 main_dialog.my_tabs.addTab(new_tab, "")
在这种情况下工作得很好。
经过一些实验,我意识到问题可能出现在复制 my_tab
对象时。确实,复制我刚创建的标签页对象时,我发现尝试将副本添加到 GUI 标签页时也失败了,并且出现了同样的错误。
看起来复制在某种程度上失败了,或者对象由于某种原因被立即删除。这是我推测的……
我的问题
考虑到这一切,我想找到一种方法,要么成功复制对象,要么找到另一种方法,使用现有的 pyside 对象作为其他类似对象的模板。
当然,我 可以 把标签页的代码从生成的文件中取出来,自己编写一个 addTab()
方法。但是,我需要基于现有的 .ui
文件构建,而不是硬编码 GUI 元素。
编辑:
使用 pickle
时:
new_tab = pickle.loads(pickle.dumps(main_dialog.my_tab, -1))
我得到了以下错误:
new_tab = pickle.loads(pickle.dumps(main_dialog.my_tab, -1))
_pickle.PicklingError: Can't pickle <class 'Pyside.QtCore.SignalInstance'>: attribute lookup Pyside.QtCore.SignalInstance failed.
2 个回答
经过进一步研究,我认为用这些方法复制一个pyside对象是不可能的。
首先要注意的是,Qt没有内置的函数来克隆一个小部件,所以克隆应该使用像copy
、pickle
或marshal
这样的模块。
使用pickle
或marshal
会失败,因为发现这个对象是不可被序列化的。
虽然copy.copy
或copy.deepcopy
没有抛出任何警告或错误,但复制并没有发生,或者因为某种原因在之后被删除了。
当尝试将deepcopy
作为参数传递给addTab
时,没有抛出任何警告或错误,但程序在那一行停止并返回到Python命令提示符。由于在那一行停留了几秒钟后才退出,我猜测deepcopy
试图浏览对象的属性,但在某个点失败了。用copy
做同样的事情会导致之前提到的C++对象被删除
的错误,所以我只能推测deepcopy
操作确实失败了。
因此,我能给那些寻找类似答案的人的唯一建议就是实现自己的copy-widget
函数,这也是我现在要做的。
不过,我还是想理解为什么deepcopy
会这样悄无声息地失败,却导致程序执行结束。我开始了一个讨论线程,试图找到答案,在这里。
编辑:
我找到了这个问题的解决方案,满足我不硬编码GUI元素的要求,并使用Qt Designer来创建GUI和可重复元素的模板。希望能帮助到遇到同样问题的人:
这个想法是,使用Qt和pyside可以在运行时加载给定的.ui
文件,使用QUiLoader()
方法。因此,可以解析.ui
文件以提取给定的小部件(.ui
文件是简单的XML文件),并使用以下代码来使用它:
loader = QUiLoader()
ui_file = QFile("path_to_ui_file.ui")
ui_file.open(QFile.ReadOnly)
new_tab = loader.load(ui_file)
ui_file.close()
main_dialog.my_tabs.addTab(new_tab, "")
而且它有效!
关于上面例子的一些事情:
- 第二行假设你已经在文件
path_to_ui_file.ui
中隔离了你的小部件 - 在我的例子中,小部件是一个标签,当然它适用于你可能做的任何小部件,最后一行只是为了说明不再抛出错误
- 最后,这种方法的好处是允许你使用像Qt Designer这样的工具来开发你的GUI元素,即使涉及到一些变量,比如你想要多少个标签?
建议为你想要复制的小部件创建一个单独的ui文件,这个想法听起来很不错。虽然使用pyside-uic生成一个单独的gui模块来处理这个小部件,效果和用QUiLoader
一样好,实际上,这样做会稍微高效一些。
至于为什么用比如说copy.deepcopy
来克隆小部件不管用:这是因为它只会复制Python的外壳,而不会复制底层的C++对象。想要更详细的解释,可以查看这个回答。