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

问题描述 投票:9回答:2

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

我想要的是能够使用动画图标作为我的一些项目的装饰(通常表明该特定项目正在进行一些处理)。我有一个自定义表模型,我在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 * >();
}
c++ qt qtableview qabstracttablemodel
2个回答
5
投票

为了记录,我最终在我的代表的QAbstractItemView::setIndexWidget方法中使用paint,在项目中插入显示QLabelQMovie(参见下面的代码)。

此解决方案非常好用,并将显示问题与模型分开。一个缺点是标签中新框架的显示导致整个项目再次渲染,导致几乎连续调用委托的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 * >();
}

1
投票

在我的应用程序中,我有一个典型的旋转圆圈图标,表示表格中某些单元格的等待/处理状态。然而,我最终使用的方法与目前接受的答案中提出的方法不同,我认为我的方法更简单,更有效。使用小部件似乎是一种过度杀伤,如果有太多的小部件会破坏性能。我的解决方案中的所有功能仅在我的模型层(QAbstractItemModel的后代)类中实现。我不需要在视图和委托中进行任何更改。然而,我只是动画一个GIF,所有动画都是同步的。这是我简单方法的当前限制。

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

  • QImages的矢量 - 我使用QImageReader,它允许我读取所有动画帧,我将它们存储到QVector<QImage>
  • 一个QTimer与动画GIF的周期性勾选 - 时间段是使用QImageReader::nextImageDelay()获得的。
  • 当前帧的索引(int)(我认为所有动画单元的帧都是相同的 - 它们是同步的;如果你想要不同步,那么你可以为每个帧使用整数偏移量)
  • 了解哪些单元格应该是动画的,以及将单元格转换为QModelIndex的能力(这取决于您的自定义代码来实现这一点,取决于您的特定需求)
  • 覆盖模型的QAbstractItemModel::data()部分以响应任何动画单元格的Qt::DecorationRoleQModelIndex)并将当前帧作为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行,而不需要改变视图或委托,只需要模型。

© www.soinside.com 2019 - 2024. All rights reserved.