[prev in list] [next in list] [prev in thread] [next in thread] 

List:       pykde
Subject:    Re: Moving child widgets between QTabWidgets?
From:       Maurizio Berti <maurizio.berti () gmail ! com>
Date:       2023-01-26 2:15:41
Message-ID: CAPn+-XQVWM_zt2sMVV3N7O9SFGP1azZTppqTnFSG+=YnBQk-mg () mail ! gmail ! com
[Download RAW message or body]

There are some issues in your code and misconceptions in your understanding.

First of all, setParent(None) does *not* delete anything, at least, not
explicitly.

In fact, if you think about it, top level windows normally have no parent
at all (except for dialogs).
For instance, when you create your MyWindow, it doesn't have any parent,
nor it could: widgets can only have other widgets (or None) as parents.
It's not a chicken/egg dilemma: at least one window or widget will always
have no parent in any QApplication.

Also, setting the parent to None is actually one way to make a child widget
to a top level one, with the exception of explicitly setting the Window
flag (which is implicitly done for QDialogs, since the Dialog flag also
includes Window).

What actually matters is that, if a widget *has* a parent, setting it to
None *may* result in deleting it, but that only happens as long as no
object has ownership of it and (in python) its reference count becomes 0.
This is exactly what happens with a common beginner mistake, when trying to
create new windows:

def createWindow(self):
    newWindow = QDialog() # or any QWidget() without a given parent
    newWindow.show()

The above creates a new widget without a parent (so, a top level window),
but since there's no parent nor any reference left after the function
returns, the widget is destroyed, just like it would happen with any local
variable in Python. The window will be shown only for a fraction of a
second (or not shown at all), and will be destroyed immediately.

Remember that PyQt (like PySide) is a *binding* to Qt. We use its API from
Python, but objects are created on the "C++ side". Python has a garbage
collection system that (theoretically) deletes the object whenever its
reference count is 0, but since the python reference of Qt objects is *not*
the object, deleting all references won't necessarily delete the Qt object.
Note that by "Qt object", I mean QObject, and, by extension, any Qt object
that inherits from QObject (including QWidget and all its subclasses).

In fact, the following will behave quite differently than the above:

def createWindow(self):
    newWindow = QDialog(self) # similar behavior also with QMainWindow
    newWindow.show()

The new window will not be destroyed, even if we didn't keep a persistent
reference to it.
Even the following won't change anything:

def createWindow(self):
    self.newWindow = QDialog(self)
    self.newWindow.show()
    self.newWindow = None
    # similarly:
    del self.newWindow

This, instead, will be practically the same as the first createWindow()
code above:

def createWindow(self):
    newWindow = QDialog(self)
    newWindow.show()
    QTimer.singleShot(0, lambda: newWindow.setParent(None))

Remember that setParent() resets the window flags and hides the widget. The
following will be *visually* the same, but will not delete the dialog:

def createWindow(self):
    self.newWindow = QDialog(self)
    self.newWindow.show()
    QTimer.singleShot(0, lambda: self.newWindow.setParent(None))

Note that there is no absolute guarantee about the order or timing of
QObject deletions in PyQt. The general rule is that a QObject will be
persistent as long as at least one python reference to it exists *or* it is
owned by another QObject.

There's a specific chapter in the PyQt documentation:
https://www.riverbankcomputing.com/static/Docs/PyQt5/gotchas.html#garbage-collection
Consider the following examples.

This will *not* delete the child, just its python reference:

parent = QObject()
child = QObject(parent)
del child

This will *not* delete the child, but just change its parent:

parent = QObject()
child = QObject(parent)
child.setParent(None)

This will not *immediately* delete the child:

parent = QObject()
child = QObject(parent)
child.deleteLater()
assert child in parent.children() # True

