Как лучше всего отобразить анимированный значок в QTableView? - PullRequest
9 голосов
/ 06 декабря 2010

Я боролся с этим уже несколько раз, и я не могу найти правильный способ сделать это.

Что мне хотелось бы, так это возможность использовать анимированный значок в качествеукрашение для некоторых из моих предметов (как правило, чтобы показать, что для этого конкретного предмета происходит некоторая обработка).У меня есть пользовательская модель таблицы, которую я отображаю в 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 * >();
}

Ответы [ 2 ]

5 голосов
/ 09 декабря 2010

Для записи я использовал 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 * >();
}
1 голос
/ 25 марта 2019

В моем приложении у меня есть типичный значок вращающегося круга, чтобы указать состояние ожидания / обработки для некоторых ячеек в таблице. Однако я в конечном итоге использовал подход, который отличается от предложенного в принятом в настоящее время ответе, мой, на мой взгляд, проще и несколько более эффективен. Использование виджетов кажется излишним, что может привести к снижению производительности, если их слишком много. Все функциональные возможности в моем решении реализованы только в моем слое модели (потомок QAbstractItemModel) класса. Мне не нужно вносить никаких изменений в мнение ни делегата. Однако я только анимирую один GIF и все анимации синхронизированы. Это текущее ограничение моего простого подхода.

Класс модели, который используется для реализации этого поведения, должен иметь следующее:

  • вектор QImage с - я использую QImageReader, что позволяет мне читать все кадры анимации, я сохраняю их в QVector<QImage>

  • a QTimer с периодичностью анимированного GIF - период времени получается с использованием 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 строк, без необходимости изменять представление или делегировать, только модель.

...