在QTableView中显示动画图标的最佳方法是什么?

时间:2010-12-05 21:36:42

标签: c++ qt qtableview qabstracttablemodel

我一直在努力解决这个问题,我似乎无法找到正确的方法。

我想要的是能够使用动画图标作为我的某些项目的装饰(通常表示此特定项目正在进行某些处理)。我有一个自定义表模型,我在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 * >();
}

5 个答案:

答案 0 :(得分:5)

为了记录,我最终在我的委托的paint方法中使用QAbstractItemView::setIndexWidget,在项目中插入显示QLabel的{​​{1}}(请参阅代码下文)。

此解决方案运行良好,并将显示问题与模型分开。一个缺点是在标签中显示新框架会导致整个项目再次渲染,导致几乎连续调用代理人的QMovie方法...

为了减少这些调用所带来的开销,我尝试通过重用现有标签来最小化代理中处理电影所做的工作(如果有的话)。但是,这会在调整窗口大小时产生奇怪的行为:动画向右移动,就像两个标签并排放置一样。

那么,这是一个可能的解决方案,随时可以评论如何改进它!

paint

答案 1 :(得分:2)

最好的解决方案是在委托中使用QSvgRenderer

enter image description here

它非常容易实现,并且与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/49015e2aac118fd97b7327a55c19f2e97f37b1c9https://github.com/KDE/ruqola/commit/2b358fb0471f795289f9dc13c256800d73accae4