Qt之QHeaderView自定义排序(终极版)

简述

本节主要解决自定义排序衍生的第二个问题-将整形显示为字符串,而排序依然正常。

下面我们介绍三种方案:

  1. 委托绘制
  2. 用户数据
  3. 辅助列

很多人也许会有疑虑,平时都用delegate来绘制各种按钮、图标、图形等操作,它还能排序?当然,它本身是不会排序的,但他的高级用法之一就是-辅助排序。

  • 简述
  • 委托绘制
    • 效果
    • QStyledItemDelegate
    • 眼见不一定为实
  • 用户数据
    • QAbstractTableModel
    • QSortFilterProxyModel
  • 辅助列
    • 效果
    • QAbstractTableModel
    • QSortFilterProxyModel
    • 隐藏辅助列
  • 总结

委托绘制

效果

Qt之QHeaderView自定义排序(终极版)_第1张图片

QStyledItemDelegate

我们可以通过设置显示的文本,然后调用QStyle的drawControl来进行ViewItem的绘制。绘制之后,数据源中的数据依然是qint64的,而我们看到的是绘制之后的文本-QString类型,这样QSortFilterProxyModel默认排序(根据源数据排序)就可以满足我们的要求了。

void SortDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItem viewOption(option);
    initStyleOption(&viewOption, index);
    if (option.state.testFlag(QStyle::State_HasFocus))
        viewOption.state = viewOption.state ^ QStyle::State_HasFocus;

    // 进行大小转换
    if (index.column() == FILE_SIZE_COLUMN)
    {
        qint64 bytes = index.data().toLongLong();
        viewOption.text = bytesToGBMBKB(bytes) ;
        QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &viewOption, painter, viewOption.widget);
    }
    else
    {
        QStyledItemDelegate::paint(painter, viewOption, index);
    }
}

眼见不一定为实

通过效果图我们也可以很明显的看出来,其实内部的数据并不是界面显示的字符串,而是原始的qint64类型的数据。

pTableView->setMouseTracking(true);
connect(pTableView, SIGNAL(entered(QModelIndex)), this, SLOT(showToolTip(QModelIndex)));

void MainWindow::showToolTip(const QModelIndex &index)
{
    if (!index.isValid())
        return;

    int nColumn = index.column();
    if ((nColumn == FILE_SIZE_COLUMN))
        QToolTip::showText(QCursor::pos(), index.data().toString());
}

用户数据

QAbstractTableModel

显示在界面的数据为DisplayRole中的数据,我们可以看到已经通过bytesToGBMBKB转化为字符串,这时我们可以通过设置UserRole添加用户数据将源数据存储起来。

// 表格项数据
QVariant TableModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    int nRow = index.row();
    int nColumn = index.column();
    FileRecord record = m_recordList.at(nRow);

    switch (role)
    {
    case Qt::TextColorRole:
        return QColor(Qt::white);
    case Qt::TextAlignmentRole:
        return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
    case Qt::DisplayRole:
    {
        if (nColumn == FILE_NAME_COLUMN)
        {
            return record.strFileName;
        }
        else if (nColumn == DATE_TIME_COLUMN)
        {
            return record.dateTime;
        }
        else if (nColumn == FILE_SIZE_COLUMN)
        {
            return bytesToGBMBKB(record.nSize);
        }

        return "";
    }
    case Qt::UserRole:
    {
        // 新增代码
        if (nColumn == FILE_SIZE_COLUMN)
            return record.nSize;
    }
    default:
        return QVariant();
    }

    return QVariant();
}

QSortFilterProxyModel

根据用户源数据进行排序。

bool SortFilterProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
    if (!source_left.isValid() || !source_right.isValid())
        return false;

    if ((source_left.column() == FILE_SIZE_COLUMN) && (source_right.column() == FILE_SIZE_COLUMN))
    {
        // 这里我们所取得数据是用户源数据
        QVariant leftData = sourceModel()->data(source_left, Qt::UserRole);
        QVariant rightData = sourceModel()->data(source_right, Qt::UserRole);

        if (leftData.canConvert() && rightData.canConvert())
        {
            return leftData.toLongLong() < rightData.toLongLong();
        }
    }

    return QSortFilterProxyModel::lessThan(source_left, source_right);
}

辅助列

效果

Qt之QHeaderView自定义排序(终极版)_第2张图片

QAbstractTableModel

设置辅助数据

#define FILE_NAME_COLUMN 0          // 文件名
#define DATE_TIME_COLUMN 1          // 修改日期
#define FILE_SIZE_COLUMN 2          // 文件大小
#define FILE_SIZE_HIDDEN_COLUMN 3   // 文件大小隐藏列,显示为字节

