当给定索引处的列尚未创建时,setSectionResizeMode() 崩溃

setSectionResizeMode() crashes when the column at given index isn't created yet

我有一个包含两列的 QTreeView 对象。我希望第一个能够拉伸,第二个具有固定宽度。

有一个答案,我试图将其应用到我的案例中。我将这些设置为我的树头:

header->setSectionResizeMode(0, QHeaderView::Stretch);
header->setSectionResizeMode(1, QHeaderView::Fixed);
header->setStretchLastSection(false);

这样我的程序就崩溃了。我想问题是当我调用 setSectionResizeMode() 时,该列不存在。文档摘录:

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.

现在,当我将这些属性设置为 QTreeWidget 对象时,一切正常:

#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();
}

QTreeView有什么问题?我在使用时什么时候调用setSectionResizeMode()

P.S。这是我的代码:

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;
    }
};

由于列数是通过模型处理的,因此必须在 QTreeView 中设置表头,即表头使用在视图中建立的模型:

TreeHeader header(Qt::Horizontal, &treeView);
treeView.setHeader(&header); // <---
header.setStretchLastSection(false);
// ...