使用旋转框小部件 (PyQt5) 从 QTableWidget 移动项目和读取值

Move items and read values from QTableWidget with spinbox widget (PyQt5)

我有一个软件工具,其中 table 填充了两列:参数和精度。 “精度”列中的一些参数具有微调框小部件,而一些参数只有文本“N/A”。 我有两个问题:

  1. 如何上下移动 table 中的行,包括旋转框小部件。目前我只能移动参数名称,一旦我尝试向上或向下移动行,旋转框就会消失。 下面是定义选定行向上或向下移动的函数的代码: '''

     def _buttonMoveUpClicked(self):
         rowIndex = self.table.currentRow()
         self.table.insertRow(rowIndex - 1)
         for i in range(self.table.columnCount()):
             self.table.setItem(rowIndex - 1, i, self.table.takeItem(rowIndex + 1, i))
             self.table.selectRow(rowIndex - 1)
         self.table.removeRow(rowIndex + 1)
    
     def _buttonMoveDownClicked(self):
         rowIndex = self.table.currentRow()
         self.table.insertRow(rowIndex + 2)
         for i in range(self.table.columnCount()):
             self.table.setItem(rowIndex + 2, i,
                                self.table.takeItem(rowIndex, i))
             self.table.selectRow(rowIndex + 2)
         self.table.removeRow(rowIndex)
    

'''

  1. 我需要将 table 中的值保存到列表中。如何使用旋转框小部件从单元格中读取值? 我现在尝试保存它的方式:

'''

def _readTable(self):
        list_Parameters = []
        list_Precision = []
        print('1')
        for i in range(self.table.rowCount()):
            print(self.table.item(i, 0).text())
            list_Parameters.append(self.table.item(i, 0).text())
            list_Precision.append(self.table.item(i, 1).text())

''' 这是软件工具的完整代码:

import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtCore
import pandas as pd

class Window(QWidget):
    singleton: 'Window' = None

    def __init__(self):
        super(Window, self).__init__()
        self.setWindowTitle("Software tool")
        self.setGeometry(50, 50, 1800, 900)
        self.mainLayout=QHBoxLayout()
        self.setLayout(self.mainLayout)
        self.UI()

    def UI(self):
        self.sublayouts = {}
        self.buttons = {}
        self._view()
        self._fillListWidget()
        self.table.selectionModel().selectionChanged.connect(self._updateButtonStatus)
        self.buttons['Up'].clicked.connect(self._buttonMoveUpClicked)
        self.buttons['Down'].clicked.connect(self._buttonMoveDownClicked)
        self.show()

    def _view(self):
        self.table = QTableWidget(0, 2)
        self.table.setHorizontalHeaderLabels(['Parameter', 'Precision'])
        self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.buttons['Up'] = QPushButton('&Up')
        self.buttons['Down'] = QPushButton('&Down')

        self.sublayouts['table'] = QGridLayout()
        self.sublayouts['table'].addWidget(self.table, 1, 0, 4, 4)
        self.sublayouts['table'].setRowStretch(4, 1)
        self.sublayouts['table'].addWidget(self.buttons['Up'], 1, 4, 1, 1, alignment=QtCore.Qt.AlignTop)
        self.sublayouts['table'].addWidget(self.buttons['Down'], 2, 4, 1, 1, alignment=QtCore.Qt.AlignTop)

        self.mainLayout.addLayout(self.sublayouts['table'])

    def _fillListWidget(self):
        list = {
            'Parameters': ['a', 'b', 'c', 'd', 'e'],
            'Precision': [-1, -1, 2, 3, 1]}
        self.df = pd.DataFrame(list)
        for row in zip(*self.df.to_dict('list').values()):
            itemColumn = QTableWidgetItem(
                row[self.df.columns.get_loc("Parameters")])
            rowPosition = self.table.rowCount()
            self.table.insertRow(rowPosition)
            itemTable = self._tableCell(row[self.df.columns.get_loc("Parameters")])
            self.table.setItem(rowPosition, 0, itemTable)
            df_tmp = self.df[self.df['Parameters'] == row[self.df.columns.get_loc("Parameters")]]
            if df_tmp['Precision'].values[0] >= 0:
                self.spinBox = QSpinBox()
                self.spinBox.setValue(df_tmp['Precision'].values[0])
                self.table.setCellWidget(rowPosition, 1, self.spinBox)
            else:
                itemTable = self._tableCell('N/A')
                self.table.setItem(rowPosition, 1, itemTable)

    def _updateButtonStatus(self):
        self.buttons['Up'].setDisabled(not bool(self.table.selectedItems()) or self.table.rowCount() == 0 or self.table.currentRow()==0)
        self.buttons['Down'].setDisabled(not bool(self.table.selectedItems()) or self.table.rowCount() == 0 or self.table.currentRow()==self.table.rowCount()-1)
        # self._readTable()

    def _buttonMoveUpClicked(self):
        rowIndex = self.table.currentRow()
        self.table.insertRow(rowIndex - 1)
        for i in range(self.table.columnCount()):
            self.table.setItem(rowIndex - 1, i, self.table.takeItem(rowIndex + 1, i))
            self.table.selectRow(rowIndex - 1)
        self.table.removeRow(rowIndex + 1)

    def _buttonMoveDownClicked(self):
        rowIndex = self.table.currentRow()
        self.table.insertRow(rowIndex + 2)
        for i in range(self.table.columnCount()):
            self.table.setItem(rowIndex + 2, i,
                               self.table.takeItem(rowIndex, i))
            self.table.selectRow(rowIndex + 2)
        self.table.removeRow(rowIndex)

    def _tableCell(self, text):
        item = QTableWidgetItem()
        item.setText(text)
        return item

    def _readTable(self):
        list_Parameters = []
        list_Precision = []
        print('1')
        for i in range(self.table.rowCount()):
            print(self.table.item(i, 0).text())
            list_Parameters.append(self.table.item(i, 0).text())
            list_Precision.append(self.table.item(i, 1).text())

