如何在 QTableview 单元格的值更新后及时为它的颜色设置动画?

Posted

技术标签:

【中文标题】如何在 QTableview 单元格的值更新后及时为它的颜色设置动画?【英文标题】:How to animate the color of a QTableview cell in time once it's value gets updated? 【发布时间】:2019-01-04 08:25:27 【问题描述】:

一旦通过连接的数据模型更新了 QTableview 单元格的值,我想对它的颜色(及时)进行动画处理,以吸引最终用户注意某些事情发生了变化。

这个想法是颜色在 f.i. 的渐变中发生变化。蓝色,在数值改变后从蓝色开始,大约 1 ~ 2 秒后变为白色。

我想这里必须使用 QStyledItemDelegate,因为我使用的是模型视图概念 (http://doc.qt.io/qt-5/model-view-programming.html)。

需要一个触发器来触发单元格的值更改才能开始动画,这可以通过 paint() 方法来实现,因为它是在值更改时调用的。可以从传递给paint() 的索引参数中找出行和列,以获取要为哪个单元格设置动画。

到目前为止一切顺利,您可以将该单元格的颜色设置为蓝色。 问题来了;颜色会随着时间的推移逐渐变白。所以我正在考虑 QStyledItemDelegate 类中的 QTimer 并为正在动画的单元格维护一个有点簿记(可能是用于计算蓝色渐变颜色的倒计时值的简单列表。值越低越渐变走向白色,一旦 0 结果是白色,这是单元格的默认颜色。在每个 QTimer timeout() 事件上,所有不等于 0 的值都降低 1。QStyledItemDelegate 仅连接到 QTableview 的行我想要彩色动画,即显示值项的位置。

我面临的问题是:

    paint() 是一个 const 方法,因此您不能更改任何类 参数。 如何在 QTimer 事件上重新绘制单元格颜色 (重绘整个QTableview不是神作)

我设法让它工作,但我认为这是一个肮脏的解决方案。我所做的是维护数据模型中每个单元格的颜色动画的簿记。我认为这是一个肮脏的解决方案,因为彩色动画只是一个视觉方面,所以恕我直言,它不应该驻留在数据模型中。这样,它也不是一个可移植的解决方案,即在另一个项目中,您必须进行大量返工才能使其正常工作。

我将我的应用程序剥离到核心问题上。完整的代码可以在这里找到(一个工作的应用程序):https://github.com/fruitCoder123/animated_tableview_cell

void TableViewDelegateValueWritable::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const

    // Paint background
    uint8_t red_gradient = calculate_color_gradient(RGB_RED_MAX, RGB_RED_MIN, red_gradient_step_size, m_step_value);
    uint8_t green_gradient = calculate_color_gradient(RGB_GREEN_MAX, RGB_GREEN_MIN, green_gradient_step_size, m_step_value);
    painter->fillRect(option.rect, QColor(red_gradient, green_gradient, 255));

    // Paint text
    QStyledItemDelegate::paint(painter, option, index);


uint8_t TableViewDelegateValueWritable::calculate_color_gradient(const uint8_t MAX_COLOR, const uint8_t MIN_COLOR, const uint8_t step_size, uint8_t step) const

    uint16_t color = (step_size * (1 + MAX_COLOR_GRADIENT_STEP - step)) + MIN_COLOR;

    // Handle overflow and rounding errors
    if(color > MAX_COLOR || color > (MAX_COLOR-(step_size/2)))
        color = MAX_COLOR;

    return static_cast<uint8_t>(color);


void TableViewDelegateValueWritable::gradient_timer_elapsed()

    if(m_step_value)
    
        m_step_value--;
        m_timer->start(GRADIENT_TIMEOUT_VALUE);
        //this->paint(m_painter, m_option, m_model_index);
    

我花了很长时间才找到一个好的解决方案。我一个月前开始使用 Qt,所以也许我缺乏知识。希望有人可以提示如何以一种很好的方式解决它 - 封装在视图中而不是与数据模型纠缠在一起。

【问题讨论】:

paint() 中的触发值改变是不够的,调用insert 或remove row 时可能会改变索引值中的列、行。我认为您应该首先正确处理触发器。如果您看到“更改”的数据项是您的数据的一个属性(数据模型 - 您不想搞砸),那么它应该是实现您的 paint() 函数的一种简单方法。 我完全同意你的看法。为 rowInserted()、rowDeleted() 等添加插槽并在簿记中相应地处理它不是问题。但我认为这是下一个要解决的问题。总之,谢谢你的回答! 所以基本上我的问题是,在我获得 dataChanged 触发器后,如何通过计时器事件更改单元格颜色 - 考虑到模型视图概念,这是一种“外部”...... 如果您可以为每个项目添加一个属性,例如QDateTime modifiedTime(应该通过使用 userRole 调用 setData 来为每一行添加一个属性)。绘制函数会绘制这个修改时间对应的渐变。 实际上我的应用程序中确实有日期/时间(这是一个精简版),所以这可能是一个选择。然而仍然开放的是如何通过 QTimer::timeout() 事件强制执行 paint() 操作。你知道怎么做吗? (注释掉的行不起作用:this->paint(m_painter, m_option, m_model_index) 因为我没有这些参数 m_painter、m_model 和 m_model_index。它们来自数据模型的更改。 【参考方案1】:

针对上述问题:

    paint() 被声明为 const,您可以使用 mutable 变量成员并随时修改它。例如:

    class TableViewDelegateValueWritable : public QStyledItemDelegate
    
        Q_OBJECT
        mutable QTableView * m_view; 
    
    public:
        explicit TableViewDelegateValueWritable(QTableView * view,  QObject *parent)
            m_view = view;
            //...
        
    
        void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 
    
            //...
            int nFrame m_view->getFrameOfIndex(index);
            drawFrame (painter, nFrame ); 
            m_view->setFrameOfIndex(index, ++nFrame);
            //...
        
        //class body
    
    

    重绘整个 QTableView 不是神作,但只重绘 QTableView 的视口是一个不错的选择,在 MainWindow 构造函数中:

    m_db->insert_record(QString("my_val_1"), "0");
    m_db->insert_record(QString("my_val_2"), "0");
    m_db->insert_record(QString("my_val_3"), "0");
    
    QTimer * timer = new QTimer( this );
    
    connect( timer, &QTimer::timeout, this, [this]()
        ui->tableView->viewport()->repaint();
    );
    
    timer->start( TIME_RESOLUTION ); //set to 1000ms
    

这里是一个sn-p,当一个单元格被修改时,它的颜色会每1s改变一次,使用的方法是继承QSqlTableModel来跟踪修改过的单元格(通过dataChanged信号):

   enum MyDataRole 
        ItemModifiedRole = Qt::UserRole + 1
    ;

    class mysqlTableModel : public QSqlTableModel 

        Q_OBJECT
        QMap<int, QVariant > mapTimeout;

    public:

        MySqlTableModel( QObject *parent = Q_NULLPTR, QSqlDatabase db = QSqlDatabase() )
            :QSqlTableModel( parent, db ) 
            connect( this, &QSqlTableModel::dataChanged,
                     this, [this]( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles )
            
                for(int i = topLeft.row(); i <= bottomRight.row(); i ++ )
                    mapTimeout.insert( i , QDateTime::currentDateTime() );
                
             );
        

        //this data function will be called in the delegate paint() function.
        QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE
        
            if( role != ItemModifiedRole )
                return QSqlTableModel::data( idx, role );

            QMap<int, QVariant>::const_iterator it = mapTimeout.find( idx.row() );
            return it == mapTimeout.end() ?  QVariant() : it.value();
        

        void clearEffects() 
            mapTimeout.clear();
        

    ;

还有委托paint()函数:

    // background color manipulation
    void TableViewDelegateValueWritable::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
    
        QSqlTableModel * db_model  = (QSqlTableModel *)index.model();
        QVariant v = db_model->data( index, ItemModifiedRole );

        if( !v.isNull() )

            QDateTime dt = v.toDateTime();
            int nTimePassed =  dt.secsTo( QDateTime::currentDateTime() );
            int step_value = nTimePassed + 2;

            uint8_t red_gradient = calculate_color_gradient( RGB_RED_MAX, RGB_RED_MIN, red_gradient_step_size,  step_value );
            uint8_t green_gradient = calculate_color_gradient( RGB_GREEN_MAX, RGB_GREEN_MIN, red_gradient_step_size, step_value );

            painter->fillRect( option.rect, QColor( red_gradient, green_gradient, 255) );
        

        // Paint text
        QStyledItemDelegate::paint(painter, option, index);
    

如果可能,您应该使用 QSqlRecord 方式而不是执行 sql 语句。事实上,每条sql语句执行完后,你调用select()函数,这会重置整个表模型。这是插入/更新记录的示例:

    void dbase::insert_record(const QString &signal_name, const QString &value)
    
        QSqlRecord r = db_model->record();

        r.setValue( "signal_name", signal_name );
        r.setValue( "signal_value", value );

        db_model->insertRecord(-1, r );
    

    void dbase::update_record(const QString &signal_name, const QString &new_value)
    
        for(int row = 0; row < db_model->rowCount(); row ++ )
            QSqlRecord r = db_model->record( row );

            if( r.value("signal_name").toString() == signal_name )
                r.setValue("signal_value", new_value );
                db_model->setRecord( row, r );
                break;
            
        
    

【讨论】:

以上是关于如何在 QTableview 单元格的值更新后及时为它的颜色设置动画?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 QTableView 中发出输入单元格和离开单元格的信号

将 pandas DataFrame 与 PyQt5 QTableView 同步

更改值后使用代理模型更改 QTableView 的单元格的背景颜色

高速高效地更新 QTableView

QTableView,设置单元格的字体和背景颜色

QTableview,PySide2中单元格的背景颜色