The above will only work within the same call; it will not work with the
interactive shell, if the input hook is active and a QCoreApplication
exists (see
https://www.riverbankcomputing.com/static/Docs/PyQt5/python_shell.html ).
As the documentation explains, deleteLater() doesn't immediately delete the
object, but will *schedule its deletion*.
This will, in fact, behave differently instead (assuming a Q*Application
instance was previously created):

parent = QObject()
child = QObject(parent)
child.deleteLater()
QCoreApplication.processEvents() # process the current event queue,
including scheduled deletions
assert child in parent.children() # False


Now, back to your case.
As the documentation explains, removeTab() just removes the tab ("The page
widget itself is not deleted"), but it doesn't change the ownership (the
documentation always notes it when that happens), meaning that removing the
tab will keep the widget as a child of the tab widget.

In your code, you reparent all children of QSplitter to None, including the
frame used as parent of the tab widget. That frame was declared as a local
variable in the __init__, so the only thing that keeps it alive is its
parent (the splitter): setting the parent to None clears that, and since
there's no other reference left in python, it's destroyed along with any of
its children.

At the same time, the tab widget will still exist, because self.tab_widget
keeps it alive. Unfortunately, you destroy that reference right after
that (self.tab_widget
= None), and all the "removed" tab widgets along with it, since they are
still owned by it.

Depending on your needs, there are various possibilities. If you want to
delete all children of QSplitter before adding a new one, you could do that
in three steps:

- get a list of the current children of QSplitter;
- add the new one;
- reparent the previously collected widgets to None;

But, actually, all the above can be done much more easily.
Note that using a reverse list of indexes to transfer Qt items isn't really
effective, as you'd need to reverse again the resulting list (in fact,
after using the change above, your code will add tabs in reverse order).
Just always call removeTab(0) for every iteration of the loop (or use a
while loop until the tab widget is empty).
Even better, directly add the pages to the new tab widget in the same
order; since reparenting causes a child remove event for the original
parent, they will be automatically moved while you're getting them, meaning
that always using the 0 index is perfectly safe, because the removal order
is the same as the insertion one.

    def switch(self):
        old_tab_widget = self.tab_widget
        self.tab_widget = QTabWidget()
        while old_tab_widget.count():
            # this will automatically reparent the page, thus removing it
from
            # the "old" tab widget; unlike deleteLater, reparenting happens
immediately
            self.tab_widget.addTab(old_tab_widget.widget(0),
old_tab_widget.tabText(0))

        splitter = self.centralWidget()
        for i in range(splitter.count()):
            # schedule widgets for removal, they will still exist within
this function call
            splitter.widget(i).deleteLater()

        splitter.addWidget(self.tab_widget)

Note that the second part of the code above (the one including the for
loop) can even be put at the very beginning of the function: since
deleteLater() is only scheduling the deletion, the widgets will still be
accessible within the lifetime of the function (as long as
QApplication.processEvents() is not called, obviously)

Hope this helps you to clarify things.

Best regards,
Maurizio


Il giorno mer 18 gen 2023 alle ore 14:49 Matic Kukovec <
kukovecmatic@hotmail.com> ha scritto:

