12

我已经为此苦苦挣扎了一段时间,我似乎找不到正确的方法来做到这一点。

我想要的是能够使用动画图标作为我的一些项目的装饰(通常表明正在对这个特定项目进行一些处理)。我有一个自定义表格模型,我显示在QTableView.

我的第一个想法是创建一个自定义委托来处理动画的显示。当QMovie为装饰角色传递 a 时,委托将连接到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 * >();
}
4

5 回答 5

7

最好的解决方案是在委托中使用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);
}

这是一个不错的网站,您可以在其中生成自己的旋转图标并以 SVG 格式导出。

于 2019-09-29T00:52:02.650 回答
6

作为记录,我最终从我的委托方法QAbstractItemView::setIndexWidget内部使用,插入一个显示项目内部的内容(参见下面的代码)。paintQLabelQMovie

这个解决方案效果很好,并且将显示问题与模型分开。一个缺点是标签中新框架的显示会导致整个项目再次被渲染,导致几乎连续调用委托的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 * >();
}
于 2010-12-09T09:22:52.900 回答
4

在我的应用程序中,我有一个典型的旋转圆圈图标来指示表格中某些单元格的等待/处理状态。但是我最终使用了一种方法,该方法与当前接受的答案中建议的方法不同,在我看来,我的方法更简单且性能更高(更新:我在将不同的答案设置为已接受时写了这个 - 建议使用QAbstractItemView::setIndexWidget)。使用小部件似乎是一种过度杀伤力,如果它们太多会破坏性能。我的解决方案中的所有功能仅在我的模型层(的后代QAbstractItemModel)类中实现。我不需要对视图或委托进行任何更改。然而,我只为一个 GIF 制作动画,并且所有动画都是同步的。这是我的简单方法的当前限制。

用于实现此行为的模型类需要具有以下内容:

  • s的向量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 行,而无需更改视图或委托,只需更改模型即可。

于 2019-03-25T09:53:24.100 回答
2

一种解决方案是将 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();
            }
        }
    }
}
于 2019-09-14T17:32:18.240 回答
0

我编写了一个基于 QMovie 的解决方案来为 QListView/QTableView 中的单个项目设置动画,当它们可见时(用例是消息中的动画 gif,在聊天程序中)。该解决方案类似于另一个答案中的 QSvgRenderer 解决方案,但它使用 QMovie 并添加了当前可见索引的“地图”和 QMovie(每个)。请参阅提交https://github.com/KDE/ruqola/commit/49015e2aac118fd97b7327a55c19f2e97f37b1c9https://github.com/KDE/ruqola/commit/2b358fb0471f795289f9dc13c256800d73accae4

于 2020-03-01T23:57:26.700 回答