Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QSortFilterProxyModel: Update after insert columns



  • Hi guys,

    I've got a problem with a QSortFilterProxyModel. After inserting a new column, the view doesn't update properly. I've called beginInsertColumns(QModelIndex(), old_row_count, old_row_count) before and self.model.endInsertColumns() after inserting the new column, but the view doesn't display the data and alternating row coloring properly.

    All seems to be fine and dandy if I don't use the proxy model.

    My understanding is that under "normal" circumstances I don't need to reimplement the usual methods (like, "index", "data", ...) when subclassing a QSortFilterProxyModel. The QSortFilterProxyModel's standard implementations call these methods after converting the indeces. Is that correct?

    Any advice?

    Cheers and many thanks in advance,

    Jan

    I've prepared a full working example, posted in two parts. Here Part 1 (custom tree_items, header, proxymodel and view), Part 2 (model and code to run the example) follows in the first answer to this question.

    @
    import collections

    from PyQt4.QtGui import *
    from PyQt4.QtCore import *

    class ViewItem(object):

    def __init__(self, data, parent):
        self.data = data
        self.parent = parent
        self.children = []
    

    class ColumnHeader(QHeaderView):

    colums_about_to_be_updated = pyqtSignal(int)
    colums_updated = pyqtSignal()
    
    def __init__(self, applicable_columns, initial_columns, parent=None):
        super(ColumnHeader, self).__init__(Qt.Horizontal, parent)
        self.applicable_columns = applicable_columns
        self.initial_columns = initial_columns
        self.all_columns = self.applicable_columns
        self.columns = initial_columns
        self.setMovable(True)
        self.setContextMenuPolicy(Qt.ActionsContextMenu)
        self.att_name_to_action = collections.OrderedDict()
        self._prepopulate_att_name_to_action()
        self._create_att_name_to_action()
        self._set_initial_colums()
    
    def _prepopulate_att_name_to_action(self):
        for col in self.columns:
            self.att_name_to_action[col] = None
    
    def _create_att_name_to_action(self):
        for col in self.all_columns:
            action = QAction(col, self)
            action.setCheckable(True)
            action.toggled.connect(self.set_columns)
            self.addAction(action)
            self.att_name_to_action[col] = action
    
    def _set_initial_colums(self):
        for active_name in self.columns:
            self.att_name_to_action[active_name].toggle()
    
    def set_columns(self):
        self.colums_about_to_be_updated.emit(len(self.columns))
        self.columns = []
        for att, action in self.att_name_to_action.items():
            if action.isChecked():
                self.columns.append(att)
        self.colums_updated.emit()
    

    class SearchFilterProxyModel(QSortFilterProxyModel):

    def __init__(self, source_model, parent=None):
        super(SearchFilterProxyModel, self).__init__(parent)
        self.source_model = source_model
        self.setSourceModel(self.source_model)
    

    class TreeView(QTreeView):

    def __init__(self, aux_root_view_item, all_applicable_attributes, initial_columns, parent=None):
    
        super(TreeView, self).__init__(parent)
        self.aux_root_view_item = aux_root_view_item
        self.setAlternatingRowColors(True)
    
        self.column_header = ColumnHeader(all_applicable_attributes, initial_columns)
        self.column_header.setStretchLastSection(True)
        self.setHeader(self.column_header)
    
        self.column_header.colums_about_to_be_updated.connect(self.header_about_to_be_changed)
        self.column_header.colums_updated.connect(self.header_changed)
    
        self.model = TreeModel(aux_root_view_item, self.column_header, self)
        self.proxy_model = SearchFilterProxyModel(self.model, self)
    
        #self.setModel(self.proxy_model)
        self.setModel(self.model)
    
        self.selection_model = self.selectionModel()
    
    def header_about_to_be_changed(self, old_row_count):
        self.model.beginInsertColumns(QModelIndex(), old_row_count, old_row_count)
    
    def header_changed(self):
        self.model.endInsertColumns()
    

    @



  • Here Part 2:

    @
    class TreeModel(QAbstractItemModel):
    def init(self, aux_root_view_item, header, parent):
    super(TreeModel, self).init(parent)
    self.tree_view = parent
    self.aux_root_view_item = aux_root_view_item
    self.header = header

    def rowCount(self, parent_index=QModelIndex()):
        if parent_index.column() > 0:
            return 0
        parent_view_item = self.view_item_from_index(parent_index)
        if parent_view_item is None:
            return 0
        return len(parent_view_item.children)
    
    def columnCount(self, parent_index=QModelIndex()):
        return len(self.header.columns)
    
    def headerData(self, section, orientation, role):
        if (orientation == Qt.Horizontal and
            role == Qt.DisplayRole):
            assert 0 <= section <= len(self.header.columns)
            return self.header.columns[section]
        elif role == Qt.DisplayRole:
            return QVariant(int(section + 1))
        return QVariant()
    
    def data(self, index, role):
        view_item = self.view_item_from_index(index)
        data = view_item.data
        if data is None:
            return None
        if role != Qt.DisplayRole and role != Qt.EditRole:
            return None
        column = index.column()
        value = data[column]
        return unicode(value)
    
    def index(self, row, column, parent_index):
        if row < 0 or column < 0:
            return QModelIndex()
        view_item_parent = self.view_item_from_index(parent_index)
        if row > len(view_item_parent.children) - 1:
            return QModelIndex()
        child = view_item_parent.children[row]
        return self.createIndex(row, column, child)
    
    def parent(self, child_index):
        child_view_item = self.view_item_from_index(child_index)
        if child_view_item is None:
            return QModelIndex()
        parent_view_item = child_view_item.parent
        if parent_view_item is None:
            return QModelIndex()
        grandparent_view_item = parent_view_item.parent
        if grandparent_view_item is None:
            return QModelIndex()
        row = grandparent_view_item.children.index(parent_view_item)
        assert row != -1
        return self.createIndex(row, 0, parent_view_item)
    
    def view_item_from_index(self, index):
        if index.isValid():
            view_item = index.internalPointer()
            return view_item
        else:
            return self.aux_root_view_item
    

    def create_test_data():

    aux_root_view_item = ViewItem(None, None)
    root_view_item = ViewItem(["1a", "1b", "1c"], aux_root_view_item)
    child_11 = ViewItem(["11a", "11b", "11c"], root_view_item)
    child_12 = ViewItem(["12a", "12b", "12c"], root_view_item)
    child_111 = ViewItem(["111a", "111b", "111c"], child_11)
    child_112 = ViewItem(["112a", "112b", "112c"], child_11)
    
    aux_root_view_item.children = [root_view_item]
    root_view_item.children = [child_11, child_12]
    child_11.children = [child_111, child_112]
    
    return aux_root_view_item
    

    if name == "main":

    import sys
    
    app = QApplication(sys.argv)
    
    aux_root_view_item = create_test_data()
    tree_view = TreeView(aux_root_view_item,["a", "b", "c"], ["a", "b"])
    tree_view.show()
    
    app.exec_()
    

    @



  • Did you call setDynamicSortFilter(true) on your proxy model?



  • No, I didn't. And doing (toggle true / false) so does not have any effect on my issue.



  • Hi,
    I know this thread is half a year old but i'm facing
    the same problem right now.
    Did you manage to solve it somehow?

    Ben


Log in to reply