> Hi Maurizio,
>
> You are correct, I apologize for the mistakes.
> I managed to get a self-contained example which throws the same error as
> my development code:
>
>
>     from PyQt5.QtCore import *
>     from PyQt5.QtGui import *
>     from PyQt5.QtWidgets import *
>
>     class MyWindow(QMainWindow):
>         def __init__(self):
>             super().__init__()
>             splitter = QSplitter(self)
>             self.setCentralWidget(splitter)
>
>             # Frame & frame
>             frame = QFrame(self)
>             splitter.addWidget(frame)
>             layout = QVBoxLayout()
>             frame.setLayout(layout)
>
>             # Button
>             button = QPushButton("Switch")
>             button.clicked.connect(self.switch)
>             layout.addWidget(button)
>
>             # Tab widget
>             tab_widget = QTabWidget()
>             layout.addWidget(tab_widget)
>             self.tab_widget = tab_widget
>
>             # Red
>             w = QWidget(tab_widget)
>             w.setStyleSheet("background: red;")
>             tab_widget.addTab(w, "Red")
>
>             # Green
>             w = QWidget(tab_widget)
>             w.setStyleSheet("background: green;")
>             tab_widget.addTab(w, "Green")
>
>             # TreeView
>             tv = QTreeView(tab_widget)
>             tv.horizontalScrollbarAction(1)
>
> tv.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
>             tree_model = QStandardItemModel()
>             tree_model.setHorizontalHeaderLabels(["TREE EXPLORER"])
>             tv.header().hide()
>             tv.setModel(tree_model)
>             for i in range(100):
>                 item = QStandardItem("ITEM {}".format(i))
>
>             tv.setUniformRowHeights(True)
>
>             tab_widget.addTab(tv, "TreeView")
>
>         def switch(self, *args):
>             widgets = []
>             old_tab_widget = self.tab_widget
>             for i in reversed(range(old_tab_widget.count())):
>                 widgets.append((old_tab_widget.widget(i),
> old_tab_widget.tabText(i)))
>                 old_tab_widget.removeTab(i)
>
>             for i in reversed(range(self.centralWidget().count())):
>                 self.centralWidget().widget(i).setParent(None)
>             old_tab_widget = None
>             self.tab_widget = None
>
>             new_tab_widget = QTabWidget()
>             for widget, tab_text in widgets:
>                 new_tab_widget.addTab(widget, tab_text) # <- ERROR THROWN
> HERE
>
>             self.centralWidget().addWidget(new_tab_widget)
>             self.tab_widget = new_tab_widget
>
>
>     if __name__ == '__main__':
>         import sys
>
>         app = QApplication(sys.argv)
>         w = MyWindow()
>
>         w.resize(640, 480)
>         w.show()
>
>         sys.exit(app.exec_())
>
>
> Matic
>
> ------------------------------
> *From:* Maurizio Berti <maurizio.berti@gmail.com>
> *Sent:* Tuesday, January 17, 2023 10:25 PM
> *To:* Matic Kukovec <kukovecmatic@hotmail.com>
> *Cc:* PyQt@riverbankcomputing.com <pyqt@riverbankcomputing.com>
> *Subject:* Re: Moving child widgets between QTabWidgets?
>
> If it throws that error, it's because the widgets (or their parent) have
> been deleted.
> Unfortunately, your code is insufficient to actually understand where the
> issue is, and, by the way, it also has important issues: first of all the
> second for loop is wrong (new_tab_widget is a widget, so that will throw an
> exception), and using removeTab with the number of a counter is wrong, as
> you'll always get even numbered items, if you want to remove all widgets,
> always use removeTab(0).
> I suggest you to provide a valid minimal reproducible example.
>
> Maurizio
>
> Il giorno mar 17 gen 2023 alle ore 14:11 Matic Kukovec <
> kukovecmatic@hotmail.com> ha scritto:
>
> HI,
>
> What is the PyQt idiomatic way of moving a widget from one QTabWidget to
> another?
> I have tried this:
>
>
> widgets = []
> for i in range(old_tab_widget.count()):
>     widgets.append((old_tab_widget.widget(i), old_tab_widget.tabText(i)))
>     old_tab_widget.removeTab(i)
>
> for widget, tab_text in new_tab_widget:
>     new_tab_widget.addTab(widget, tab_text)
>
>
> but this throws errors for example for QTreeView widgets like this:
>
> ...
>     new_tab_widget.addTab(widget, tab_text)
> RuntimeError: wrapped C/C++ object of type TreeExplorer has been deleted
>
> (TreeExplorer is my subclassed QTreeView)
>
> Thanks
> Matic
>
>
>
> --
> È difficile avere una convinzione precisa quando si parla delle ragioni
> del cuore. - "Sostiene Pereira", Antonio Tabucchi
> http://www.jidesk.net
>


-- 
È difficile avere una convinzione precisa quando si parla delle ragioni del
cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net

[Attachment #3 (text/html)]

<div dir="ltr"><div>There are some issues in your code and misconceptions in your \
understanding.</div><div><br></div><div>First of all, <span \
style="font-family:monospace">setParent(None)</span> does *not* delete anything, at \
least, not explicitly.</div><div><br></div><div>In fact, if you think about it, top \
level windows normally have no parent at all (except for dialogs).</div><div>For \
instance, when you create your MyWindow, it doesn&#39;t have any parent, nor it \
could: widgets can only have other widgets (or None) as parents. It&#39;s not a \
chicken/egg dilemma: at least one window or widget will always have no parent in any \
QApplication.<br></div><div><br></div><div>Also, setting the parent to None is \
actually one way to make a child widget to a top level one, with the exception of \
explicitly setting the <span style="font-family:monospace">Window</span> flag (which \
is implicitly done for QDialogs, since the <span \
style="font-family:monospace">Dialog</span> flag also includes <span \
style="font-family:monospace">Window</span>).<br></div><div><br></div><div>What \
actually matters is that, if a widget <i>has</i> a parent, setting it to None *may* \
result in deleting it, but that only happens as long as no object has ownership of it \
and (in python) its reference count becomes 0. This is exactly what happens with a \
common beginner mistake, when trying to create new \
windows:</div><div><br></div><div><span style="font-family:monospace">def \
createWindow(self):</span></div><div><span style="font-family:monospace">       \
newWindow = QDialog() # or any QWidget() without a given \
parent<br></span></div><div><span style="font-family:monospace">       \
newWindow.show()</span></div><div><br></div><div>The above creates a new widget \
without a parent (so, a top level window), but since there&#39;s no parent nor any \
reference left after the function returns, the widget is destroyed, just like it \
would happen with any local variable in Python. The window will be shown only for a \
fraction of a second (or not shown at all), and will be destroyed \
immediately.<br></div><div><br></div><div>Remember that PyQt (like PySide) is a \
*binding* to Qt. We use its API from Python, but objects are created on the &quot;C++ \
side&quot;. Python has a garbage collection system that (theoretically) deletes the \
object whenever its reference count is 0, but since the python reference of Qt \
objects is *not* the object, deleting all references won&#39;t necessarily delete the \
Qt object.<br></div><div>Note that by &quot;Qt object&quot;, I mean QObject, and, by \
extension, any Qt object that inherits from QObject (including QWidget and all its \
subclasses).<br></div><div><br></div><div>In fact, the following will behave quite \
differently than the above:</div><div><br></div><div><span \
style="font-family:monospace">def createWindow(self):</span></div><div><span \
style="font-family:monospace">       newWindow = QDialog(self) # similar behavior \
also with QMainWindow<br></span></div><div><span style="font-family:monospace">       \
newWindow.show()</span></div><div><br></div><div>The new window will not be \
destroyed, even if we didn&#39;t keep a persistent reference to it.</div><div>Even \
the following won&#39;t change anything:</div><div><br></div><div><span \
style="font-family:monospace">def createWindow(self):</span></div><div><span \
style="font-family:monospace">       self.newWindow = \
QDialog(self)</span></div><div><div><span style="font-family:monospace"><span \
style="font-family:monospace">       self.newWindow.show()</span></span></div><span \
style="font-family:monospace">       self.newWindow = None</span></div><span \
style="font-family:monospace">       # similarly:</span><div><span \
style="font-family:monospace">       del self.newWindow</span></div><br><div>This, \
instead, will be practically the same as the first createWindow() code \
above:</div><div><br></div><div><div><span style="font-family:monospace">def \
createWindow(self):</span></div><div><span style="font-family:monospace">       \
newWindow = QDialog(self)</span><div><span \
style="font-family:monospace"></span></div><div><div><span \
style="font-family:monospace"><span style="font-family:monospace">       \
newWindow.show()</span></span><span style="font-family:monospace"></span><br><span \
style="font-family:monospace"></span></div></div></div><div><span \
style="font-family:monospace"></span></div></div><div><div><span \
style="font-family:monospace">       QTimer.singleShot(0, lambda: \
newWindow.setParent(None))</span></div></div><div><br></div><div>Remember that \
setParent() resets the window flags and hides the widget. The following will be \
<i>visually</i> the same, but will not delete the \
dialog:</div><div><br></div><div><div><div><span style="font-family:monospace">def \
createWindow(self):</span></div><div><span style="font-family:monospace">       \
self.newWindow = QDialog(self)</span><div><span \
style="font-family:monospace"></span></div><div><div><span \
style="font-family:monospace"><span style="font-family:monospace">       \
self.newWindow.show()</span></span><span \
style="font-family:monospace"></span><br><span \
style="font-family:monospace"></span></div></div></div><div><span \
style="font-family:monospace"></span></div></div><div><div><span \
style="font-family:monospace">       QTimer.singleShot(0, lambda: \
self.newWindow.setParent(None))</span></div></div></div><div><br></div><div>Note that \
there is no absolute guarantee about the order or timing of QObject deletions in \
PyQt. The general rule is that a QObject will be persistent as long as at least one \
python reference to it exists *or* it is owned by another \
QObject.</div><div><br></div><div>There&#39;s a specific chapter in the PyQt \
documentation: <a href="https://www.riverbankcomputing.com/static/Docs/PyQt5/gotchas.h \
tml#garbage-collection">https://www.riverbankcomputing.com/static/Docs/PyQt5/gotchas.html#garbage-collection</a></div><div>Consider \
the following examples.</div><div><br></div><div><div><div>This will *not* delete the \
child, just its python reference:</div><div><br></div><div></div></div><span \
style="font-family:monospace">parent = QObject()</span></div><div><span \
style="font-family:monospace">child = QObject(parent)</span></div><div><span \
style="font-family:monospace">del \
child</span></div><div><br></div><div><div><div>This will *not* delete the child, but \
just change its parent:</div><div><br></div></div></div><span \
style="font-family:monospace">parent = QObject()</span><div><div><span \
style="font-family:monospace">child = QObject(parent)</span></div></div><div><span \
style="font-family:monospace">child.setParent(None)</span></div><div><br></div><div>This \
will not *immediately* delete the child:</div><div><br></div><div><span \
style="font-family:monospace">parent = QObject()</span><div><div><span \
style="font-family:monospace">child = QObject(parent)</span></div><span \
style="font-family:monospace">child.deleteLater()</span><div><span \
style="font-family:monospace">assert child in parent.children() # \
True</span></div><div><br></div><div><div>The above will only work within the same \
call; it will not work with the  interactive shell, if the input hook is active and a \
QCoreApplication  exists (see 
<a href="https://www.riverbankcomputing.com/static/Docs/PyQt5/python_shell.html">https://www.riverbankcomputing.com/static/Docs/PyQt5/python_shell.html</a> \
 ). As the documentation explains, <span \
style="font-family:monospace">deleteLater()</span> doesn&#39;t immediately delete the \
object, but will <i>schedule its deletion</i>.<br></div><div></div>This will, in \
fact, behave differently instead (assuming a Q*Application instance was previously \
created):<br></div><div><br></div><div><span style="font-family:monospace">parent = \
QObject()</span><div><div><span style="font-family:monospace">child = \
QObject(parent)</span></div><span \
style="font-family:monospace">child.deleteLater()</span></div><div><span \
style="font-family:monospace">QCoreApplication.processEvents() # process the current \
event queue, including scheduled deletions<br></span></div><div><div><span \
style="font-family:monospace">assert child in parent.children() # \
False</span></div><div><br></div><div><br></div></div></div></div></div>Now, back to \
your case.<div><div>As the documentation explains, <span \
style="font-family:monospace">removeTab()</span> just removes the tab (&quot;The page \
widget itself is not deleted&quot;), but it doesn&#39;t change the ownership (the \
documentation always notes it when that happens), meaning that removing the tab will \
keep the widget as a child of the tab widget.<br></div><div><br></div><div>In your \
code, you reparent all children of QSplitter to None, including the frame used as \
parent of the tab widget. That frame was declared as a local variable in the \
__init__, so the only thing that keeps it alive is its parent (the splitter): setting \
the parent to None clears that, and since there&#39;s no other reference left in \
python, it&#39;s destroyed along with any of its \
children.</div><div><br></div><div>At the same time, the tab widget will still exist, \
because self.tab_widget keeps it alive. Unfortunately, you destroy that reference \
right after that (<span style="font-family:monospace">self.tab_widget = None</span>), \
and all the &quot;removed&quot; tab widgets along with it, since they are still owned \
by it.</div><div><br></div><div>Depending on your needs, there are various \
possibilities. If you want to delete all children of QSplitter before adding a new \
one, you could do that in three steps:</div><div><br></div><div>- get a list of the \
current children of QSplitter;</div><div>- add the new one;</div><div>- reparent the \
previously collected widgets to None;</div><div><br></div><div>But, actually, all the \
above can be done much more easily.</div><div>Note that using a reverse list of \
indexes to transfer Qt items isn&#39;t really effective, as you&#39;d need to reverse \
again the resulting list (in fact, after using the change above, your code will add \
tabs in reverse order).</div><div>Just always call <span \
style="font-family:monospace">removeTab(0)</span> for every iteration of the loop (or \
use a while loop until the tab widget is empty).</div><div>Even better, directly add \
the pages to the new tab widget in the same order; since reparenting causes a child \
remove event for the original parent, they will be automatically moved while \
you&#39;re getting them, meaning that always using the 0 index is perfectly safe, \
because the removal order is the same as the insertion \
one.<br></div><div><br></div><div><span style="font-family:monospace">      def \
switch(self):<br>            old_tab_widget = self.tab_widget<br>            \
self.tab_widget = QTabWidget()</span></div><span style="font-family:monospace">       \
while old_tab_widget.count():</span><br><span \
style="font-family:monospace"></span><div><span style="font-family:monospace">        \
# this will automatically reparent the page, thus removing it from<br>                \
# the &quot;old&quot; tab widget; unlike deleteLater, reparenting happens \
immediately<br>                  self.tab_widget.addTab(old_tab_widget.widget(0), \
old_tab_widget.tabText(0))<br></span></div><div><br><span \
style="font-family:monospace"></span></div><div><div><span \
style="font-family:monospace">               splitter = <span \
style="font-family:monospace">self.centralWidget()</span></span></div><div><span \
style="font-family:monospace">            for i in \
range(splitter.count()):</span></div><div><span style="font-family:monospace">        \
# schedule widgets for removal, they will still exist within this function \
call<br></span></div><div><span style="font-family:monospace">                  <span \
style="font-family:monospace">splitter</span>.widget(i).deleteLater()<br><br></span></div><span \
style="font-family:monospace">            splitter<span \
style="font-family:monospace"></span>.addWidget(self.tab_widget)</span></div><div><br></div><div>Note \
that the second part of the code above (the one including the for loop) can even be \
put at the very beginning of the function: since <span \
style="font-family:monospace">deleteLater()</span> is only scheduling the deletion, \
the widgets will still be accessible within the lifetime of the function (as long as \
<span style="font-family:monospace">QApplication.processEvents()</span> is not \
called, obviously)<br><br></div><div>Hope this helps you to clarify \
things.</div><div><br></div><div>Best \
regards,</div><div>Maurizio<br></div><div><br></div></div></div><br><div \
class="gmail_quote"><div dir="ltr" class="gmail_attr">Il giorno mer 18 gen 2023 alle \
ore 14:49 Matic Kukovec &lt;<a \
href="mailto:kukovecmatic@hotmail.com">kukovecmatic@hotmail.com</a>&gt; ha \
scritto:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px \
0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div \
class="msg-7814069801704466625">




