如何在 QTableWidget 列的每个单元格中显示一个按钮,以便在单击时删除其相应的行?

发布于 2025-01-15 15:41:51 字数 3223 浏览 6 评论 0原文

我想在 QTableWidget 列的每个单元格中显示一个按钮。单击每个按钮时,必须删除表中其相应的行。

为此,我创建了一个 RemoveRowDelegate 类,其中按钮作为编辑器,并使用 CustomTable 类中的 QAbstractItemView::openPersistentEditor 方法来显示按钮永久。

class RemoveRowDelegate(QStyledItemDelegate):
    def __init__(self, parent, cross_icon_path):
        super().__init__(parent)
        self.cross_icon_path = cross_icon_path
        self.table = None

    def createEditor(self, parent, option, index):
        editor = QToolButton(parent)
        editor.setStyleSheet("background-color: rgba(255, 255, 255, 0);")  # Delete borders but maintain the click animation (as opposed to "border: none;")
        pixmap = QPixmap(self.cross_icon_path)
        button_icon = QIcon(pixmap)
        editor.setIcon(button_icon)
        editor.clicked.connect(self.remove_row)
        return editor

    # Delete the corresponding row
    def remove_row(self):
        sending_button = self.sender()
        for i in range(self.table.rowCount()):
            if self.table.cellWidget(i, 0) == sending_button:
                self.table.removeRow(i)
                break

class CustomTable(QTableWidget):
    def __init__(self, parent=None, df=None):
        super().__init__(parent)
        self.columns = []
        self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)

        if df is not None:
            self.fill(df)

    # Build the table from a pandas df
    def fill(self, df):
        self.columns = [''] + list(df.columns)
        nb_rows, _ = df.shape
        nb_columns = len(self.columns)
        self.setRowCount(nb_rows)
        self.setColumnCount(nb_columns)
        self.setHorizontalHeaderLabels(self.columns)

        for i in range(nb_rows):
            self.openPersistentEditor(self.model().index(i, 0))
            for j in range(1, nb_columns):
                item = df.iloc[i, j-1]
                table_item = QTableWidgetItem(item)
                self.setItem(i, j, table_item)

    def add_row(self):
        nb_rows = self.rowCount()
        self.insertRow(nb_rows)
        self.openPersistentEditor(self.model().index(nb_rows, 0))

    def setItemDelegateForColumn(self, column_index, delegate):
        super().setItemDelegateForColumn(column_index, delegate)
        delegate.table = self

我为表的第一列设置了委托,并从 pandas 数据帧构建后者:

self.table = CustomTable()  # Here, self is my user interface
remove_row_delegate = RemoveRowDelegate(self, self.cross_icon_path) 
self.table.setItemDelegateForColumn(0, remove_row_delegate)
self.table.fill(df)

目前,此解决方案可以完成这项工作,但我想到了其他几种可能性:

  • 使用 QTableWidget: :setCellWidget 方法
  • 覆盖 paint 方法并捕获左键单击事件

但是:

  • 我相信第一个替代方案不是很干净,因为我必须在 for 循环中创建按钮并且每次一行添加了(但毕竟我也打电话openPersistentEditor 与此处相同)。
  • 我想知道第二种选择是否值得付出努力。如果是的话,该怎么做?

另外:

  • 我相信我的 remove_row 方法可以在迭代所有行时进行优化(这就是我考虑第二种选择的原因之一)。您有更好的建议吗?
  • 我必须重写 setItemDelegateForColumn 方法,以便可以从 RemoveRowDelegate 类访问该表。可以避免吗?

您认为可能感兴趣的任何其他评论将不胜感激!

I want to display a button in each cell of a QTableWidget's column. Each button, when clicked, must remove its corresponding row in the table.

To do so, I created a RemoveRowDelegate class with the button as editor and used the QAbstractItemView::openPersistentEditor method in a CustomTable class to display the button permanently.

class RemoveRowDelegate(QStyledItemDelegate):
    def __init__(self, parent, cross_icon_path):
        super().__init__(parent)
        self.cross_icon_path = cross_icon_path
        self.table = None

    def createEditor(self, parent, option, index):
        editor = QToolButton(parent)
        editor.setStyleSheet("background-color: rgba(255, 255, 255, 0);")  # Delete borders but maintain the click animation (as opposed to "border: none;")
        pixmap = QPixmap(self.cross_icon_path)
        button_icon = QIcon(pixmap)
        editor.setIcon(button_icon)
        editor.clicked.connect(self.remove_row)
        return editor

    # Delete the corresponding row
    def remove_row(self):
        sending_button = self.sender()
        for i in range(self.table.rowCount()):
            if self.table.cellWidget(i, 0) == sending_button:
                self.table.removeRow(i)
                break

