QStyledItemDelegate自定义委托中实现文字滚动效果

一、QStyledItemDelegate

前言
阅读本篇博客需要对Qt的MVC模型视图委托的架构有一定的了解,如果不了解的可以自行百度或者参考Qt助手官方文档的介绍

1.官方描述

原文:
The QStyledItemDelegate class provides display and editing facilities for data items from a model.
When displaying data from models in Qt item views, e.g., a QTableView, the individual items are drawn by a delegate. Also, when an item is edited, it provides an editor widget, which is placed on top of the item view while editing takes place. QStyledItemDelegate is the default delegate for all Qt item views, and is installed upon them when they are created.
The QStyledItemDelegate class is one of the Model/View Classes and is part of Qt’s model/view framework. The delegate allows the display and editing of items to be developed independently from the model and view.
The data of items in models are assigned an ItemDataRole; each item can store a QVariant for each role. QStyledItemDelegate implements display and editing for the most common datatypes expected by users, including booleans, integers, and strings.
The data will be drawn differently depending on which role they have in the model. The following table describes the roles and the data types the delegate can handle for each of them. It is often sufficient to ensure that the model returns appropriate data for each of the roles to determine the appearance of items in views.


翻译如下:
QStyledItemDelegate类为模型中的数据项提供显示和编辑功能。
当在Qt项目视图(例如QTableView)中显示来自模型的数据时,各个项目由代表绘制。此外,当编辑一个项目时,它会提供一个编辑器小部件,在进行编辑时,该小部件会放置在项目视图的顶部。QStyledItemDelegate是所有Qt项目视图的默认委托,并在创建时安装在它们上。
QStyledItemDelegate类是模型/视图类之一,是Qt模型/视图框架的一部分。代理允许独立于模型和视图开发项目的显示和编辑。
模型中项目的数据被分配一个ItemDataRole;每个项目可以存储每个角色的QVariant。QStyledItemDelegate实现了用户期望的最常见数据类型的显示和编辑,包括布尔值、整数和字符串。
根据他们在模型中的角色,数据的绘制方式会有所不同。下表描述了代理可以为每个角色处理的角色和数据类型。确保模型为每个角色返回适当的数据以确定视图中项目的外观通常就足够了。


二、关键代码

1.初始化model

void DefectDlg::initcodemodel()
{
    mStandardmodel = new QStandardItemModel();
    mDefectdelegate = new DefectDelegate(this);
    ui->tableView->setItemDelegate(mDefectdelegate);       //为视图设置委托
    mSortFiltermodel = new QSortFilterProxyModel(ui->tableView);
    mSortFiltermodel->setSourceModel(mStandardmodel);
    mSortFiltermodel->setFilterRole(Qt::UserRole+1);
    mSortFiltermodel->setDynamicSortFilter(false);
    ui->tableView->setModel(mSortFiltermodel);

    ui->tableView->setDragEnabled(false);            //控件不允许拖动
    ui->tableView->show();
    ui->tableView->setShowGrid(false);
    ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);//ResizeToContents自适应宽度,Stretch各列平均分配列宽
    ui->tableView->verticalHeader()->setDefaultSectionSize(80);//设置默认行高
    ui->tableView->horizontalHeader()->resizeSection(0,50); //设置表头第一列的宽度为150
    ui->tableView->horizontalHeader()->setFixedHeight(50); //设置列表头的高度
    ui->tableView->verticalHeader()->setFixedWidth(100); //设置行表头的宽度
    ui->tableView->verticalHeader()->setVisible(false);
    ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);//禁止编辑
    ui->tableView->setSelectionBehavior(QAbstractItemView::SelectItems); //设置选择行为时每次选择一行
    ui->tableView->horizontalHeader()->setVisible(false);
    ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection); //可多选(Ctrl、Shift、 Ctrl+A都可以)


//        view->horizontalHeader()->setStretchLastSection(true); //设置充满表宽度
    ui->tableView->setFrameShape(QFrame::NoFrame); //设置无边框
    ui->tableView->setAutoFillBackground(false);
    connect(ui->tableView,&QTableView::clicked,this,&DefectDlg::slot_showLevelgrade);

# if 0
    /// mouse clicked method
    QObject::connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
                     [&](const QItemSelection &selected, const QItemSelection & deselected){
        for(const QModelIndex & index: selected.indexes()){
            if(!index.isValid()){qDebug()<<__FUNCTION__<<__LINE__<<"index is not valid";return;}
            QModelIndex sIndex = mSortFiltermodel->mapToSource(index);
            QVariant var = mStandardmodel->data(sIndex,Qt::UserRole);
            DEFECTINFO defdata = var.value<DEFECTINFO>();
            if(defdata.def_dsc.length()==0) continue;
            mStandardmodel->setData(sIndex, true, Qt::UserRole+3);
            new CustomAnimation(mStandardmodel, QPersistentModelIndex(sIndex),defdata.def_dsc.length());
        }

        for(const QModelIndex & index: deselected.indexes()){
            if(!index.isValid()){qDebug()<<__FUNCTION__<<__LINE__<<"index is not valid";return;}
            QModelIndex sIndex = mSortFiltermodel->mapToSource(index);
            mStandardmodel->setData(sIndex, false, Qt::UserRole+3);
        }
    });