<div dir="ltr">
<div><span style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">Hi \
Maurizio,</span></div> <div><span \
style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)"><br>
 </span></div>
<div><span style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">You \
are correct, I apologize for the mistakes.</span></div> <div><span \
style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">I \
managed to get a self-contained example which throws the same error as my development \
code:</span></div> <blockquote style="margin-top:0px;margin-bottom:0px">
<div><span style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 <div style="color:black;font-size:12pt;background-color:white">
<span><br>
</span></div>
</span></div>
</blockquote>
<span style="color:rgb(0,0,0);font-size:10pt">      from PyQt5.QtCore import *</span>
<div><span style="color:rgb(0,0,0);font-size:10pt">      from PyQt5.QtGui import \
*</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">      from \
PyQt5.QtWidgets import *</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">      </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">      class \
MyWindow(QMainWindow):</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">            def __init__(self):</span></div> \
<div><span style="color:rgb(0,0,0);font-size:10pt">                  \
super().__init__()</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">   \
splitter = QSplitter(self)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  \
self.setCentralWidget(splitter)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">                  # Frame &amp; \
frame</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">                \
frame = QFrame(self)</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt"> \
splitter.addWidget(frame)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  layout = \
QVBoxLayout()</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">        \
frame.setLayout(layout)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">                  # \
Button</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">               \
button = QPushButton(&quot;Switch&quot;)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  \
button.clicked.connect(self.switch)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  \
layout.addWidget(button)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">                  # Tab \
widget</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">               \
tab_widget = QTabWidget()</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  \
layout.addWidget(tab_widget)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  self.tab_widget = \
tab_widget</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">           \
 </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">                  # \
Red</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">                  \
w = QWidget(tab_widget)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  \
w.setStyleSheet(&quot;background: red;&quot;)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  tab_widget.addTab(w, \
&quot;Red&quot;)</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">     \
 </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">                  # \
