I have a QTreeView
object with two columns. I want the first one to be able to stretch, and the second one to have a fixed width.
This question has an answer which I tried to apply to my case. I set these to my tree header:
header->setSectionResizeMode(0, QHeaderView::Stretch);
header->setSectionResizeMode(1, QHeaderView::Fixed);
header->setStretchLastSection(false);
This way my program crashes. I guess the problem is that when I call setSectionResizeMode()
, the column doesn't exist. Excerpt from docs:
void QHeaderView::setSectionResizeMode(int logicalIndex, QHeaderView::ResizeMode mode)
Sets the constraints on how the section specified by logicalIndex in the header can be resized to those described by the given mode. The logical index should exist at the time this function is called.
Now, when I set these properties to a QTreeWidget
object, everything is fine:
#include <QApplication>
#include <QTreeWidget>
#include <QHeaderView>
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QTreeWidget treeWidget;
treeWidget.setColumnCount(2);
QList<QTreeWidgetItem *> items;
for (int i = 0; i < 5; ++i)
items.append(new QTreeWidgetItem(&treeWidget,
QStringList{ "item", QString("%1").arg(i) }));
treeWidget.insertTopLevelItems(0, items);
auto header = treeWidget.header();
header->setSectionResizeMode(0, QHeaderView::Stretch);
header->setSectionResizeMode(1, QHeaderView::Fixed);
header->setStretchLastSection(false);
header->setDefaultSectionSize(50);
treeWidget.show();
return QApplication::exec();
}
What's the problem with QTreeView
? When should I call setSectionResizeMode()
when I'm using it?
P.S. here's my code:
main.cpp
#include <QApplication>
#include <QTreeWidget>
#include "TreeModel.h"
#include "TreeItem.h"
int main(int argc, char **argv)
{
QApplication app(argc, argv);
TreeModel model;
NodeDelegate delegate;
QTreeView treeView;
treeView.setModel(&model);
treeView.setItemDelegate(&delegate);
TreeHeader header(Qt::Horizontal, &treeView);
header.setStretchLastSection(false);
header.setSectionResizeMode(0, QHeaderView::Stretch); // <--- crashes, column 0 doesn't exist yet
header.setSectionResizeMode(1, QHeaderView::Fixed); // <--- crashes, column 1 doesn't exist yet
header.setDefaultSectionSize(50);
treeView.setHeader(&header);
treeView.show();
return QApplication::exec();
}
TreeItem.h
#pragma once
#include <QVariant>
#include <QVector>
#include <QStyleOptionViewItem>
#include <QItemDelegate>
#include <QPainter>
class TreeItem
{
public:
TreeItem(QVector<QVariant> data, TreeItem *parent = nullptr)
: m_itemData(std::move(data))
, m_parentItem(parent)
{
}
~TreeItem()
{
qDeleteAll(m_childItems);
}
TreeItem *child(const int row) const
{
return m_childItems.value(row);
}
int childCount() const
{
return m_childItems.count();
}
int row() const
{
if (m_parentItem)
return m_parentItem->m_childItems.indexOf(const_cast<TreeItem *>(this));
return 0;
}
int columnCount() const
{
return m_itemData.count();
}
QVariant data(const int column) const
{
return m_itemData.value(column);
}
TreeItem *parentItem() const
{
return m_parentItem;
}
bool setData(const int column, const QVariant &value)
{
return column >= 0 && column < m_itemData.size() && (m_itemData[column] = value, true);
}
bool insertRows(const int position, const int count, const int columns)
{
if (position < 0 || position > m_childItems.size())
return false;
for (auto row = 0; row < count; ++row)
{
// create a new row with columns number of columns
const QVector<QVariant> newData(columns);
m_childItems.insert(position, new TreeItem{ newData, this });
}
return true;
}
bool removeRows(const int position, const int count)
{
if (position < 0 || position > m_itemData.size())
return false;
for (auto row = 0; row < count; ++row)
delete m_childItems.takeAt(position);
return true;
}
bool insertColumns(const int position, const int columns)
{
if (position < 0 || position > m_childItems.size())
return false;
for (auto column = 0; column < columns; ++column)
m_itemData.insert(position, QVariant{});
for (auto child : m_childItems)
child->insertColumns(position, columns);
return true;
}
bool removeColumns(const int position, const int columns)
{
if (position < 0 || position > m_childItems.size())
return false;
for (auto column = 0; column < columns; ++column)
m_itemData.remove(position);
for (auto child : m_childItems)
child->removeColumns(position, columns);
return true;
}
bool paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QRect rect = option.rect;
painter->drawText(rect, "data");
return true;
}
private:
QList<TreeItem *> m_childItems;
QVector<QVariant> m_itemData;
TreeItem *m_parentItem = nullptr;
};
class NodeDelegate : public QItemDelegate
{
public:
NodeDelegate(QObject *parent = nullptr)
: QItemDelegate(parent)
{
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
if (!index.isValid()) return;
auto node = static_cast<TreeItem *>(index.internalPointer());
if (!node->paint(painter, option, index))
QItemDelegate::paint(painter, option, index);
}
};
TreeModel.h
#pragma once
#include <QAbstractItemModel>
#include <QHeaderView>
#include "TreeItem.h"
/*
* A read-only tree model
*/
class TreeModel final : public QAbstractItemModel
{
Q_OBJECT
public:
TreeModel(QObject *parent = nullptr)
: QAbstractItemModel(parent)
{
const auto rootData = QVector<QVariant>{} << "Number" << "Data";
m_pRootItem = new TreeItem{ rootData };
}
QModelIndex index(const int row, const int column, const QModelIndex &parent /* = QModelIndex() */) const
{
return QModelIndex{};
}
QModelIndex parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex{};
const auto childItem = getItem(index);
const auto parentItem = childItem->parentItem();
// don't return a model index corresponding to the root item
if (parentItem == m_pRootItem)
return QModelIndex{};
return createIndex(parentItem->row(), 0, parentItem);
}
int rowCount(const QModelIndex &parent /* = QModelIndex() */) const
{
const auto parentItem = getItem(parent);
return parentItem->childCount();
}
int columnCount(const QModelIndex &parent /* = QModelIndex() */) const
{
// all items have the same number of columns
return m_pRootItem->columnCount();
}
QVariant data(const QModelIndex &index, const int role /* = Qt::DisplayRole */) const
{
if (!index.isValid())
return QVariant{};
if (role != Qt::DisplayRole)
return QVariant{};
const auto item = static_cast<TreeItem *>(index.internalPointer());
return item->data(index.column());
}
Qt::ItemFlags flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}
QVariant headerData(const int section, const Qt::Orientation orientation, const int role /* = Qt::DisplayRole */) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
return m_pRootItem->data(section);
return QVariant{};
}
bool setData(const QModelIndex &index, const QVariant &value, int role /* = Qt::EditRole */)
{
if (role != Qt::EditRole)
return false;
auto item = getItem(index);
const auto result = item->setData(index.column(), value);
if (result)
emit dataChanged(index, index);
return result;
}
bool setHeaderData(int section, Qt::Orientation orientation,
const QVariant &value, int role)
{
if (role != Qt::EditRole || orientation != Qt::Horizontal)
return false;
const auto result = m_pRootItem->setData(section, value);
if (result)
emit headerDataChanged(orientation, section, section);
return result;
}
TreeItem *getItem(const QModelIndex &index) const
{
if (index.isValid())
{
if (const auto item = static_cast<TreeItem *>(index.internalPointer()))
return item;
}
return m_pRootItem;
}
TreeItem *m_pRootItem{};
};
class TreeHeader : public QHeaderView
{
public:
TreeHeader(Qt::Orientation orientation, QWidget *parent = nullptr)
: QHeaderView(orientation, parent)
{
}
protected:
void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override
{
if (!rect.isValid()) return;
QStyleOptionHeader option;
option.initFrom(this);
option.text = logicalIndex == 0 ? "Header text" : "#";
if (logicalIndex == 1)
option.textAlignment = Qt::AlignLeft;
}
};
You must set the header in QTreeView since the number of columns is handled through the model, ie the header uses the model that was established in the view:
TreeHeader header(Qt::Horizontal, &treeView);
treeView.setHeader(&header); // <---
header.setStretchLastSection(false);
// ...