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

2 views with different layout of the same data



  • I have a (main) table model with a 2d array of data and a tableview to represent that data, So far so good, but I want a second view that has the same columns but only one row that shows a value calculated from the whole corresponding column in the first view.

    So I took a QIdentityProxyModel and reimplemented rowCount() and data() and that works fine until rows are added to the sourceModel. They also get added to the second view, despite rowcount() returning 1. (why is that, and is there a way to solve that?)

    I guess I can get it to work the way I want it by using a second model that operates on the same data and manually connect the necessary signals between the two.
    But it feels wrong to have to use two models in this situation.

    Am I right in that feeling? Is there a better way to accomplish what I want, perhaps using some fancy delegate(s)?
    I have used some simple custom delegates before, but they still feel a bit like dark magic to me.


  • Lifetime Qt Champion

    @mahkitah said in 2 views with different layout of the same data:

    They also get added to the second view, despite rowcount() returning 1. (why is that, and is there a way to solve that?)

    Please show some code. I don't think this is true when you implemented it correct.



  • Warning: it's python
    and it couldn't be simpler

    class Proxy(QIdentityProxyModel):
    
        def rowCount(self, parent):
            return 1
    
        def data(self, index, role):
           //...//
    

    Rows are added with this method of the source model

        def add_item(self, item):
            pos = len(self.items)
            self.beginInsertRows(QModelIndex(), pos, pos)
            self.items.append(item)
            self.endInsertRows()
    

  • Lifetime Qt Champion

    Then your view can not show more than one row when Proxy is set as sourcemodel.


  • Lifetime Qt Champion

    Ok, I can reproduce it. This is because QIdentityProxyModel does what it should and transfers all signals (e.g. 'a new row is inserted') through to the view.
    QIdentityProxyModel is not meant to do filtering -> use QSortFilterProxyModel



  • @Christian-Ehrlicher said in 2 views with different layout of the same data:

    Then your view can not show more than one row when Proxy is set as sourcemodel.

    I'm sorry but it does.
    Here is full working minimal code that represents the issue:

    import sys
    from random import randint
    from PySide6.QtWidgets import QApplication, QWidget, QTableView, QPushButton, QVBoxLayout
    from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex, QIdentityProxyModel
    
    def get_me_some_data():
        data = []
        for x in range(4):
            row = []
            for y in range(3):
                row.append(randint(0, 9))
            data.append(row)
        return data
    
    class MainWindow(QWidget):
        def __init__(self):
            super().__init__()
            self.setGeometry(900, 300, 400, 400)
    
            main_model = MyModel()
            view1 = QTableView()
            view1.setModel(main_model)
    
            proxy = Proxy()
            proxy.setSourceModel(main_model)
            view2 = QTableView()
            view2.setModel(proxy)
    
            but = QPushButton('add row')
            but.clicked.connect(main_model.add_row)
            layout = QVBoxLayout(self)
            layout.addWidget(view1)
            layout.addWidget(view2)
            layout.addWidget(but)
            self.show()
    
    
    class MyModel(QAbstractTableModel):
        def __init__(self):
            super().__init__()
            self.things = get_me_some_data()
    
        def rowCount(self, parent):
            return len(self.things)
    
        def columnCount(self, parent):
            return len(self.things[0])
    
        def data(self, index, role):
            if role == Qt.DisplayRole:
                return self.things[index.row()][index.column()]
    
        def add_row(self):
            new_thing = []
            for x in range(3):
                new_thing.append(randint(0, 9))
            pos = len(self.things)
            self.beginInsertRows(QModelIndex(), pos, pos)
            self.things.append(new_thing)
            self.endInsertRows()
    
    class Proxy(QIdentityProxyModel):
        def rowCount(self, parent):
            return 1
    
        def data(self, index, role):
            if role == Qt.DisplayRole:
                things = self.sourceModel().things
                return sum(x[index.column()] for x in things)
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        window = MainWindow()
        sys.exit(app.exec())
    

    screenshot initial state:
    alt text
    after adding 2 rows:
    alt text
    Am I missing something here?



  • @Christian-Ehrlicher said in 2 views with different layout of the same data:

    Ok, I can reproduce it. This is because QIdentityProxyModel does what it should and transfers all signals (e.g. 'a new row is inserted') through to the view.
    QIdentityProxyModel is not meant to do filtering -> use QSortFilterProxyModel

    Yeah, but you'd expect the view to ignore that, with rowCount() being as it is.
    I see now that if you replace the insertRow signals with layoutChanged, the rows are not added to view2. That's a bit inconsistent if you ask me.
    I just moved away from using layoutChanged because it gave me problems with the headerview not being properly synched up.

    I just tried to see if blocking the insertRows signals with disconnect() would solve it, but I couldn't figure it out. Too many possible signatures and I'm a bit too stupid to fully understand the docs.

    But do you agree that, in principal, using a proxymodel is the right solution for this situation?


  • Lifetime Qt Champion

    @mahkitah said in 2 views with different layout of the same data:

    Yeah, but you'd expect the view to ignore that, with rowCount() being as it is

    No because the view does not only react on the rowCount() but also on signals from the model. Use the correct proxy model type.



  • Fair enough, in case of conflicting signals, you can't expect qt to know exactly what the user wants.

    What proxy model should I use then? As far as I can see, the choice is between Identity, Sortfilter, and Transpose.
    I don't need sorting or transposing so that's why I chose Identity.
    And wouldn't other proxy models have the same problem?


  • Lifetime Qt Champion

    @mahkitah said in 2 views with different layout of the same data:

    What proxy model should I use then?

    @Christian-Ehrlicher said in 2 views with different layout of the same data:

    QIdentityProxyModel is not meant to do filtering -> use QSortFilterProxyModel

    And no there are no conflicting signals - the view simply does not look at the rowCount() when it gets a signal that a row is about to be inserted because at this time the rowCount() must not yet increased. It could look at rowCount() during rowsInserted() but imo the view should assume that the model is doing it's stuff correctly (which you don't)



  • @Christian-Ehrlicher said in 2 views with different layout of the same data:

    QIdentityProxyModel is not meant to do filtering -> use QSortFilterProxyModel

    Sorry, missed that.

    But still, This proxy also passes through all the signals. I can see how it can be used for sorting and filtering data, but not how you can use it to stop certain signals from reaching the view.


  • Lifetime Qt Champion

    @mahkitah said in 2 views with different layout of the same data:

    This proxy also passes through all the signals

    It does not. Otherwise filtering would not work.



  • Okay, how should I use it then?

    When I simply replace

    class Proxy(QIdentityProxyModel):
    

    with

    class Proxy(QSortFilterProxyModel):
    

    I get exactly the same behaviour.

    In the docs on QSortFilterProxyModel I can't find anything about changing the signals.
    Can you give me some pointers or maybe an example of how it should be implemented?


  • Lifetime Qt Champion

    Why should you modify any signals? You want to filter your model then do it like it is described in the documentation.



  • Are we not talking about the same thing here? I thought we established that the problem is caused by the rowsInserted signal from the source model that is passed on by the proxy model to the view.
    Therefore, you would need to keep that signal from reaching the view. That is what I mean by changing the signals.

    Please tell me what you mean by "filter your model". I understand that as "filter the data", or as it says in the docs "QSortFilterProxyModel can be used to hide items that do not match a certain filter". It is beyond me how that is related to the issue here. to my understanding, whatever you do to the data, when that rowsInserted signal gets to the view, it will add a row, no matter how much filtering has been going on.


  • Lifetime Qt Champion

    @mahkitah said in 2 views with different layout of the same data:

    so far so good, but I want a second view that has the same columns but only one row that shows a value calculated from the whole corresponding column in the first view.

    Add a new row to your base model which contains the calculated values. Then use two QSortFilterProxyModels to either show only the sum row or the others.



  • Wow, that's a completely different approach, but it makes a lot of sense, and it is why I asked this question. I wanted to know if my general approach could be improved.

    thanks for sticking with me


Log in to reply