Green</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">                \
w = QWidget(tab_widget)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  \
w.setStyleSheet(&quot;background: green;&quot;)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  tab_widget.addTab(w, \
&quot;Green&quot;)</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">   \
 </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">                  # \
TreeView</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">             \
tv = QTreeView(tab_widget)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  \
tv.horizontalScrollbarAction(1)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  \
tv.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)</span></div> \
<div><span style="color:rgb(0,0,0);font-size:10pt">                  tree_model = \
QStandardItemModel()</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt"> \
tree_model.setHorizontalHeaderLabels([&quot;TREE EXPLORER&quot;])</span></div> \
<div><span style="color:rgb(0,0,0);font-size:10pt">                  \
tv.header().hide()</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">   \
tv.setModel(tree_model)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  for i in \
range(100):</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">          \
item = QStandardItem(&quot;ITEM {}&quot;.format(i))</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">                  \
tv.setUniformRowHeights(True)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">                  \
tab_widget.addTab(tv, &quot;TreeView&quot;)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">            </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">            def switch(self, \
*args):</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">              \
widgets = []</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">         \
old_tab_widget = self.tab_widget</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  for i in \
reversed(range(old_tab_widget.count())):</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                        \
widgets.append((old_tab_widget.widget(i), old_tab_widget.tabText(i)))</span></div> \
<div><span style="color:rgb(0,0,0);font-size:10pt">                        \
old_tab_widget.removeTab(i)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">                  for i in \
reversed(range(self.centralWidget().count())):</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                        \
self.centralWidget().widget(i).setParent(None)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  old_tab_widget = \
None</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">                 \
self.tab_widget = None</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">                  new_tab_widget = \
QTabWidget()</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">         \
for widget, tab_text in widgets:</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                        \
new_tab_widget.addTab(widget, tab_text) # &lt;- ERROR THROWN HERE<br> </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">                 
</span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">                  \
self.centralWidget().addWidget(new_tab_widget)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">                  self.tab_widget = \
new_tab_widget</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">       \
 </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">     
