Qt TableView+Delegate+ProxyIndex+PersistenIndex - 使用小部件删除行

Qt TableView+Delegate+ProxyIndex+PersistenIndex - deleting rows with widgets

我正在尝试弄清楚如何使用具有 sorting/filtering 可能性的小部件编写 table 代码。 我使用 QItemDelegate、QAbstractTableModel、QTableView。

已经检查了很多关于这个主题的话题,这让我编写了我的应用程序的一部分。

我想了解的是如何正确删除行以保持所有这些索引和数据的一致性。起初我虽然我做对了,但是多次尝试排序和删除显示带有按钮删除小部件的列的行为非常奇怪。

例如,如果我一直点击同一 table 行(例如第 5 行)中的按钮,这些删除按钮只会向上移动单词,而前 3 列会正确更新。还有一些删除按钮被复制了。

完整代码:

import sys
from PySide2.QtWidgets import QApplication

app = QApplication( )


from PySide2.QtWidgets import QApplication, QLabel, QMainWindow, QWidget, QHBoxLayout ,QVBoxLayout,QGridLayout,QStackedLayout, QTableView 
from PySide2.QtCore import Qt

# QHBoxLayout   Linear horizontal layout
# QVBoxLayout   Linear vertical layout
# QGridLayout   In indexable grid XxY
# QStackedLayout
def get_layout(ll):

    if ll not in ['grid','hbox','vbox','stack']:
        # print(ll)
        # print('wrong layout name')
        exit()
        
    ret_layout = QGridLayout()
    
    if ll=='hbox':
        ret_layout = QHBoxLayout()
    elif ll=='vbox':
        ret_layout = QVBoxLayout()
    elif ll=='stack':
        ret_layout = QStackedLayout()

    return ret_layout
    
    
    
    

class MainWindow(QMainWindow):
 
    def __init__(self, win_title='Default title', win_layout='grid',main_widget = QWidget()):
    
        super(MainWindow, self).__init__()

        self.setWindowTitle(win_title)
        self.layout_name=win_layout
        self.main_layout=get_layout(win_layout)
        self.setGeometry(100,100,1000,800)
         
        main_widget.setLayout(self.main_layout)
         
        self.setCentralWidget(main_widget)
 
    def addFrame(self,frame):
    
        if self.layout_name!='grid':
        
            if type(frame)==type([]):
                self.main_layout.addWidget(frame[-1])
            else:
                self.main_layout.addWidget(frame)
                
        else:
            self.main_layout.addWidget(frame[2],frame[0],frame[1])

            


import sys
import json
import PySide2
from PySide2 import QtCore, QtGui, QtWidgets 
from PySide2.QtCore import Qt #, QFile, QIODevice


class ExampleWidget(QtWidgets.QWidget):
    def __init__(self, x, index, parent=None):
        super(ExampleWidget, self).__init__(parent)
        self.orig_index=index
        self.p_index = QtCore.QPersistentModelIndex(index)
        
        self.content_button = QtWidgets.QWidget(self)
        lay = QtWidgets.QHBoxLayout(self.content_button)
        lay.setContentsMargins(0, 0, 0,0)
        
        self.delete_btn = QtWidgets.QPushButton("delete "+str(index.row()))
        
        self.delete_btn.clicked.connect(self.delete_clicked)
        lay.addWidget(self.delete_btn)
        self.content_button.move(x, 0)
        
    @QtCore.Slot()
    def delete_clicked(self):

        model = self.p_index.model() # QSortFilterProxyModel
        src_model=model.sourceModel()
        src_index=model.mapToSource(self.orig_index)
        rows_to_del=1
        
        zxc=src_model.removeRows(src_index.row(),rows_to_del,src_index, self.p_index )
        # self.deleteLater()
        # self.QtWidgets.~QWidget()
        
        

        
class CustomDelegate(QtWidgets.QItemDelegate): #QStyledItemDelegate

    def __init__(self, parent=None):
        super(CustomDelegate, self).__init__(parent)
        
    def paint(self, painter, option, index):
        self.parent().openPersistentEditor(index)
        super(CustomDelegate, self).paint(painter, option, index)
        
    def createEditor(self, parent, option, index):
        
        if index.column()==3:
            return ExampleWidget(300, index, parent)
        
            

            
