QTableview单元格的值更新后,如何及时对它的颜色进行动画处理?



我想在QTableview单元格的值通过连接的数据模型更新后,立即为其颜色设置动画,以吸引最终用户注意到某些内容发生了变化。

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

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

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

到目前为止,你可以将单元格的颜色设置为蓝色。问题来了;颜色将随着时间的推移逐渐变白。因此,我在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,所以可能我缺乏知识。希望有人能提示如何以一种好的方式解决它——封装在视图中,而不是与数据模型纠缠在一起。

对于所述问题:

  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不是神的风格,但在MainWindow构造函数中,只重新绘制QTableView的视口是一个很好的选择:

    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
    

以下是一个片段,用于在修改单元格时对其进行动画处理,颜色将每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;
}
}
}

最新更新