I am having a rather frustrating problem using pyside and I would welcome any advice.
First, some context
I have created a simple GUI using Qt Designer and I have used pyside-uic.exe
onto my .ui
file in order to generate the associated Python file.
I am using Python 3.3 and pyside 1.2.1 with Qt Designer 4 (Qt 4.8.5).
I am using the following code to launch my 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_())
What I would like to achieve
My GUI features several tabs. The number of tabs is not pre-determined and is evaluated at run time. As a result, I've decided to create one tab in Qt Designer, to use as a template.
The first time I need to add a tab, I modify this template, and if I need any additionnal tab, I was planning on making a copy of that tab and then modify that copy appropriately.
The issue I have encountered
My problem is that I can't seem to find a way to copy the tab widget. After some research, I thought the copy
module (or the pickle
module, see edit) might do the trick (the following code was inserted at (1)):
new_tab = copy.deepcopy(main_dialog.my_tab)
main_dialog.my_tabs.addTab(new_tab, "")
But that triggered the following error:
main_dialog.my_tabs.addTab(new_tab, "")
RuntimeError: Internal C++ object (Pyside.QtGui.QWidget) already deleted
What I could find on my own
I have seen on SO and other sites that there may be issues, when using pyside, of objects being collected because there is no reference to them in Python.
The fact remains, however, that even if I move this code to very setupUi()
method in the .py
file generated by pyside, I still get the exact same error.
It is also worth noting that I am able to access the my_tab
object to modify its content without any trouble.
I am able to create another tab from scratch in my code and main_dialog.my_tabs.addTab(new_tab, "")
works perfectly fine in that context.
After some experimentations, I realized the problem probably occurs at the copy of the my_tab
object. Indeed, copying a tab object that I just created, I could see that trying to add the copy to the GUI tabs failed too, and with the same error.
It looks like the copy fails somehow, or the object is being immediately deleted for some reason. That's what I'm infering anyway...
My question
Considering all this, I would like to find a way to either succeed in the object copy, find another way to use an existing pyside object as template for other similar objects.
I could of course take the code for the tab out of the generated file and code my own addTab()
method. However, I am expected to build from an existing .ui
file and avoid hardcoding GUI elements.
EDIT:
When using pickle
:
new_tab = pickle.loads(pickle.dumps(main_dialog.my_tab, -1))
I get the following error:
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.
After some more research, I believe copying a pyside object using one of those techniques is not possible.
The first thing to note is that there is no built-in function to clone a Qt widget, so the cloning should be done using modules like
copy
,pickle
ormarshal
.Using
pickle
ormarshal
fails because the object is found to be not pickable.Whilst the
copy.copy
orcopy.deeepcopy
do not raise any warning/exception/error, the copy does not occur, or is deleted right afterwards for some reason.When trying to pass in the
deepcopy
as parameter toaddTab
, no warning/exception/error is thrown, yet the program stops at that line and exits back to the Python command prompt. The fact that it takes a few seconds on that line before exiting makes me assumedeepcopy
tries to browse through the object attributes and fails at some point. Doing the same withcopy
results in the previousC++ object deleted
error mentionned in the question, so I can only infer thedeepcopy
operation does fail.As a result, the only advice I could give someone looking for a similar answer is to implement their own
copy-widget
function, which is ultimately what I will do now.Still, I wish to understand how is that
deepcopy
fails like this, so silently, yet provoking the end of the execution. I started a thread to try and find an answer to this thereEDIT:
I found a solution for this problem, that respects my requirements of not hard-coding GUI elements and of using Qt Designer to create the GUI and templates for repeatable elements. I hope it helps anyone having the same issue:
The idea is that it is possible using Qt -- and pyside -- to load a given
.ui
file at run time, using theQUiLoader()
method. It is thus possible to parse the.ui
file to extract a given widget (.ui
files are simple XML files) and use the following code to use it:And it works!
A few things about the above example:
path_to_ui_file.ui
The suggestion of creating a separate ui file for the widget you want to copy seems a reasonable solution. Although it would seem that generating a separate gui module for the widget using pyside-uic would work just as well as using
QUiLoader
(in fact, it would be slightly more efficient).As for the question of why cloning widget using e.g.
copy.deepcopy
doesn't work: this is because it will only copy the python wrapper, and not the underlying C++ object. A somewhat fuller explanation can be found in this answer.