QTableview单元格的值更新后,如何及时对它的颜色进行动画处理?
How to animate the color of a QTableview cell in time once it's value gets updated?
我想在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中我要用颜色设置动画的行,即显示值项的行。
我面临的问题是:
- 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()被声明为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不是神的风格,但在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;
}
}
}
- 在执行其他功能的同时播放动画(LED矩阵和Arduino/ESP8266)
- 将"打开的CV图像"中的"颜色"转换为整数格式
- 如何在内核C++中使用1920x1080x16M图形或类似的16M颜色?(VGA)
- 如何在24位SDL_Surface上设置像素的颜色
- 如何从SDL_Surface获取特定像素的颜色
- 使用 GLUT 使用键停止动画?
- 使用对象数组对 SFML 进行动画处理
- (SFML)按下键时,播放器构造函数未使用正确的动画进行更新
- 列表视图更改选择颜色
- GtkTreeView 交替行颜色
- dx11 渲染到纹理仅显示透明颜色
- 如何减慢从 BVH 文件读取的 opengl 动画?
- 使用 OpenGL 4.5 更改所选顶点的颜色
- 为什么我的 LEGACY OPENGL 颜色反转了?
- 双击更改 mfc 中列表控件中的行的颜色
- 如何检测是否在缓冲绘画动画中绘制最后一帧?
- 使用动画更改Qwidget的边框颜色
- QTableview单元格的值更新后,如何及时对它的颜色进行动画处理?
- 如何在 qt 中对 QPushButton 的背景颜色进行动画处理
- 单击按钮后对QML矩形的颜色设置动画