我一直在努力解决这个问题,我似乎无法找到正确的方法。
我想要的是能够使用动画图标作为我的某些项目的装饰(通常表示此特定项目正在进行某些处理)。我有一个自定义表模型,我在QTableView
显示。
我的第一个想法是创建一个自定义委托,负责显示动画。为装饰角色传递QMovie
时,代理将连接到QMovie
,以便每次有新框架时更新显示(请参阅下面的代码)。但是,在调用委托的paint
方法之后,画家似乎仍然无效(调用画家的save
方法时出错,可能是因为指针不再指向有效的内存)。
另一个解决方案是每次新帧可用时发出项目的dataChanged
信号,但1)会产生许多不必要的开销,因为数据并没有真正改变; 2)在模型级别处理电影似乎并不干净:显示层(QTableView
或委托)应负责处理新帧的显示。
有没有人知道在Qt视图中显示动画的干净(优选高效)方式?
对于那些感兴趣的人,这里是我开发的代表的代码(目前不起作用)。
// Class that paints movie frames every time they change, using the painter
// and style options provided
class MoviePainter : public QObject
{
Q_OBJECT
public: // member functions
MoviePainter( QMovie * movie,
QPainter * painter,
const QStyleOptionViewItem & option );
public slots:
void paint( ) const;
private: // member variables
QMovie * movie_;
QPainter * painter_;
QStyleOptionViewItem option_;
};
MoviePainter::MoviePainter( QMovie * movie,
QPainter * painter,
const QStyleOptionViewItem & option )
: movie_( movie ), painter_( painter ), option_( option )
{
connect( movie, SIGNAL( frameChanged( int ) ),
this, SLOT( paint( ) ) );
}
void MoviePainter::paint( ) const
{
const QPixmap & pixmap = movie_->currentPixmap();
painter_->save();
painter_->drawPixmap( option_.rect, pixmap );
painter_->restore();
}
//-------------------------------------------------
//Custom delegate for handling animated decorations.
class MovieDelegate : public QStyledItemDelegate
{
Q_OBJECT
public: // member functions
MovieDelegate( QObject * parent = 0 );
~MovieDelegate( );
void paint( QPainter * painter,
const QStyleOptionViewItem & option,
const QModelIndex & index ) const;
private: // member functions
QMovie * qVariantToPointerToQMovie( const QVariant & variant ) const;
private: // member variables
mutable std::map< QModelIndex, detail::MoviePainter * > map_;
};
MovieDelegate::MovieDelegate( QObject * parent )
: QStyledItemDelegate( parent )
{
}
MovieDelegate::~MovieDelegate( )
{
typedef std::map< QModelIndex, detail::MoviePainter * > mapType;
mapType::iterator it = map_.begin();
const mapType::iterator end = map_.end();
for ( ; it != end ; ++it )
{
delete it->second;
}
}
void MovieDelegate::paint( QPainter * painter,
const QStyleOptionViewItem & option,
const QModelIndex & index ) const
{
QStyledItemDelegate::paint( painter, option, index );
const QVariant & data = index.data( Qt::DecorationRole );
QMovie * movie = qVariantToPointerToQMovie( data );
// Search index in map
typedef std::map< QModelIndex, detail::MoviePainter * > mapType;
mapType::iterator it = map_.find( index );
// if the variant is not a movie
if ( ! movie )
{
// remove index from the map (if needed)
if ( it != map_.end() )
{
delete it->second;
map_.erase( it );
}
return;
}
// create new painter for the given index (if needed)
if ( it == map_.end() )
{
map_.insert( mapType::value_type(
index, new detail::MoviePainter( movie, painter, option ) ) );
}
}
QMovie * MovieDelegate::qVariantToPointerToQMovie( const QVariant & variant ) const
{
if ( ! variant.canConvert< QMovie * >() ) return NULL;
return variant.value< QMovie * >();
}
答案 0 :(得分:5)
为了记录,我最终在我的委托的paint
方法中使用QAbstractItemView::setIndexWidget
,在项目中插入显示QLabel
的{{1}}(请参阅代码下文)。
此解决方案运行良好,并将显示问题与模型分开。一个缺点是在标签中显示新框架会导致整个项目再次渲染,导致几乎连续调用代理人的QMovie
方法...
为了减少这些调用所带来的开销,我尝试通过重用现有标签来最小化代理中处理电影所做的工作(如果有的话)。但是,这会在调整窗口大小时产生奇怪的行为:动画向右移动,就像两个标签并排放置一样。
那么,这是一个可能的解决方案,随时可以评论如何改进它!
paint
答案 1 :(得分:2)
最好的解决方案是在委托中使用QSvgRenderer。
它非常容易实现,并且与gif不同,SVG轻巧且支持透明。
TableViewDelegate::TableViewDelegate(TableView* view, QObject* parent)
: QStyledItemDelegate(parent), m_view(view)
{
svg_renderer = new QSvgRenderer(QString{ ":/res/img/spinning_icon.svg" }, m_view);
connect(svg_renderer, &QSvgRenderer::repaintNeeded,
[this] {
m_view->viewport()->update();
});
}
void TableViewDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
QStyleOptionViewItem opt{ option };
initStyleOption(&opt, index);
if (index.column() == 0) {
if (condition)
{
// transform bounds, otherwise fills the whole cell
auto bounds = opt.rect;
bounds.setWidth(28);
bounds.moveTo(opt.rect.center().x() - bounds.width() / 2,
opt.rect.center().y() - bounds.height() / 2);
svg_renderer->render(painter, bounds);
}
}
QStyledItemDelegate::paint(painter, opt, index);
}
Here's一个不错的网站,您可以在其中生成自己的旋转图标并以SVG格式导出。
答案 2 :(得分:1)
在我的应用程序中,我有一个典型的旋转圆圈图标,用于指示表中某些单元格的等待/处理状态。但是,我最终使用了一种与当前接受的答案中所建议的方法不同的方法,在我看来,我的方法更简单,性能也更高。使用小部件似乎是一个过大的杀伤力,如果它们太多,则会破坏性能。解决方案中的所有功能仅在模型层(QAbstractItemModel
的后代)类中实现。我不需要在视图或委托中进行任何更改。但是,我只设置了一个GIF动画,并且所有动画都已同步。这是我的简单方法目前的局限性。
用于实现此行为的模型类需要具有以下内容:
QImage
的向量-我使用QImageReader
,它允许我读取所有动画帧,并将它们存储到QVector<QImage>
以动画GIF的周期滴答QTimer
-使用QImageReader::nextImageDelay()
获得时间段。
当前帧的索引(int)(我想所有动画单元格的帧都是相同的-它们是同步的;如果要不同步,则可以为每个动画单元使用整数偏移量)
有关哪些单元格应设置动画的知识以及将单元格转换为QModelIndex
的能力(取决于您的特定需求,这取决于您的自定义代码)
覆盖模型的QAbstractItemModel::data()
部分,以对任何动画单元格(Qt::DecorationRole
)响应QModelIndex
并以QImage
由QTimer::timeout
信号触发的插槽
关键部分是对计时器作出反应的插槽。它必须这样做:
1)增加当前帧,例如m_currentFrame = (m_currentFrame + 1) % m_frameImages.size();
2)获取必须设置动画的单元格的索引列表(例如QModelIndexList getAnimatedIndices();
)。 getAnimatedIndices()
的代码由您自己决定-使用蛮力查询模型中的所有单元格或进行一些巧妙的优化...
3)为每个动画单元格发出dataChanged()
信号,例如for (const QModelIndex &idx : getAnimatedIndices()) emit dataChanged(idx, idx, {Qt::DecorationRole});
仅此而已。我估计,取决于确定动画索引的函数的复杂性,整个实现可以有15到25行,而无需更改视图或委托,而只需更改模型即可。
答案 3 :(得分:1)
一种解决方案是将QMovie与GIF一起使用。 我还尝试使用SVG(它是轻量级的,并且提供了对透明度的支持),但是QMovie和QImageReader似乎都不支持动画SVG。
Model::Model(QObject* parent) : QFileSystemModel(parent)
{
movie = new QMovie{ ":/resources/img/loading.gif" };
movie->setCacheMode(QMovie::CacheAll);
movie->start();
connect(movie, &QMovie::frameChanged,
[this] {
dataChanged(index(0, 0), index(rowCount(), 0),
QVector<int>{QFileSystemModel::FileIconRole});
});
}
QVariant Model::data(const QModelIndex& index, int role) const
{
case QFileSystemModel::FileIconRole:
{
if (index.column() == 0) {
auto const path = QString{ index.data(QFileSystemModel::FilePathRole).toString() };
if (path.isBeingLoaded()){
return movie->currentImage();
}
}
}
}
答案 4 :(得分:0)
我编写了一个基于QMovie的解决方案,以对QListView / QTableView中的单个项目进行动画处理(用例是聊天程序中消息中的gif动画)。该解决方案在另一个答案中类似于QSvgRenderer解决方案,但它使用QMovie,并且使用QMovie(每个)添加了当前可见索引的“映射”。参见提交https://github.com/KDE/ruqola/commit/49015e2aac118fd97b7327a55c19f2e97f37b1c9和https://github.com/KDE/ruqola/commit/2b358fb0471f795289f9dc13c256800d73accae4。