Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt: How to set header on child tables in QAbstractItemModel?

Tags:

qt

QAbstractItemModel has a setHeaderData( int section, ..... ) method that takes a section which is either a row or a column depending on the header orientation. I have a model that contains several tables which are all children of the top item. That is, the first level of my model hierarchy (under the invisible root) consists of 8 rows, each row has a child element which is a table (8 tables total of course). It appears setHeaderData will provide views with a header for the first level, but how do I specify header data for these child tables? QTableView has a setRootIndex() method so it can drill down into the model hierarchy and display the data in these child tables, and I would expect setHeaderData to also take a root index, but it doesn't. I can set the header manually on the QTableView, but that's a lot messier--is there a better solution?

like image 269
Scott Avatar asked May 13 '15 17:05

Scott


1 Answers

Unfortunately, model API doesn't provide a good way to do this. The simplest solution is to make your model return different headerData depending on root index currently in use. However, this means that the model needs to know view's state (more specifically, the root index), which is not what you usually want.

I think setting a proxy model could be an elegant solution to this problem. Here is how it can be implemented:

class ChildHeadersProxy : public QSortFilterProxyModel {
public:
  static const int HorizontalHeaderRole = Qt::UserRole + 1;
  static const int VerticalHeaderRole = Qt::UserRole + 2;

  void setRootIndex(const QModelIndex& index) {
    m_rootIndex = index;
    if (sourceModel()) {
      emit headerDataChanged(Qt::Horizontal, 0, sourceModel()->columnCount(m_rootIndex));
      emit headerDataChanged(Qt::Vertical, 0, sourceModel()->rowCount(m_rootIndex));
    }
  }

  QVariant headerData(int section, Qt::Orientation orientation, 
                      int role = Qt::DisplayRole) const {
    if (sourceModel() && m_rootIndex.isValid()) {
      int role = orientation == Qt::Horizontal ? HorizontalHeaderRole : VerticalHeaderRole;
      QStringList headers = sourceModel()->data(m_rootIndex, role).toStringList();
      if (section >= 0 && section < headers.count()) {
        return headers[section];
      }
    }
    return QSortFilterProxyModel::headerData(section, orientation, role);
  }

private:
  QModelIndex m_rootIndex;

};

This proxy model uses headers provided by the source model through two custom roles. For example, if you use QStandardItemModel, setting headers is as simple as this:

model.item(0, 1)->setData(QStringList() << "h1" << "h2", 
  ChildHeadersProxy::HorizontalHeaderRole);
model.item(0, 1)->setData(QStringList() << "vh1" << "vh2", 
  ChildHeadersProxy::VerticalHeaderRole);

where model.item(0, 1) is corresponding root item. Setting the view will look like:

QTableView view;
ChildHeadersProxy proxy;
proxy.setSourceModel(&model);
view.setModel(&proxy);

And changing the root index will look like:

view.setRootIndex(proxy.mapFromSource(model.index(0, 1)));
proxy.setRootIndex(model.index(0, 1));
like image 174
Pavel Strakhov Avatar answered Sep 20 '22 15:09

Pavel Strakhov