1

I want to move a QWidget from one position to another. I tried the following:

import sys
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QTableWidget, QLabel, QPushButton, QVBoxLayout, QWidget
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.table = QTableWidget(5, 2)  # 5 rows, 2 columns
        self.table.setHorizontalHeaderLabels(["Column 1", "Column 2"])

        # Add a QLabel to cell (4, 0)
        label = QLabel("Test")
        self.table.setCellWidget(4, 0, label)

        # Add a button to trigger the move
        self.button = QPushButton("Move widget")
        self.button.clicked.connect(self.move_widget)

        layout = QVBoxLayout()
        layout.addWidget(self.table)
        layout.addWidget(self.button)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def move_widget(self):
        """Move the widget from cell (4,0) to (3,0)."""
        widget = self.table.cellWidget(4, 0)

        self.table.removeCellWidget(4, 0)
        self.table.setCellWidget(3, 0, widget)
        widget.show()

        # Debug: Verify the move
        print(f"Cell (3,0) now has: {self.table.cellWidget(3, 0)}")
        print(f"Cell (4,0) now has: {self.table.cellWidget(4, 0)}")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

This has the following output:

Cell (3,0) now has: <PySide6.QtWidgets.QLabel(0x26d65f7fc00) at 0x0000026D67071380>
Cell (4,0) now has: None

Process finished with exit code -1073741819 (0xC0000005)

It seems to me that upon rendering, the QLabel is removed. Is there any way to prevent this from happening?

3
  • 1
    This was asked here just yesterday: removeCellWidget() acts as setCellWidget(row, col, None), and as the docs explain, setting another widget (including no widget) deletes the previous one. The deletion happens on the "Qt side", and there's nothing you can do about it. The only possibility is to always add widgets within a QWidget parent, then reparent the inner widget before replacing. That said, did you consider simply using the moveSection() function of the vertical header? Commented May 21 at 0:39
  • See the related Drag and Drop a Widget in QTreeWidget. It's about drag&drop, but the concept remains: the only way to move an "index widget" with high level classes such as QTableWidget (or models like QStandardItemModel) is by using a container QWidget for the actual widget, then reparent it before calling removeCellWidget() and setCellWidget(), unless moving the section in the header is acceptable. The only alternative is to use a QAbstractItemModel subclass and use related functions (such as beginMoveRows() and endMoveRows() to move rows). Commented May 21 at 0:46
  • @musicamante thanks! I missed that post. I adjusted my code and it works now Commented May 21 at 9:36

1 Answer 1

0

Using @musicamante's explanation I got my code to work. It is a bit of a workaround, but it works. Here is the fixed code:

import sys
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QTableWidget, QLabel, QPushButton, QVBoxLayout, QWidget, QHBoxLayout
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.table = QTableWidget(5, 2)  # 5 rows, 2 columns
        self.table.setHorizontalHeaderLabels(["Column 1", "Column 2"])

        # Add a QLabel to cell (4, 0)
        label_container = QWidget()
        widget_layout = QHBoxLayout(label_container)

        self.label = QLabel("Test", label_container)
        widget_layout.addWidget(self.label)
        self.table.setCellWidget(4, 0, label_container)

        # Add a button to trigger the move
        self.button = QPushButton("Move widget")
        self.button.clicked.connect(self.move_widget)

        layout = QVBoxLayout()
        layout.addWidget(self.table)
        layout.addWidget(self.button)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def move_widget(self):
        """Move the widget from cell (4,0) to (3,0)."""
        new_container = QWidget()
        widget_layout = QHBoxLayout(new_container)
        widget_layout.addWidget(self.label)

        self.table.setCellWidget(3, 0, new_container)
        self.table.removeCellWidget(4, 0)

        # Debug: Verify the move
        print(f"Cell (3,0) now has: {self.table.cellWidget(3, 0)}")
        print(f"Cell (4,0) now has: {self.table.cellWidget(4, 0)}")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

It uses a layout as well so that the QLabel is centred, this is probably not necessary for other widgets.

Sign up to request clarification or add additional context in comments.

5 Comments

Note that there are some issues with this implementation: most importantly, you're just reparenting the label, but in reality you need to add it to the layout (it will be reparented automatically). The layout is necessary for any widget, not just labels, as it ensures that it's properly resized whenever the cell is. You also don't need access to the previous container widget (nor you should try to show it, as will soon be deleted), unless you need dynamic access to its contained widget, which is unnecessary in this case, as you kept a reference to self.label: just call removeWidget().
The following changes within moveWidget() would make this answer more appropriate: 1. remove the widget = ... as it's pointless (unless the inner widget is dynamically added or created, therefore you need it to access the items in its layout); 2. add layout.addWidget(self.label); 3. remove the setParent() line as it's made redundant due to the layout.addWidget() suggested before; 4. remove widget.show(), as it's irrelevant (it's the previous "cell widget", which is going to be deleted, so there's no point in trying to show it); 5. use new_container instead of new_label_widget.
If you're not sure about how to properly edit your code with the above suggestions, let me know and I'll revise it for you. It's normally a discouraged action, but it may be acceptable in cases like these, as providing a further answer with the correct code would just be redundant.
@musicamante sorry for the slow reply. I implemented your suggestions now. I still do not fully understand why renaming it to new_container would be clearer, since the container defined in __init__ is completely different: it is the container for the table and the button. To make this clearer I also renamed label_widget to label_container in __init__.
Frankly, now that I read again your original code, I don't know why I made that suggestion :-D Note that you should call widget_layout.addWidget(self.label) in the __init__ too.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.