class CustomTable(QTableWidget):
    def __init__(self, parent=None, df=None):
        super().__init__(parent)
        self.columns = []
        self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)

        if df is not None:
            self.fill(df)

    # Build the table from a pandas df
    def fill(self, df):
        self.columns = [''] + list(df.columns)
        nb_rows, _ = df.shape
        nb_columns = len(self.columns)
        self.setRowCount(nb_rows)
        self.setColumnCount(nb_columns)
        self.setHorizontalHeaderLabels(self.columns)

        for i in range(nb_rows):
            self.openPersistentEditor(self.model().index(i, 0))
            for j in range(1, nb_columns):
                item = df.iloc[i, j-1]
                table_item = QTableWidgetItem(item)
                self.setItem(i, j, table_item)

    def add_row(self):
        nb_rows = self.rowCount()
        self.insertRow(nb_rows)
        self.openPersistentEditor(self.model().index(nb_rows, 0))

    def setItemDelegateForColumn(self, column_index, delegate):
        super().setItemDelegateForColumn(column_index, delegate)
        delegate.table = self

I set the delegate for the first column of the table and build the latter from a pandas dataframe:

self.table = CustomTable()  # Here, self is my user interface
remove_row_delegate = RemoveRowDelegate(self, self.cross_icon_path) 
self.table.setItemDelegateForColumn(0, remove_row_delegate)
self.table.fill(df)

For now, this solution does the job but I think of several other possibilities:

  • Using the QTableWidget::setCellWidget method
  • Overriding the paint method and catching the left click event

But:

  • I believe the first alternative is not very clean as I must create the buttons in a for loop and each time a row is added (but after all, I also call openPersistentEditor the same way here).
  • I am wondering if the second alternative is worth the effort. And if it does, how to do it?

Also:

  • I believe my remove_row method can be optimized as I iterate over all rows (that is one of the reasons why I thought about the second alternative). Would you have a better suggestion ?
  • I had to override the setItemDelegateForColumn method so that I can access the table from the RemoveRowDelegate class. Can it be avoided ?

Any other remark that you think might be of interest would be greatly appreciated!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

稀香 2025-01-22 15:41:51

根据@ekhumoro的建议,我最终使用了上下文菜单:

    class CustomTable(QTableWidget):
        def __init__(self, parent=None, df=None, add_icon_path=None, remove_icon_path=None):
            super().__init__(parent)
            self.add_icon_path = add_icon_path
            self.remove_icon_path = remove_icon_path
    
            # Activation of customContextMenuRequested signal and connecting it to a method that displays a context menu
            self.setContextMenuPolicy(Qt.CustomContextMenu)
            self.customContextMenuRequested.connect(lambda pos: self.show_context_menu(pos))

        def show_context_menu(self, pos):
            idx = self.indexAt(pos)
            if idx.isValid():
                row_idx = idx.row()

                # Creating context menu and personalized actions
                context_menu = QMenu(parent=self)

                if self.add_icon_path:
                    pixmap = QPixmap(self.add_icon_path)
                    add_icon = QIcon(pixmap)
                    add_row_action = QAction('Insert a line', icon=add_icon)
                else:
                    add_row_action = QAction('Insert a line')
                add_row_action.triggered.connect(lambda: self.insertRow(row_idx))

                if self.remove_icon_path:
                    pixmap = QPixmap(self.remove_icon_path)
                    remove_icon = QIcon(pixmap)
                    remove_row_action = QAction('Delete the line', icon=remove_icon)
                else:
                    remove_row_action = QAction('Delete the line')
                remove_row_action.triggered.connect(lambda: self.removeRow(row_idx))

                context_menu.addAction(add_row_action)
                context_menu.addAction(remove_row_action)

                # Displaying context menu
                context_menu.exec_(self.mapToGlobal(pos))

此外,请注意,使用 QTableWidget::removeRow 方法比我以前的方法更优化。由于 QTableWidget::indexAt 方法,我们只需要从点击位置正确获取行索引即可。

As suggested by @ekhumoro, I finally used a context menu:

    class CustomTable(QTableWidget):
        def __init__(self, parent=None, df=None, add_icon_path=None, remove_icon_path=None):
            super().__init__(parent)
            self.add_icon_path = add_icon_path
            self.remove_icon_path = remove_icon_path
    
            # Activation of customContextMenuRequested signal and connecting it to a method that displays a context menu
            self.setContextMenuPolicy(Qt.CustomContextMenu)
            self.customContextMenuRequested.connect(lambda pos: self.show_context_menu(pos))

        def show_context_menu(self, pos):
            idx = self.indexAt(pos)
            if idx.isValid():
                row_idx = idx.row()

                # Creating context menu and personalized actions
                context_menu = QMenu(parent=self)

                if self.add_icon_path:
                    pixmap = QPixmap(self.add_icon_path)
                    add_icon = QIcon(pixmap)
                    add_row_action = QAction('Insert a line', icon=add_icon)
                else:
                    add_row_action = QAction('Insert a line')
                add_row_action.triggered.connect(lambda: self.insertRow(row_idx))

                if self.remove_icon_path:
                    pixmap = QPixmap(self.remove_icon_path)
                    remove_icon = QIcon(pixmap)
                    remove_row_action = QAction('Delete the line', icon=remove_icon)
                else:
                    remove_row_action = QAction('Delete the line')
                remove_row_action.triggered.connect(lambda: self.removeRow(row_idx))

                context_menu.addAction(add_row_action)
                context_menu.addAction(remove_row_action)

                # Displaying context menu
                context_menu.exec_(self.mapToGlobal(pos))

Moreover, note that using QTableWidget::removeRow method is more optimized than my previous method. One just need to get the row index properly from the click position thanks to QTableWidget::indexAt method.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文