0

How can I use pyqt5's model-view concept to allow a user access to a 'hidden' column in a view?

I often subclass QAbstractTableModel to show a Pandas DataFrame in PyQt5. (h/t to posts on this site). See a MRE below. In using that model (but I am sure the principle/answer will apply to other models too), I am wondering if there is a Model-View concept I am missing that can help me.

For cleanliness of the display for the majority of users/use cases, I have removed a column from the model. In the MRE I do that implicitly by not including column 'd'. I include only the 'normalized_columns', because in most use cases the data relegated to column d is worthless/unpredictable/ugly/not-normalized/etc. But what do I do in the rare case a user wants to 'drill down' into that column d data?

Is the solution to pass ALL the data to QAbstractTableModel, maybe storing an attribute self._all_data_df ?, but then set self._df to the 'normalized only' data, and then, if the user right clicks and asks to 'show me column d for this row', (I could add support for a context menu), THEN check which row he's clicked on in self._df, associate that row to the corresponding row in self._all_data_df, and then serve it to him? But that intuitively feels like an implementation that doesn't get the benefit Model/View.

I sense there is a more general model-view best practice question here around either how to show just a portion of the data while keeping all the data available in memory, or how to keep a reference to 'the real/full data' while only showing the user a user-friendly version, etc, but I am still too much a novice to know the obvious answer.

import builtins

import pandas as pd

from PyQt5.QtWidgets import QApplication, QTableView
from PyQt5.QtCore import QAbstractTableModel, Qt


class DfModelLight(QAbstractTableModel):

    def __init__(self, data=pd.DataFrame()):
        QAbstractTableModel.__init__(self)
        self._df = data

    def rowCount(self, parent=None):
        return self._df.shape[0]

    def columnCount(self, parent=None):
        return self._df.shape[1]

    def data(self, index, role=Qt.DisplayRole):
        if index.isValid():
            val = self._df.iloc[index.row()][index.column()]
            if role == Qt.DisplayRole:
                return str(val)
        return None

    def headerData(
            self, 
            section: int, 
            orientation: Qt.Orientation,
            role: int = Qt.DisplayRole
            ):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                col_object = self._df.columns[section]
                if isinstance(col_object, builtins.str):
                    return col_object
                elif isinstance(col_object, builtins.tuple):
                    return ' '.join(col_object)
                else:
                    raise ValueError
            else:
                return str(self._df.index[section])
        return None
    
    def set_df(self, df):
        self.beginResetModel()
        self._df = df.copy()
        self.endResetModel()
        
    def get_df(self):
        return self._df
        

if __name__ == '__main__':
    app = QApplication(sys.argv)
    
    data = {
        'a': ['Mary', 'Jim', 'John'],
        'b': [100, 200, 300],
        'c': ['a', 'b', 'c'],
        'd':[{'unwieldy_data':'randomness','unknown_data':None,'uncontrolled_key':[0,1,0,1]},
             {'unwieldy_data':'more_randomness','other_data':3.14,'mykey':[]},
             {'unwieldy_data':'even_more_randomness','foreign_data':9999999,'id':[0,1,2,3]}
             ]
        }
    normalized_columns = ['a', 'b', 'c']
    df = pd.DataFrame(data)
    df = df[normalized_columns]
    model = DfModelLight(df)
    view = QTableView()
    view.setModel(model)
    view.resize(800, 600)
    view.show()
    sys.exit(app.exec_())
10mjg
  • 573
  • 1
  • 6
  • 18
  • 3
    [`QTableView.setColumnHidden()`](https://doc.qt.io/qt-5/qtableview.html#setColumnHidden)? – musicamante Jan 19 '22 at 03:25
  • @musicamante ... ha - i knew there must be a simpler way... – 10mjg Jan 19 '22 at 03:33
  • @musicamante - that works for me in simple scenarios. i have one real world more complicated implementation question - if our proverbial column 'd' could show up in a dynamic column number, and i need to 'look for it' in the model to know which column to pass setColumnHidden to, how do i do that so that I can hide it by default? does the model's endResetModel method call a method in the View I can subclass/overload? obviously the model doesn't know about the view, so, not sure how to go from model update to view check model for column and hide it ... thanks ... – 10mjg Jan 19 '22 at 14:41
  • 1
    As you already know, the model doesn't know about the view, nor it should. There's no default behavior for that, so you have to provide it: supposing that the model won't change its size at runtime, if the column that has to be hidden can be "known" to the model when it's initialized, then create a function or variable that returns the list of hidden columns and call `setColumnHidden()` accordingly. You can even create a QTableView subclass that will automatically interface with the model by subclassing `setModel()` and implement there the hiding. – musicamante Jan 19 '22 at 14:48

0 Answers0