</span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">      if __name__ == \
&#39;__main__&#39;:</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">  \
import sys</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">     
</span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">            app = \
QApplication(sys.argv)</span></div> <div><span \
style="color:rgb(0,0,0);font-size:10pt">            w = MyWindow()</span></div> \
<div><span style="color:rgb(0,0,0);font-size:10pt">      </span></div>
<div><span style="color:rgb(0,0,0);font-size:10pt">            w.resize(640, \
480)</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">            \
w.show()</span></div> <div><span style="color:rgb(0,0,0);font-size:10pt">     
</span></div>
<span style="color:rgb(0,0,0);font-size:10pt">            \
sys.exit(app.exec_())</span> <blockquote style="margin-top:0px;margin-bottom:0px">
<div><span style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 <div><span style="color:black"></span></div>
</span></div>
</blockquote>
<div><span style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)"><br>
 </span></div>
<div><span style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">Matic<br>
 </span></div>
<div><span style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)"><br>
 </span></div>
<div id="m_5972996877237112175appendonsend"></div>
<hr style="display:inline-block;width:98%">
<div id="m_5972996877237112175divRplyFwdMsg" dir="ltr"><font style="font-size:11pt" \
face="Calibri, sans-serif" color="#000000"><b>From:</b> Maurizio Berti &lt;<a \
href="mailto:maurizio.berti@gmail.com" \
target="_blank">maurizio.berti@gmail.com</a>&gt;<br> <b>Sent:</b> Tuesday, January \
17, 2023 10:25 PM<br> <b>To:</b> Matic Kukovec &lt;<a \
href="mailto:kukovecmatic@hotmail.com" \
target="_blank">kukovecmatic@hotmail.com</a>&gt;<br> <b>Cc:</b> <a \
href="mailto:PyQt@riverbankcomputing.com" \
target="_blank">PyQt@riverbankcomputing.com</a> &lt;<a \
href="mailto:pyqt@riverbankcomputing.com" \
target="_blank">pyqt@riverbankcomputing.com</a>&gt;<br> <b>Subject:</b> Re: Moving \
child widgets between QTabWidgets?</font> <div>  </div>
</div>
<div>
<div dir="ltr">
<div>If it throws that error, it&#39;s because the widgets (or their parent) have \
been deleted.</div> <div>Unfortunately, your code is insufficient to actually \
understand where the issue is, and, by the way, it also has important issues: first \
of all the second for loop is wrong (new_tab_widget is a widget, so that will throw \
an exception), and using removeTab  with the number of a counter is wrong, as \
you&#39;ll always get even numbered items, if you want to remove all widgets, always \
use removeTab(0).<br> </div>
<div>I suggest you to provide a valid minimal reproducible example.</div>
<div><br>
</div>
<div>Maurizio<br>
</div>
</div>
<br>
<div>
<div dir="ltr">Il giorno mar 17 gen 2023 alle ore 14:11 Matic Kukovec &lt;<a \
href="mailto:kukovecmatic@hotmail.com" \
target="_blank">kukovecmatic@hotmail.com</a>&gt; ha scritto:<br> </div>
<blockquote style="margin:0px 0px 0px 0.8ex;border-left:1px solid \
rgb(204,204,204);padding-left:1ex"> <div>
<div dir="ltr">
<div><span style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">HI,</span></div>
 <div><br>
