Qt模型/视图:自定义QAbstractItemModel/QAbstractItemDelegate

前言

因为工作需要,对Qt的Model/View/Degelate进行了学习,事实证明,实践是学习的最佳途径。

主要内容:

1.自定义实现Model功能,各虚函数的功能作用与实现。

2.自定义degelate功能,各虚函数的功能作用与实现。

3.实现复选框选择功能、绘制进度条、QComboBox、QSpinBox控制输入等。

界面

代码

MyTableModel.h

#ifndef MYTABLEVIEW_H
#define MYTABLEVIEW_H

#include <QAbstractTableModel>

struct DataModel
{
    bool check;
    QString name;
    qint32 age;
    int sex;
    qint32 skill;
};

struct HeaderParam
{
    QString title;
    Qt::Alignment align;
};

class MyTableDelegate;

class MyTableModel : public QAbstractTableModel
{
    Q_OBJECT
    friend class MyTableDelegate;
public:
    ~MyTableModel();
    MyTableModel(QObject* parent = Q_NULLPTR);

    // 控制 TableView 水平标题显示
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;

    // 指示 TableView 行列数目
    int rowCount(const QModelIndex& parent = QModelIndex()) const;
    int columnCount(const QModelIndex& parent = QModelIndex()) const;

    // QModelIndex索引控制
    QModelIndex parent(const QModelIndex& index) const;
    QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const;

    // 控制 Model 数据读写
    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
    Qt::ItemFlags flags(const QModelIndex& index) const;
    bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);

    // 添加条目
    void addData(const DataModel& item);

    enum
    {
        HEADER_CHECK = 0,
        HEADER_NAME,
        HEADER_AGE,
        HEADER_SEX,
        HEADER_SKILL,
    };

private:
    void initialize();

private:
    QList<DataModel> datas;
    QList<HeaderParam> header;
};

#endif // MYTABLEVIEW_H

MyTableModel.cpp

#include "mytablemodel.h"

MyTableModel::~MyTableModel()
{
}

MyTableModel::MyTableModel(QObject* parent)
    : QAbstractTableModel(parent)
{
    initialize();
}

QVariant MyTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Vertical || section < 0 || section >= header.size())
    {
        return QVariant();
    }

    if (role == Qt::DisplayRole)
    {
        // 控制标题文字显示
        return header.at(section).title;
    }
    else if (role == Qt::TextAlignmentRole)
    {
        // 控制标题文字对齐显示
        return (uint)header.at(section).align;
    }

    /*由此看出 role 在模型/视图结构中指定交换数据的类型描述*/

    return QVariant();
}

/*
返回 parent 索引所包含的行数
其中,无效的 QModelIndex表示根索引。我的视图只有6行5列的table表格,没有树形结构嵌套,所以只在无效索引时返回数据行数,而有效的索引由于没有子项数据,所以返回0值。
*/
int MyTableModel::rowCount(const QModelIndex& parent) const
{
    if (parent.isValid())
    {
        return 0;
    }

    return datas.size();
}

/*
返回 parent 索引所包含的列数
其中,无效的 QModelIndex表示根索引。我的视图只有6行5列的table表格,没有树形结构嵌套,所以只在无效索引时返回数据列数,而有效的索引由于没有子项数据,所以返回0值。
*/
int MyTableModel::columnCount(const QModelIndex& parent) const
{
    if (parent.isValid())
    {
        return 0;
    }

    return header.size();
}

/* 
返回index索引的父级索引
我的视图只有6行5列的table表格,没有树形结构嵌套,所以任何索引的父级索引都返回无效索引。
*/
QModelIndex MyTableModel::parent(const QModelIndex& index) const
{
    return QModelIndex();
}

/* 
返回parent索引的第row行第column列索引
我的视图只有6行5列的table表格,没有树形结构嵌套,所以任何有效索引的子项都是空索引。而根索引的子项则是table表中的每一项数据。
*/
QModelIndex MyTableModel::index(int row, int column, const QModelIndex& parent) const
{
    if (parent.isValid() || row < 0 || row >= datas.size() || column < 0 || column >= header.size())
    {
        return QModelIndex();
    }

    return createIndex(row, column);
}

// 获取 index 的 role 指定类型的数据
QVariant MyTableModel::data(const QModelIndex& index, int role) const
{
    if (!index.isValid())
    {
        return QVariant();
    }

    int row = index.row();
    int column = index.column();
    if (row < 0 || row >= datas.size() || column < 0 || column >= header.size())
    {
        return QVariant();
    }

    if (role == Qt::DisplayRole)
    {
        switch (column)
        {
        case HEADER_CHECK:
            return datas.at(row).check;
            break;

        case HEADER_NAME:
            return datas.at(row).name;
            break;

        case HEADER_AGE:
            return datas.at(row).age;
            break;

        case HEADER_SEX:
            return datas.at(row).sex;
            break;

        case HEADER_SKILL:
            return datas.at(row).skill;
            break;

        default:
            break;
        }
    }
    else if (role == Qt::TextAlignmentRole)
    {
        return (uint)header.at(column).align;
    }

    return QVariant();
}

// 获取 index 的标识,可用来控制项的读写权限等
Qt::ItemFlags MyTableModel::flags(const QModelIndex& index) const
{
    if (!index.isValid())
    {
        return Qt::NoItemFlags;
    }

    Qt::ItemFlags flags = QAbstractItemModel::flags(index);
    int column = index.column();
    if (column >= 0 && column < header.size() && column > HEADER_CHECK && column < HEADER_SKILL)
    {
        flags |= Qt::ItemIsEditable;
    }

    return flags;
}

// 设置 index 的 role 类型数据
bool MyTableModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
    if (!index.isValid() || role != Qt::EditRole)
    {
        return false;
    }

    int row = index.row();
    int column = index.column();

    switch (column)
    {
    case HEADER_CHECK:
        datas[row].check = value.toBool();
        break;

    case HEADER_NAME:
        datas[row].name = value.toString();
        break;

    case HEADER_AGE:
        datas[row].age = value.toInt();
        break;

    case HEADER_SEX:
        datas[row].sex = value.toInt();
        break;

    case HEADER_SKILL:
        datas[row].skill = value.toInt();
        break;

    default:
        break;
    }

    // 数据设置好通知视图更新显示
    emit dataChanged(index, index, {Qt::DisplayRole});
    return true;
}

void MyTableModel::initialize()
{
    header.append({"", Qt::AlignCenter});
    header.append({"姓名", Qt::AlignLeft | Qt::AlignVCenter});
    header.append({"年龄", Qt::AlignLeft | Qt::AlignVCenter});
    header.append({"性别", Qt::AlignLeft | Qt::AlignVCenter});
    header.append({"技能点", Qt::AlignCenter});
}

void MyTableModel::addData(const DataModel& item)
{
    beginInsertRows(QModelIndex(), datas.size(), datas.size());
    datas.append(item);
    endInsertRows();
}

MyTableDelegate.h

#ifndef MYTABLEITEMDELEGATE_H
#define MYTABLEITEMDELEGATE_H

#include <QStyledItemDelegate>

class MyTableDelegate : public QStyledItemDelegate
{
public:
    ~MyTableDelegate();
    MyTableDelegate(QObject* parent = Q_NULLPTR);

    // 编辑控件控制
    QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
    void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const;

    // 数据交换
    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
    void setEditorData(QWidget* editor, const QModelIndex& index) const;

    // 控制单元格显示
    void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;

    // 实现特定编辑业务
    bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index);

};

#endif // MYTABLEITEMDELEGATE_H

MyTableDelegate.cpp

#include <QPainter>
#include <QLineEdit>
#include <QSpinBox>
#include <QComboBox>
#include <QCheckBox>
#include <QMouseEvent>
#include <QApplication>
#include <QAbstractItemView>
#include "mytabledelegate.h"
#include "mytablemodel.h"

MyTableDelegate::~MyTableDelegate()
{
}

MyTableDelegate::MyTableDelegate(QObject* parent)
    : QStyledItemDelegate(parent)
{
}

void MyTableDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    QStyleOptionViewItem opt(option);
    opt.displayAlignment = (Qt::Alignment)index.data(Qt::TextAlignmentRole).toUInt();
    if (opt.state & QStyle::State_HasFocus)
    {
        opt.state ^= QStyle::State_HasFocus;
    }
    if (option.state & QStyle::State_Selected)
    {
        opt.rect.setBottom(option.rect.bottom() - 1);
        opt.backgroundBrush = option.state & QStyle::State_Active
                ? option.palette.brush(QPalette::Active, QPalette::Highlight)
                : option.palette.brush(QPalette::Inactive, QPalette::Highlight);
    }

    if (index.column() == MyTableModel::HEADER_SEX)
    {
        // 控制性别列显示
        opt.text = index.data().toInt() > 0 ? "男" : "女";
        QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
    }
    else if (index.column() == MyTableModel::HEADER_CHECK)
    {
        // 控制 check 列显示,QCheckBox 标识
        QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);

        QStyleOptionButton checkBoxStyle;
        checkBoxStyle.state = index.data().toBool() ? QStyle::State_On : QStyle::State_Off;
        checkBoxStyle.state |= QStyle::State_Enabled;
        checkBoxStyle.rect = option.rect;

        QCheckBox checkBox;
        QApplication::style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &checkBoxStyle, painter, &checkBox);
    }
    else if (index.column() == MyTableModel::HEADER_SKILL)
    {
        // 控制技能点列显示,QProgressBar 显示
        int num = index.data().toInt();
        QStyleOptionProgressBar barOpt;
        barOpt.rect = option.rect;
        barOpt.minimum = 0;
        barOpt.maximum = 100;
        barOpt.progress = num;
        barOpt.text = QString::number(num) + "%";
        barOpt.textVisible = true;
        barOpt.textAlignment = (Qt::Alignment)index.data(Qt::TextAlignmentRole).toUInt();
        QApplication::style()->drawControl(QStyle::CE_ProgressBar, &barOpt, painter);
    }
    else
    {
        opt.text = index.data().toString();
        QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
    }

    // 绘制下边框虚线
    QPen pen(painter->pen());
    painter->save();
    pen.setStyle(Qt::DashLine);
    pen.setDashPattern(QVector<qreal>() << 1 << 1);
    painter->setPen(pen);
    painter->drawLine(QPointF(option.rect.left(), option.rect.bottom()), QPointF(option.rect.right(), option.rect.bottom()));
    painter->restore();
}

bool MyTableDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
{
    // 控制 check 列勾选功能
    if (index.column() == MyTableModel::HEADER_CHECK
        && event->type() == QEvent::MouseButtonRelease)
    {
        model->setData(index, !index.data().toBool());
        event->accept();
        return true;
    }

    return QStyledItemDelegate::editorEvent(event, model, option, index);
}

QWidget* MyTableDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    // 根据不同的列,创建不同的编辑器
    if (index.column() == MyTableModel::HEADER_SEX)
    {
        QComboBox* box = new QComboBox(parent);
        box->addItems(QStringList() << "女" << "男");
        return box;
    }
    else if (index.column() == MyTableModel::HEADER_AGE)
    {
        QSpinBox* box = new QSpinBox(parent);
        box->setMinimum(0);
        return box;
    }

    return QStyledItemDelegate::createEditor(parent, option, index);
}

void MyTableDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    // 指示编辑器的位置和大小
    if (editor != nullptr)
    {
        editor->setGeometry(option.rect);
    }
}

void MyTableDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
{
    if (index.column() == MyTableModel::HEADER_SEX)
    {
        QComboBox* box = static_cast<QComboBox*>(editor);
        model->setData(index, box->currentIndex());
        return;
    }
    else if (index.column() == MyTableModel::HEADER_AGE)
    {
        QSpinBox* box = static_cast<QSpinBox*>(editor);
        model->setData(index, box->value());
        return;
    }

    return QStyledItemDelegate::setModelData(editor, model, index);
}

void MyTableDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
    if (index.column() == MyTableModel::HEADER_SEX)
    {
        QComboBox* box = static_cast<QComboBox*>(editor);
        box->setCurrentIndex(index.data().toInt() > 0 ? 1 : 0);
    }
    else if (index.column() == MyTableModel::HEADER_AGE)
    {
        QSpinBox* box = static_cast<QSpinBox*>(editor);
        box->setValue(index.data().toInt());
    }
    else
    {
        QLineEdit* edit = static_cast<QLineEdit*>(editor);
        if (edit != nullptr)
        {
            edit->setText(index.data().toString());
        }
    }
}

完整源码链接:

https://download.csdn.net/download/gsl2016/70734839

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值