Qt TableView+Delegate+ProxyIndex+PersistenIndex - 删除带有小部件的行

问题描述

我试图弄清楚如何使用具有排序/过滤可能性的小部件对表格进行编码。 我使用 QItemDelegate、QAbstractTableModel、QTableView。

已经检查了关于这个主题的许多线程,这让我对我的应用程序的一部分进行了编码。

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

例如,如果我一直单击同一表格行(例如 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)
        
        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,index)
        
    def createEditor(self,parent,index):
        
        if index.column()==3:
            return ExampleWidget(300,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,p_index):
        self.layoutAboutToBeChanged.emit()
        self.beginRemoveRows(index,row)
        del self.table[row]
        self.endRemoveRows()
        self.layoutChanged.emit()
        
        return True
        
        
    def data(self,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,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,('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,('METHYL ETHYLKetoNE',79.6,-86.7,('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,('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,('PROPANENITRILE',97.1,-92.8,0.782),('PROPIONIC ACID',141.1,-20.7,0.993),('PROPIONITRILE',97.4,('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,0.726),('TRIFLUOROACETIC ACID',71.8,1.489),('WATER',100.0,0.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):
    # ...
    def removeRow(self,row):
        self.beginRemoveRows(QModelIndex(),row,row)
        del self.table[row]
        self.endRemoveRows()

然后映射 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())