def main():
  App=QApplication(sys.argv)
  window =Window()
  sys.exit(App.exec_())

if __name__ == '__main__':
  main()

您不能直接使用 QTableWidget 执行此操作,因为它是一个更高级别的小部件,不会在其模型中实现索引移动。

正确的 解决方案是创建一个 QAbstractTableItem 子类,该子类也将实现 moveRows().

可以使用更简单的替代方法,但请记住,模型视图应始终跟踪索引,deleting/adding 行不允许这样做。如果你想做一个好的实现,一个项目模型子类是强制性的。

另一种选择来自这样一个事实,即每当删除行(或列)时,它们的编辑器也会被销毁,您对此无能为力。
假设您总是只想移动一行,解决方案是获取当前编辑器(及其值),删除该行,插入新的,并使用之前读取的值为其创建一个新的编辑器。

出于简单原因(并遵循 OOP 模式),我创建了一个通用函数,它接受当前行和一个“delta”参数,告诉它该行是否必须向上或向下“移动”。

def _moveRow(self, row, delta):
    colIndex = self.table.currentColumn()
    items = [self.table.takeItem(row, c) for c in range(self.table.columnCount())]
    oldSpinBox = self.table.cellWidget(row, 1)
    self.table.removeRow(row)
    newRow = row + delta
    self.table.insertRow(newRow)
    for i, item in enumerate(items):
        self.table.setItem(newRow, i, item)
    if isinstance(oldSpinBox, QAbstractSpinBox):
        newSpinBox = QSpinBox()
        newSpinBox.setValue(oldSpinBox.value())
        self.table.setCellWidget(newRow, 1, newSpinBox)
    if colIndex >= 0:
        self.table.setCurrentCell(newRow, colIndex)

def _buttonMoveUpClicked(self):
    rowIndex = self.table.currentRow()
    if rowIndex > 0:
        self._moveRow(rowIndex, -1)

def _buttonMoveDownClicked(self):
    rowIndex = self.table.currentRow()
    if rowIndex < self.table.rowCount() - 1:
        self._moveRow(rowIndex, 1)

如前所述,这是一个非常简单的解决方案,仅适用于硬编码的 row/column 小部件。一个更正确和更明智的解决方案是创建 QAbstractTableModel 的子类并实现 moveRows() 函数:这样做允许更正确的实现,将提供正确的编辑器(因此不需要 setCellWidget() )和将允许更正确的行移动。