// 列数
int TableModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);

    return 4;
}

// 设置表格项数据
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid())
        return false;

    int nColumn = index.column();
    FileRecord record = m_recordList.at(index.row());
    switch (role)
    {
    case Qt::DisplayRole:
    {
        if (nColumn == FILE_NAME_COLUMN)
        {
            record.strFileName = value.toString();
        }
        else if (nColumn == DATE_TIME_COLUMN)
        {
            record.dateTime = value.toDateTime();
        }
        // 新增代码
        else if ((nColumn == FILE_SIZE_COLUMN) || (nColumn == FILE_SIZE_HIDDEN_COLUMN))
        {
            record.nSize = value.toLongLong();
        }

        m_recordList.replace(index.row(), record);
        emit dataChanged(index, index);

        // 新增代码
        if ((nColumn == FILE_SIZE_COLUMN) || (nColumn == FILE_SIZE_HIDDEN_COLUMN))
        {
            int nSizeColumn = (nColumn == FILE_SIZE_COLUMN) ? FILE_SIZE_HIDDEN_COLUMN : FILE_SIZE_COLUMN;
            QModelIndex sizeIndex = this->index(index.row(), nSizeColumn);
            emit dataChanged(sizeIndex, sizeIndex);
        }

        return true;
    }
    default:
        return false;
    }
    return false;
}

// 表格项数据
QVariant TableModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    int nRow = index.row();
    int nColumn = index.column();
    FileRecord record = m_recordList.at(nRow);

    switch (role)
    {
    case Qt::TextColorRole:
        return QColor(Qt::white);
    case Qt::TextAlignmentRole:
        return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
    case Qt::DisplayRole:
    {
        if (nColumn == FILE_NAME_COLUMN)
        {
            return record.strFileName;
        }
        else if (nColumn == DATE_TIME_COLUMN)
        {
            return record.dateTime;
        }
        else if (nColumn == FILE_SIZE_COLUMN)
        {
            return bytesToGBMBKB(record.nSize);
        }
        // 新增代码
        else if (nColumn == FILE_SIZE_HIDDEN_COLUMN)
        {
            return record.nSize;
        }

        return "";
    }
    default:
        return QVariant();
    }

    return QVariant();
}

// 表头数据
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    switch (role)
    {
    case Qt::TextAlignmentRole:
        return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
    case Qt::DisplayRole:
    {
        if (orientation == Qt::Horizontal)
        {
            if (section == FILE_NAME_COLUMN)
                return QStringLiteral("名称");

            if (section == DATE_TIME_COLUMN)
                return QStringLiteral("修改日期");

            if (section == FILE_SIZE_COLUMN)
                return QStringLiteral("大小");

            // 新增代码
            if (section == FILE_SIZE_HIDDEN_COLUMN)
                return QStringLiteral("大小(字节)");
        }
    }
    default:
        return QVariant();
    }

    return QVariant();
}

QSortFilterProxyModel

这里对第三列进行排序,因为第三列的数据是字符串(当然,也可以反转换),所以使用的辅助列数据,获取字节大小后进行对比。

bool SortFilterProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
    if (!source_left.isValid() || !source_right.isValid())
        return false;

    if ((source_left.column() == FILE_SIZE_COLUMN) && (source_right.column() == FILE_SIZE_COLUMN))
    {
        // 获取辅助列索引
        QModelIndex sizeLeftIndex = sourceModel()->index(source_left.row(), FILE_SIZE_HIDDEN_COLUMN);
        QModelIndex sizeRightIndex = sourceModel()->index(source_right.row(), FILE_SIZE_HIDDEN_COLUMN);

        QVariant leftData = sourceModel()->data(sizeLeftIndex);
        QVariant rightData = sourceModel()->data(sizeRightIndex);

        if (leftData.canConvert() && rightData.canConvert())
        {
            return leftData.toLongLong() < rightData.toLongLong();
        }
    }

    return QSortFilterProxyModel::lessThan(source_left, source_right);
}

隐藏辅助列

一般来说,辅助列(数据)只对我们处理数据有帮助,而不直接显示在界面上,所以我们可以将其隐藏pTableView->setColumnHidden(FILE_SIZE_HIDDEN_COLUMN, true);

总结

小小一个排序居然也有这么多门道,真是条条大路通罗马,通过这几节的分享,想必大家对排序有了更深入的了解,更多的知识请参考官方文档。

你可能感兴趣的:(Qt之QHeaderView自定义排序(终极版))