class TableModel(QtCore.QAbstractTableModel ): 

    def __init__(self, table=[], col_headers=[], row_headers=[], bg_color=None ):
        super(TableModel, self).__init__()
        
        self.table =  table 
        self.col_headers=col_headers  
        self.row_headers=row_headers  
        self.bg_color=bg_color or QtGui.QColor('lightgrey')
        
    
    def itemFromIndex(self,index):
        if not index.isValid():
            return None
    
        rr=index.row()
        cc=index.column()
        return str ( self.table[rr][cc] ) 
        
        
    def removeRows(self,row, count, index, p_index):
        self.layoutAboutToBeChanged.emit()
        self.beginRemoveRows(index,row,row)
        del self.table[row]
        self.endRemoveRows()
        self.layoutChanged.emit()
        
        return True
        
        
    def data(self, index, role ):
    
        if not index.isValid():
            return None
    
        rr=index.row()
        cc=index.column()
        
        if role == Qt.DisplayRole:
            vvalue = str ( self.table[rr][cc] ) 
            return vvalue #QtWidgets.QPushButton
        
        if role == Qt.BackgroundRole:
            return self.bg_color
                
    def rowCount(self, index):
        return len(self.table)
        # return 5
        
    def columnCount(self, index):
        if len(self.table)>0:
            return len(self.table[0])
        return 0
        
        
    def headerData(self, section, orientation, role):
    
        if section<0:
            return
    
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                if len(self.col_headers)>section:
                    return str(self.col_headers[section])

            if orientation == Qt.Vertical:
                if len(self.row_headers)>section:
                    return str(self.row_headers[section])
        
        
        
        
class TableView( QtWidgets.QTableView):

    def __init__(self,params={}):
        super(TableView, self).__init__( )
        
        self.model = TableModel()
        
        self.sorting_proxy = QtCore.QSortFilterProxyModel()
        self.sorting_proxy.setSourceModel( self.model)
        self.setModel(self.sorting_proxy)       
        
        self.delegate=CustomDelegate(self)
        self.setItemDelegate(self.delegate)
        
        self.setSortingEnabled(True)
        self.setCornerButtonEnabled(False)
        self.horizontalHeader().setStretchLastSection(True)
        self.clicked.connect(self.onClick)
        self.verticalHeader().hide()
        
        tmp_header_bg_color='lightgrey'
        if 'header_bg_color' in params:
            tmp_header_bg_color=params['header_bg_color']
            
        tmp_style="""QWidget  { background-color:%s; border:none; }
                    QHeaderView::section { background-color:%s; border:none; }
                    QTableCornerButton::section { background-color:%s; border:none; }
                    """ % (tmp_header_bg_color,tmp_header_bg_color,tmp_header_bg_color)
            
        self.setStyleSheet(tmp_style)
        
        if len(params)>0:
            if 'show_grid' in params:
                self.setShowGrid(params['show_grid'])
            if 'auto_resize':
                self.auto_resize=True
            

            
    @QtCore.Slot(QtCore.QModelIndex)
    def onClick(self, ix):
        # it = self.model.itemFromIndex(ix)
        it=self.sorting_proxy.data(ix)
        if hasattr(it,'data'):
            print(it.data())            
        else:
            print(it)
                
            
    def sortByCol(self,cc,dir='asc'):
        if dir=='asc':
            self.sortByColumn(cc, Qt.AscendingOrder)
        else:
            self.sortByColumn(cc, Qt.DescendingOrder)
            
            
            
            
            
    def setdata(self,data_list,col_headers=[],row_headers=[]):

        self.model.table=data_list
        self.model.col_headers=col_headers
        self.model.row_headers=row_headers
        
        self.model.layoutChanged.emit()
        
        if hasattr(self,'auto_resize'):
            self.resizeColumnsToContents()
            self.resizeRowsToContents()

            

            

data_list = [
        ('ACETIC ACID', 117.9, 16.7, 1.049),
        ('ACETIC ANHYDRIDE', 140.1, -73.1, 1.087),
        ('ACETONE', 56.3, -94.7, 0.791),
        ('ACETONITRILE', 81.6, -43.8, 0.786),
        ('ANISOLE', 154.2, -37.0, 0.995),
        ('BENZYL ALCOHOL', 205.4, -15.3, 1.045),
        ('BENZYL BENZOATE', 323.5, 19.4, 1.112),
        ('BUTYL ALCOHOL NORMAL', 117.7, -88.6, 0.81),
        ('BUTYL ALCOHOL SEC', 99.6, -114.7, 0.805),
        ('BUTYL ALCOHOL TERTIARY', 82.2, 25.5, 0.786),
        ('CHLOROBENZENE', 131.7, -45.6, 1.111),
        ('CYCLOHEXANE', 80.7, 6.6, 0.779),
        ('CYCLOHEXANOL', 161.1, 25.1, 0.971),
        ('CYCLOHEXANONE', 155.2, -47.0, 0.947),
        ('DICHLOROETHANE 1 2', 83.5, -35.7, 1.246),
        ('DICHLOROMETHANE', 39.8, -95.1, 1.325),
        ('DIETHYL ETHER', 34.5, -116.2, 0.715),
        ('DIMETHYLACETAMIDE', 166.1, -20.0, 0.937),
        ('DIMETHYLFORMAMIDE', 153.3, -60.4, 0.944),
        ('DIMETHYLSULFOXIDE', 189.4, 18.5, 1.102),
        ('DIOXANE 1 4', 101.3, 11.8, 1.034),
        ('DIPHENYL ETHER', 258.3, 26.9, 1.066),
        ('ETHYL ACETATE', 77.1, -83.9, 0.902),
        ('ETHYL ALCOHOL', 78.3, -114.1, 0.789),
        ('ETHYL DIGLYME', 188.2, -45.0, 0.906),
        ('ETHYLENE CARBONATE', 248.3, 36.4, 1.321),
        ('ETHYLENE GLYCOL', 197.3, -13.2, 1.114),
        ('FORMIC ACID', 100.6, 8.3, 1.22),
        ('HEPTANE', 98.4, -90.6, 0.684),
        ('HEXAMETHYL PHOSPHORAMIDE', 233.2, 7.2, 1.027),
        ('HEXANE', 68.7, -95.3, 0.659),
        ('ISO OCTANE', 99.2, -107.4, 0.692),
        ('ISOPROPYL ACETATE', 88.6, -73.4, 0.872),
        ('ISOPROPYL ALCOHOL', 82.3, -88.0, 0.785),
        ('METHYL ALCOHOL', 64.7, -97.7, 0.791),
        ('METHYL ETHYLKETONE', 79.6, -86.7, 0.805),
        ('METHYL ISOBUTYL KETONE', 116.5, -84.0, 0.798),
        ('METHYL T-BUTYL ETHER', 55.5, -10.0, 0.74),
        ('METHYLPYRROLIDINONE N', 203.2, -23.5, 1.027),
        ('MORPHOLINE', 128.9, -3.1, 1.0),
        ('NITROBENZENE', 210.8, 5.7, 1.208),
        ('NITROMETHANE', 101.2, -28.5, 1.131),
        ('PENTANE', 36.1, ' -129.7', 0.626),
        ('PHENOL', 181.8, 40.9, 1.066),
        ('PROPANENITRILE', 97.1, -92.8, 0.782),
        ('PROPIONIC ACID', 141.1, -20.7, 0.993),
        ('PROPIONITRILE', 97.4, -92.8, 0.782),
        ('PROPYLENE GLYCOL', 187.6, -60.1, 1.04),
        ('PYRIDINE', 115.4, -41.6, 0.978),
        ('SULFOLANE', 287.3, 28.5, 1.262),
        ('TETRAHYDROFURAN', 66.2, -108.5, 0.887),
        ('TOLUENE', 110.6, -94.9, 0.867),
        ('TRIETHYL PHOSPHATE', 215.4, -56.4, 1.072),
        ('TRIETHYLAMINE', 89.5, -114.7, 0.726),
        ('TRIFLUOROACETIC ACID', 71.8, -15.3, 1.489),
        ('WATER', 100.0, 0.0, 1.0),
        ('XYLENES', 139.1, -47.8, 0.86)
        ]

tmpheaders=['a','b','c']

mw = MainWindow(win_layout='hbox' )

ff=TableView(params={'show_grid':False,'auto_resize':1 })
mw.addFrame(ff)
ff.setdata(data_list,tmpheaders)

mw.show()

app.exec_()

您必须创建一个删除行的方法:

class TableModel(QAbstractTableModel):
    # ...
    <b>def removeRow(self, row):
        self.beginRemoveRows(QModelIndex(), row, row)
        del self.table[row]
        self.endRemoveRows()</b>

然后映射sourceModel中的行:

class ExampleWidget(QWidget):
    # ...

    @Slot()
    def delete_clicked(self):
        index = QModelIndex(self.p_index)
        model = index.model()
        source_index = model.mapToSource(index)
        source_model = source_index.model()
        source_model.removeRow(source_index.row())