#else
    /// mouse hoverd method
    QObject::connect(ui->tableView,&QTableView::entered,this,[&](const QModelIndex &index){
        if(previousIndex.isValid()){
            mStandardmodel->setData(previousIndex, false, Qt::UserRole+3);
        }
        QModelIndex nIndex = mSortFiltermodel->mapToSource(index);
        DEFECTINFO defdata = mStandardmodel->data(nIndex,Qt::UserRole).value<DEFECTINFO>();
        if(defdata.def_dsc.length()==0) return;
        mStandardmodel->setData(nIndex, true, Qt::UserRole+3);
        new CustomAnimation(mStandardmodel, QPersistentModelIndex(nIndex),defdata.def_dsc.length());
        previousIndex = nIndex;
    });
#endif
}

说明
mStandardmodel 作为数据源模型,
mSortFiltermodel 是用于分类过滤筛选的,
tableview用于数据的展示,
mDefectdelegate是子类化QStyledItemDelegate的,用于模型项的最终显示,
最后那个信号槽绑定,是视图的模型选择项变化时,即选中item,会触发,给每个选中项设置一个动画,参数是把model指针和选中项的索引以及需要滚动文字的总长度传进去,关于CustomAnimation类,下面会讲到

这里讲一下,由于设置了过滤模型,因此选择的索引并不是源模型的索引,需要通过mapToSource将当前模型项的索引转化为源模型的索引,因为在CustomAnimation类内部需要对源模型数据角色进行修改

上述代码效果为鼠标在item上悬停滚动,如果要改为选中滚动,只需要要上述的#if 0改为#if 1即可

2.绘制

defectdelegate.h

#ifndef DEFECTDELEGATE_H
#define DEFECTDELEGATE_H

#include <QObject>
#include <QStyledItemDelegate>
#include <QPainter>
#include <QStyle>
#include <QEvent>
#include <QDebug>
#include "globaldata.h"


class DefectDelegate : public QStyledItemDelegate
{
    Q_OBJECT
signals:

public:
    explicit DefectDelegate(QObject *parent = 0);
    ~DefectDelegate();

    //重写重画函数
    void paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const;
    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;

private:
    const qreal radius = 10;
};


#endif // DEFECTDELEGATE_H

重写paint函数来实现绘制,sizeHint函数提供项的大小,由于我这边不需要提供编辑,因此没有重写editor那些虚函数,有需要的话可以自己去实现

defectdelegate.cpp

#include "defectdelegate.h"

DefectDelegate::DefectDelegate(QObject *parent) :
    QStyledItemDelegate(parent)
{

}

DefectDelegate::~DefectDelegate()
{

}

void DefectDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if(index.isValid())
    {
        painter->save();
        QVariant var = index.data(Qt::UserRole);
        DEFECTINFO defdata = var.value<DEFECTINFO>();

//        QStyleOptionViewItem viewOption(option);//用来在视图中画一个item
        painter->setRenderHints(QPainter::Antialiasing,true);

        QRectF rect;
        rect.setX(option.rect.x()+2);
        rect.setY(option.rect.y()+2);
        rect.setWidth( option.rect.width()-10);
        rect.setHeight(option.rect.height()-10);

        QPainterPath path;
        path.moveTo(rect.topRight() - QPointF(radius, 0));
        path.lineTo(rect.topLeft() + QPointF(radius, 0));
        path.quadTo(rect.topLeft(), rect.topLeft() + QPointF(0, radius));
        path.lineTo(rect.bottomLeft() + QPointF(0, -radius));
        path.quadTo(rect.bottomLeft(), rect.bottomLeft() + QPointF(radius, 0));
        path.lineTo(rect.bottomRight() - QPointF(radius, 0));
        path.quadTo(rect.bottomRight(), rect.bottomRight() + QPointF(0, -radius));
        path.lineTo(rect.topRight() + QPointF(0, radius));
        path.quadTo(rect.topRight(), rect.topRight() + QPointF(-radius, -0));


        if(option.state &QStyle::State_MouseOver)
        {
            painter->setPen(QPen(QColor(0,255,127)));
            painter->setBrush(QBrush(QColor(0,255,127)));
            painter->drawPath(path);
        }
        else if(option.state & QStyle::State_Selected)
        {
            painter->setPen(QPen(Qt::gray));
            painter->setBrush(QBrush(QColor(0,255,0)));
            painter->drawPath(path);
        }
        else{
#if 0
            painter->setPen(QPen(QColor(Qt::blue)));
            painter->setBrush(QBrush(QColor(200,200,200)));
            painter->drawPath(path);
#else
            QLinearGradient liner(rect.topLeft(),rect.bottomRight());
            liner.setColorAt(0,Qt::blue);
            liner.setColorAt(0.1,Qt::white);
            liner.setColorAt(0.9,QColor(230,230,230));
            liner.setColorAt(1.0,Qt::blue);
            painter->setBrush(QBrush(liner));
            painter->drawPath(path);
#endif
        }

        QRect codeRect = QRect(rect.left() +8, rect.top()+5, rect.width()-10, 20);
        painter->setPen(QPen(QColor(0,47,167)));
        painter->setFont(QFont("Microsoft YaHei UI", 15, QFont::Bold));
        painter->drawText(codeRect,Qt::AlignHCenter|Qt::AlignVCenter,defdata.def_code);

        QRect nameRect = QRect(rect.left() +1, rect.top()+30, rect.width(), 20);
        painter->setPen(QPen(Qt::red));
        painter->setFont(QFont("Microsoft YaHei UI", 12, QFont::Bold));
        painter->drawText(nameRect,Qt::AlignHCenter|Qt::AlignVCenter,defdata.def_name);

        int m_curIndex = index.data(Qt::UserRole+2).toInt();
        QString m_showText = defdata.def_dsc;
        QRect dscRect = QRect(rect.left() +8, rect.top()+50, rect.width()-10, 15);
        painter->setPen(QPen(QColor(0,47,167)));
        painter->setFont(QFont("Microsoft YaHei UI", 9, QFont::Bold));
        if(m_curIndex >0 && m_curIndex<m_showText.length()){
            painter->drawText(dscRect,Qt::AlignLeft,m_showText.mid(m_curIndex));
        }else{
            painter->drawText(dscRect,Qt::AlignLeft,m_showText);
        }

        painter->restore();
    }
}

QSize DefectDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    Q_UNUSED(option)
    Q_UNUSED(index)
    return QSize(150, 80);
}

说明

paint函数中,重新计算了每个item项的边界矩形范围,并且在鼠标悬浮状态和选中状态有不同的样式去绘制,其他情况下是一个区域背景渐变,当然这些效果都可以根据自己的需求去实现,这里我就不多说了。

重点就是下面的动画类,首先说下模型的自定义角色role,userrole用于分类过滤,UserRole+1则是自定义的数据结构,UserRole+2则是动画属性,在paint函数中每次的UserRole+2的data是不同的,我这边是从0到需要滚动文字的总长度,随时间变化,有点跟QTimeLine差不多。

每次加1,则模型中该角色的数据会改变,会触发重绘进入paint函数,对显示的字符串做裁剪操作,m_curIndex 就是对应位置,每次得到的字符串从头部少一个字符长度,然后交给Qpinter去重新绘制文本drawText,从效果来看就是文字滚动显示的

3.自定义动画类

自定义动画类CustomAnimation

class CustomAnimation: public QVariantAnimation{
    Q_OBJECT
    QPersistentModelIndex m_index;
    QAbstractItemModel *m_model;
public:
    CustomAnimation(QAbstractItemModel *m_model, QPersistentModelIndex index,quint8 length, QObject *parent=nullptr)
        : QVariantAnimation(parent),
          m_index(index),
          m_model(m_model)
    {
        setStartValue(0);
        setEndValue(length);
        setDuration(computeDuration(length));
        connect(this, &CustomAnimation::valueChanged, this, &CustomAnimation::on_valueChanged);
        // delete animation
        start(QAbstractAnimation::DeleteWhenStopped);
    }
private:
    //the Duration need to recompute by text lengths;
    int computeDuration(quint8 len){
        int t = len / 5 * 1000; // 5f/s
        qDebug()<<__FUNCTION__<<__LINE__<< "len is" <<len <<"time is" <<t;
        return t;
    }
private:
    //if value changed, reset model data of userrole+2
    Q_SLOT void on_valueChanged(const QVariant & value){
        if(m_model){
            if(m_model->data(m_index,Qt::UserRole+3).toBool()){
                m_model->setData(m_index, value, Qt::UserRole+2);
            }else{
                stop();
                m_model->setData(m_index, -1, Qt::UserRole+2);
            }
        }else{
            stop();
        }
    }
};


三、最终效果

1.鼠标悬停时滚动

在这里插入图片描述


2.选中时滚动

在这里插入图片描述


最后

码字不易,如需转载,请注明出处

如有其他的实现方式,也可以一起探讨


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值