Qt 自带树形表满足不了?教你自定义树形图

小王子🤴 2020-08-19 22:41:20 9066
Qt 自定义树形图

很多时候Qt自带的控件已经满足不了我们的需求,这个时候就应该自定义控件,本篇文章就来介绍下自定义树形表。
其实严格意义上来说这个不是真正的自定控件,而是使用mvc模式进行视图重绘。

MVC全称是 Model View Controller,是一种非常非常流行的架构模式,相知识网上一抓一大把,这里就不再阐述。

实际上Qt中没有MVC模式,Qt中的MVC并不叫MVC,而是叫“MVD”,Qt中没有Controller的说法,而是使用了另外一种抽象: Delegate (委托) ,其行为和传统的MVC是相同的。写过C#的同学肯定对delegate就不陌生了,这里delegate的用法就是负责协调Model和View之间的数据。

其思想如下图所示:

简单的介绍下Qt的MVC,因为重绘树形图使用的就是Qt的MVD,QStandardItemModel(M)、treeView(V)、QStyledItemDelegate(D),接下来废话少说,上代码,注释解释。

class QTreeWidgetEx : public QTreeView
{
    Q_OBJECT
public:
    explicit QTreeWidgetEx(QWidget *parent = nullptr);
    int addRow(int parentNode,QString text,QVariant v = QVariant());
    QStandardItem* addParentNode(QString text,QString key);//添加父节点
    QStandardItem* addChilderNode(QStandardItem *parentNode,QString name,int decimal,QString unit,QString key);//添加子节点
public slots:
private:
    QStandardItemModel *mode = nullptr;
};

TreeWidgetEx::QTreeWidgetEx::QTreeWidgetEx(QWidget *parent) : QTreeView(parent)
{
//创建mode
    mode = new QStandardItemModel();
    //设置mode到视图
    this->setModel(mode);

//创建代理
    ItemDelegate *itemDelegate = new ItemDelegate(this);
//设置代理
    this->setItemDelegate(itemDelegate);
//视图禁止编辑
    this->setEditTriggers(QAbstractItemView::NoEditTriggers);
//禁止显示三角标
    this->setRootIsDecorated(false);
//子节点缩进
    this->setIndentation(0);
//隐藏滚动条
    this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
//禁止双击展开
//    this->setExpandsOnDoubleClick(false);
}
//添加一行
int TreeWidgetEx::QTreeWidgetEx::addRow(int parentNode, QString text,QVariant v)
{
    QStandardItem *item = new QStandardItem(text);
  //设置节点高度
    item->setSizeHint(QSize(0,80));
    int index = -1;
    if(parentNode < 0)
    {
        mode->appendRow(item);
        index = mode->rowCount()-1;
    }
    else
    {
        mode->item(parentNode,0)->appendRow(item);
        index = mode->item(parentNode,0)->rowCount()-1;
    }

    return index;
}

//添加父节点
QStandardItem* TreeWidgetEx::QTreeWidgetEx::addParentNode(QString text, QString key)
{
    QStandardItem *item = new QStandardItem(text);
    item->setData(key);
    //设置节点高度
    item->setSizeHint(QSize(0,80));
    mode->appendRow(item);
    parentNodeMap.insert(key,item);
    return item;

}
//添加子节点
QStandardItem* TreeWidgetEx::QTreeWidgetEx::addChilderNode(QStandardItem *parentNode, QString name, int decimal, QString unit, QString key)
{
    QStandardItem *item = new QStandardItem(name);
    item->setData(key);
    //设置节点高度
    item->setSizeHint(QSize(0,80));
    parentNode->appendRow(item);
    return item;
}
//代理类,真正的自定义在这,重绘treeView
class ItemDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    explicit ItemDelegate(QWidget *parent = nullptr);
~ItemDelegate();
//重绘事件
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
//点击事件
bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index);
signals:
//按钮点击信号
    void pushButtonClick(const QModelIndex &index);
private:
    QPoint m_mousePoint;  // 鼠标位置
    QScopedPointer<QPushButton> m_PushButton;//按钮
    QStringList m_list;
    int m_nSpacing;  // 按钮之间的间距
    int m_nWidth;  // 按钮宽度
    int m_nHeight;  // 按钮高度
    int m_nType;  // 按钮状态-1:划过 2:按下
    QTreeView *tree = nullptr;
};
TreeWidgetEx::ItemDelegate::ItemDelegate(QWidget *parent)
    : QStyledItemDelegate(parent),
      m_PushButton(new QPushButton()),
      m_nSpacing(5),
      m_nWidth(35),
      m_nHeight(35)
{
    // 设置按钮正常、划过、按下样式
    m_PushButton->setStyleSheet("QPushButton {background-color: transparent;}");
    tree = reinterpret_cast<QTreeView*>(parent);
}

TreeWidgetEx::ItemDelegate::~ItemDelegate()
{

}

void TreeWidgetEx::ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
//保存按钮状态
    painter->save();
    QStyleOptionViewItem viewOption(option);
    initStyleOption(&viewOption, index);
    if (option.state.testFlag(QStyle::State_HasFocus))
        viewOption.state = viewOption.state ^ QStyle::State_HasFocus;

//抗锯齿绘图
    painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);

    //绘制背景
    QColor colorBg = QColor(53, 77, 159);
    //操作状态 选择、划过等
    if (option.state & QStyle::State_Selected) {
        if(index.data(TreeWidgetEx::selectRole).isValid())
            colorBg = QColor(53, 77, 100);
    } else if (option.state & QStyle::State_MouseOver) {
    } else {
    }
    painter->fillRect(option.rect, colorBg);

    int xPoint = 0;

//绘制父节点
    if(index.parent() == QModelIndex())
    {
        if(static_cast<QStandardItemModel*>(tree->model())->rowCount(index) >= 1)
        {
            //绘制三角形图片
            painter->setPen(QColor(255,255,255));
            painter->setBrush(QColor(255,255,255));

            QVector<QPointF> points;
            int margin = option.rect.width()/32/*25*/;
            //绘制的三角形所在的正方形框矩阵
            QRect rect = option.rect;
            int rwh = qMin(option.rect.width()/20,option.rect.height()/4);
            rect.setX(option.rect.x() + margin);
            rect.setY(option.rect.y() + (option.rect.height() - rwh)/2/*/80*23*/);
            rect.setWidth(rwh);
            rect.setHeight(rwh);

            xPoint = rect.right();

            if (!tree->isExpanded(index)) {
            //收起的三角形
                points.append(QPointF(rect.x(), rect.y()));
                points.append(QPointF(rect.x(), rect.y()+rect.height()));
                points.append(QPointF(rect.x()+rect.width(), rect.y()+rect.height()/2));
            } else {
            //展开的三角形
                points.append(QPointF(rect.x(), rect.y()));
                points.append(QPointF(rect.x()+rect.width(), rect.y()));
                points.append(QPointF(rect.x()+rect.width()/2, rect.y()+rect.height()));
            }

            painter->drawPolygon(points);
        }

        //绘制分隔线条

        painter->setPen(QPen(QColor(255,255,255) 1));
        painter->drawLine(QPointF(option.rect.x(), option.rect.y()),
                          QPointF(option.rect.x() + option.rect.width(), option.rect.y()));

    }

//    绘制条目文字
    QColor colorText = QColor(255, 255, 255);
    painter->setPen(QPen(colorText));
    //绘制文字离左边的距离
    int margin = static_cast<int>((static_cast<double>(option.rect.width())/400.0)*80.0);
    int textWidget = static_cast<int>((static_cast<double>(option.rect.width())/400.0)*200.0);
    int margin1 = static_cast<int>((static_cast<double>(option.rect.width())/400.0)*200.0);

    QRect rect = option.rect;
    rect.setX(option.rect.x() + margin);
    rect.setWidth(option.rect.width() - margin);

    QFont normalFont("Segoe UI", 24);
    QString text = index.data(Qt::DisplayRole).toString();
        for(int i = 24;i > 0;i--){
            normalFont.setPointSize(i);
            QFontMetrics fontMetrics(normalFont);
            if(fontMetrics.boundingRect(text).width() < textWidget)
                break;
        }

    painter->setFont(normalFont);
    painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, text);

    // 计算按钮显示区域
    int nTop = static_cast<int>(static_cast<double>(option.rect.height())/80.0*23);
    // 绘制按钮
    QStyleOptionButton button;
    button.rect = QRect(option.rect.right() - static_cast<int>(static_cast<double>(option.rect.width())/400.0*19) - static_cast<int>(static_cast<double>(option.rect.height())/80.0*35),
                        option.rect.top() + nTop,  static_cast<int>(static_cast<double>(option.rect.height())/80.0*35), static_cast<int>(static_cast<double>(option.rect.height())/80.0*35));

    button.state |= QStyle::State_Enabled;

        if(index.data(TreeWidgetEx::nodeState).toBool())
        {
            button.icon = QIcon(QString(":/new/prefix1/icon/*.png"));
            button.iconSize = QSize(button.rect.width(),button.rect.height());
        }else{
            button.icon = QIcon(QString(":/new/prefix1/*"));
            button.iconSize = QSize(button.rect.width(),button.rect.height());
        }
    //划过按下
    if (button.rect.contains(m_mousePoint))
    {
        if (m_nType == 0)
        {
            button.state |= QStyle::State_MouseOver;
        }
        else if (m_nType == 1)
        {
            button.state |= QStyle::State_Sunken;
            button.iconSize = QSize(button.rect.width()/2,button.rect.height()/2);
        }else if (m_nType == -1) {
        }
    }
    QWidget *pWidget = m_PushButton.data();
    pWidget->style()->drawControl(QStyle::CE_PushButton, &button, painter,pWidget);
    painter->restore();//恢复painter
}
//点击事件
bool TreeWidgetEx::ItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
    Q_UNUSED(model);
    m_nType = -1;
    bool bRepaint = false;
    QMouseEvent *pEvent = static_cast<QMouseEvent *> (event);
    m_mousePoint = pEvent->pos();

    int nTop = static_cast<int>(static_cast<double>(option.rect.height())/80.0*23);
    // 还原鼠标样式
    {
        QStyleOptionButton button;

//按钮位置
button.rect = QRect(option.rect.right() - static_cast<int>(static_cast<double>(option.rect.width())/400.0*19) - static_cast<int>(static_cast<double>(option.rect.height())/80.0*35),
                            option.rect.top() + nTop,  static_cast<int>(static_cast<double>(option.rect.height())/80.0*35), static_cast<int>(static_cast<double>(option.rect.height())/80.0*35));
        // 鼠标位于按钮之上
        if (!button.rect.contains(m_mousePoint))
        {
            if(event->type() ==  QEvent::MouseButtonRelease)
                tree->setExpanded(index,!tree->isExpanded(index));
            return bRepaint;
        }
        bRepaint = true;
        switch (event->type())
        {
        // 鼠标滑过
        case QEvent::MouseMove:
        {
            // 设置鼠标样式为手型
            m_nType = 0;
            break;
        }
        // 鼠标按下
        case QEvent::MouseButtonPress:
        {
            m_nType = 1;
            break;
        }
        // 鼠标释放
        case QEvent::MouseButtonRelease:
        {
                emit pushButtonClick(index);
            break;
        }
        default:
            break;
        }
    }

    return bRepaint;
}
声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 1 1 评论 打赏
评论
2个
内容存在敏感词
手气红包
  • 一路心旅 2023-02-10 17:32:16
    回复
    楼主,请问Qt自定义树形虚线如何绘制呢?
  • Abner 2020-08-22 22:45:25
    回复

    lohail

相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
小王子🤴
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区