我一直在努力解决这个问题,而且我似乎无法找到正确的方法来做到这一点。
我想要的是能够使用动画图标作为我的一些项目的装饰(通常表明该特定项目正在进行一些处理)。我有一个自定义表模型,我在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 * >();
}
为了记录,我最终在我的代表的QAbstractItemView::setIndexWidget
方法中使用paint
,在项目中插入显示QLabel
的QMovie
(参见下面的代码)。
此解决方案非常好用,并将显示问题与模型分开。一个缺点是标签中新框架的显示导致整个项目再次渲染,导致几乎连续调用委托的paint
方法......
为了减少这些调用所带来的开销,我尝试通过重用现有标签来最小化在委托中处理电影所做的工作(如果有的话)。但是,这会在调整窗口大小时导致奇怪的行为:动画向右移动,就像两个标签并排放置一样。
好吧,这是一个可能的解决方案,随时评论如何改进它!
// Declaration
#ifndef MOVIEDELEGATE_HPP
#define MOVIEDELEGATE_HPP
#include <QtCore/QModelIndex>
#include <QtGui/QStyledItemDelegate>
class QAbstractItemView;
class QMovie;
class MovieDelegate : public QStyledItemDelegate
{
Q_OBJECT
public: // member functions
MovieDelegate( QAbstractItemView & view, QObject * parent = NULL );
void paint( QPainter * painter,
const QStyleOptionViewItem & option,
const QModelIndex & index ) const;
private: // member functions
QMovie * qVariantToPointerToQMovie( const QVariant & variant ) const;
private: // member variables
mutable QAbstractItemView & view_;
};
#endif // MOVIEDELEGATE_HPP
// Definition
#include "movieDelegate.hpp"
#include <QtCore/QVariant>
#include <QtGui/QAbstractItemView>
#include <QtGui/QLabel>
#include <QtGui/QMovie>
Q_DECLARE_METATYPE( QMovie * )
//---------------------------------------------------------
// Public member functions
//---------------------------------------------------------
MovieDelegate::MovieDelegate( QAbstractItemView & view, QObject * parent )
: QStyledItemDelegate( parent ), view_( view )
{
}
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 );
if ( ! movie )
{
view_.setIndexWidget( index, NULL );
}
else
{
QObject * indexWidget = view_.indexWidget( index );
QLabel * movieLabel = qobject_cast< QLabel * >( indexWidget );
if ( movieLabel )
{
// Reuse existing label
if ( movieLabel->movie() != movie )
{
movieLabel->setMovie( movie );
}
}
else
{
// Create new label;
movieLabel = new QLabel;
movieLabel->setMovie( movie );
view_.setIndexWidget( index, movieLabel );
}
}
}
//---------------------------------------------------------
// Private member functions
//---------------------------------------------------------
QMovie * MovieDelegate::qVariantToPointerToQMovie( const QVariant & variant ) const
{
if ( ! variant.canConvert< QMovie * >() ) return NULL;
return variant.value< QMovie * >();
}
在我的应用程序中,我有一个典型的旋转圆圈图标,表示表格中某些单元格的等待/处理状态。然而,我最终使用的方法与目前接受的答案中提出的方法不同,我认为我的方法更简单,更有效。使用小部件似乎是一种过度杀伤,如果有太多的小部件会破坏性能。我的解决方案中的所有功能仅在我的模型层(QAbstractItemModel
的后代)类中实现。我不需要在视图和委托中进行任何更改。然而,我只是动画一个GIF,所有动画都是同步的。这是我简单方法的当前限制。
用于实现此行为的模型类需要具有以下内容:
QImage
s的矢量 - 我使用QImageReader
,它允许我读取所有动画帧,我将它们存储到QVector<QImage>
QTimer
与动画GIF的周期性勾选 - 时间段是使用QImageReader::nextImageDelay()
获得的。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行,而不需要改变视图或委托,只需要模型。