</div>
<div>What is the PyQt idiomatic way of moving a widget from one QTabWidget to \
another?</div> <div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 I have tried this:</div>
<blockquote style="margin-top:0px;margin-bottom:0px">
<div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 <span style="font-family:Calibri,Helvetica,sans-serif;color:rgb(0,0,0)"><br>
</span></div>
<div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 <span style="font-family:Calibri,Helvetica,sans-serif;color:rgb(0,0,0)">widgets = \
[]</span><br> </div>
<div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 <span style="font-family:Calibri,Helvetica,sans-serif;color:rgb(0,0,0)">for i in \
range(old_tab_widget.count()):</span></div> <div \
style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 <div><span style="font-family:Calibri,Helvetica,sans-serif;color:rgb(0,0,0)">       \
widgets.append((old_tab_widget.widget(i), old_tab_widget.tabText(i)))</span></div> \
</div> <div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 <span style="font-family:Calibri,Helvetica,sans-serif;color:rgb(0,0,0)">      \
old_tab_widget.removeTab(i)</span></div> <div \
style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 <br>
</div>
<span style="font-family:Calibri,Helvetica,sans-serif;color:rgb(0,0,0)">for widget, \
tab_text in new</span><span \
style="font-family:Calibri,Helvetica,sans-serif;color:rgb(0,0,0)">_tab_widget</span><span \
style="font-family:Calibri,Helvetica,sans-serif;color:rgb(0,0,0)">:</span> <div><span \
style="font-family:Calibri,Helvetica,sans-serif;color:rgb(0,0,0)">       </span><span \
style="font-family:Calibri,Helvetica,sans-serif;color:rgb(0,0,0)">new_tab_widget</span><span \
style="font-family:Calibri,Helvetica,sans-serif;color:rgb(0,0,0)">.addTab(widget, \
tab_text)</span></div> </blockquote>
<div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 <br>
</div>
<div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 but this throws errors for example for QTreeView widgets like this:</div>
<blockquote style="margin-top:0px;margin-bottom:0px">
<div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 <span></span><span>...<br>
</span></div>
<div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 <span>       new_tab_widget</span><span></span>.addTab(widget, tab_text)<br>
</div>
<div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
                
RuntimeError: wrapped C/C++ object of type TreeExplorer has been deleted</div>
</blockquote>
<div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 (TreeExplorer is my subclassed QTreeView)<br>
</div>
<div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 <br>
</div>
<div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 Thanks</div>
<div style="font-family:Calibri,Helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0);background-color:rgb(255,255,255)">
 Matic<br>
</div>
</div>
</div>
</blockquote>
</div>
<br clear="all">
<br>
-- <br>
<div dir="ltr">È difficile avere una convinzione precisa quando si parla delle \
ragioni del cuore. - &quot;Sostiene Pereira&quot;, Antonio Tabucchi<br> <a \
href="http://www.jidesk.net" target="_blank">http://www.jidesk.net</a></div> </div>
</div>

</div></blockquote></div><br clear="all"><br>-- <br><div dir="ltr" \
class="gmail_signature">È difficile avere una convinzione precisa quando si parla \
delle ragioni del cuore. - &quot;Sostiene Pereira&quot;, Antonio Tabucchi<br><a \
href="http://www.jidesk.net" target="_blank">http://www.jidesk.net</a></div>



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic