简体   繁体   中英

PyQt5: phantom columns when using QTableView.setSortingEnabled and QSortFilterProxyModel

I have a custom Qt table model that allows a user to add both columns and rows after it's been created. I'm trying to display this table using a QSortFilterProxyModel / QTableView setup, but I'm getting strange behavior when I try and enable sorting on the table view. My view launches and displays the added data correctly:

在发布时查看

However, when I click on one of the column headers (to sort), phantom columns are added to the view.

标题点击后查看

Has anyone seen this before or know whats going on? I'm admittedly a novice still with Qt, so maybe I'm just approaching this the wrong way. Thanks.

# -*- mode:python; mode:auto-fill; fill-column:79; coding:utf-8 -*-

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

# =====================================================================
# =====================================================================
class SimpleProxyModel(QSortFilterProxyModel):
    def filterAcceptsColumn(self, col, index):
        return True

# =====================================================================
# =====================================================================
class SimpleModel(QAbstractTableModel):

    def __init__(self):
        super(SimpleModel, self).__init__()
        self._data = {}

    def rowCount(self, index=QModelIndex()):
        if len(self._data.values()) > 0:
            return len(self._data.values()[0])
        else:
            return 0

    def columnCount(self, index=QModelIndex()):
        return len(self._data.keys())

    def data( self, index, role = Qt.DisplayRole ):
        row, col = index.row(), index.column()

        if ( not index.isValid() or
             not ( 0 <= row < self.rowCount() ) or
             not ( 0 <= col < self.columnCount() ) ):
            return QVariant()

        if role == Qt.DisplayRole:
            return QVariant( self._data[col][row] )

        return QVariant()

    def addData( self, col, val):

        new_col = False
        # Turn on flag for new column
        if col not in self._data.keys():
            new_col = True

        if new_col:
            self.beginInsertColumns(QModelIndex(), self.columnCount(),self.columnCount())
            # Catch this column up with the others by adding blank rows
            self._data[col] = [""] * self.rowCount()

        self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
        # Add data to each column, either the value specified or a blank
        for i in range(self.columnCount()):
            if i == col:
                self._data[i].append(val)
            else:
                self._data[i].append( "" )
        self.endInsertRows()

        if new_col:
            self.endInsertColumns()


# =====================================================================
# =====================================================================
class SimpleView(QWidget):

    def __init__(self, parent=None):

        super(SimpleView, self).__init__(parent)

        self._mainmodel = None
        self._proxymodel = None
        self._tableview = QTableView()
        self._tableview.setSortingEnabled(True)

        layout = QVBoxLayout()
        layout.addWidget( self._tableview )

        self.setLayout(layout)

    def setModel(self, model):

        self._mainmodel = model
        proxy = SimpleProxyModel()
        proxy.setSourceModel(model)
        self._tableview.setModel(proxy)

# =====================================================================
# =====================================================================
app = QApplication([])

v = SimpleView()
m = SimpleModel()
v.setModel( m )

m.addData(0,1)
m.addData(0,2)
m.addData(1,3)

v.show()
app.exec_()

The Qt documentation is fairly clear on this. You must call endInsertRows after beginInsertRows , and endInsertColumns after beginInsertColumns .

The most pertinent point from the last link above is this:

Note: This function emits the columnsAboutToBeInserted() signal which connected views (or proxies) must handle before the data is inserted. Otherwise, the views may end up in an invalid state.

So you must call endInsertColum() before beginning any other insertions/deletions.

It looks like the problem was in my SimpleModel.addData method, and is related to how I nested the column and row inserts. If you change this method to first insert new columns, and then insert new rows, the phantom columns don't show up.

def addData( self, col, val):                                               

    if col not in self._data.keys():                                        
        self.beginInsertColumns(QModelIndex(), self.columnCount(),self.columnCount())
        # Catch this column up with the others by adding blank rows         
        self._data[col] = [""] * self.rowCount()                            
        self.endInsertColumns()                                             

    self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())   
    # Add data to each column, either the value specified or a blank        
    for i in range(self.columnCount()):                                     
        if i == col:                                                        
            self._data[i].append(val)                                       
        else:                                                               
            self._data[i].append( "" )                                      
    self.endInsertRows()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM