如何在 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 同步