尝试复制pyside对象时遇到问题

2 投票
2 回答
2828 浏览
提问于 2025-04-18 00:07

我在使用 pyside 的时候遇到了一个相当让人沮丧的问题,希望能得到一些建议。

首先,给点背景

我用 Qt Designer 创建了一个简单的图形用户界面(GUI),然后用 pyside-uic.exe 工具把我的 .ui 文件转换成了相应的 Python 文件。

我使用的是 Python 3.3pyside 1.2.1,搭配 Qt Designer 4Qt 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 个回答

1

经过进一步研究,我认为用这些方法复制一个pyside对象是不可能的。

首先要注意的是,Qt没有内置的函数来克隆一个小部件,所以克隆应该使用像copypicklemarshal这样的模块。

使用picklemarshal会失败,因为发现这个对象是不可被序列化的

虽然copy.copycopy.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元素,即使涉及到一些变量,比如你想要多少个标签?
1

建议为你想要复制的小部件创建一个单独的ui文件,这个想法听起来很不错。虽然使用pyside-uic生成一个单独的gui模块来处理这个小部件,效果和用QUiLoader一样好,实际上,这样做会稍微高效一些。

至于为什么用比如说copy.deepcopy来克隆小部件不管用:这是因为它只会复制Python的外壳,而不会复制底层的C++对象。想要更详细的解释,可以查看这个回答

撰写回答