0

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

这个想法是颜色在 fi blue 的渐变中发生变化,在值更改后立即从蓝色开始,并在大约 1 ~ 2 秒内逐渐变为白色。

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

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

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

我面临的问题是:

  1. paint() 是一个 const 方法,因此您不能更改任何类参数。
  2. 如何在 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,所以也许我缺乏知识。希望有人可以提示如何以一种很好的方式解决它 - 封装在视图中而不是与数据模型纠缠在一起。

4

1 回答 1

0

对于所述问题:

  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
    }
    
  2. 重绘整个 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
    

这是一个在修改单元格时为单元格设置动画的片段,颜色将每 1 秒更改一次,使用的方法是继承 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;
            }
        }
    }
于 2019-01-07T15:18:16.127 回答