Pregunta

He estado luchando con esto por algunas veces, y me parece que no puede encontrar la manera correcta de hacerlo.

Lo que me gustaría es la capacidad de utilizar un icono animado como una decoración para algunos de mis artículos (por lo general para mostrar que algún tipo de procesamiento se está produciendo en este caso particular). Tengo un modelo de tabla personalizada, que puedo mostrar en un QTableView.

Mi primera idea era crear un delegado a medida que se haría cargo de visualización de la animación. Cuando se transmite un QMovie para el papel de la decoración, el delegado se conectaría a la QMovie con el fin de actualizar la pantalla cada vez que un nuevo marco está disponible (ver código de abajo). Sin embargo, el pintor no parece seguir siendo válida después de la llamada al método paint del delegado (me da un error al llamar método save del pintor, probablemente debido a que el puntero apunta ya no a memoria válida).

Otra solución sería para emitir la señal de dataChanged del tema cada vez que un nuevo marco está disponible, pero 1) que induciría muchos sobrecarga innecesaria, ya que los datos no se ha cambiado realmente; 2) no parece muy limpia para manejar la película en el nivel del modelo: debe ser responsabilidad de la capa de presentación (QTableView o el delegado) para controlar la presentación de nuevos marcos

.

¿Alguien sabe de una manera limpia (y preferiblemente eficiente) a la animación en pantalla vistas Qt?


Para los interesados, aquí está el código del delegado desarrollé (que no funciona en este momento).

// 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 * >();
}
¿Fue útil?

Solución

La mejor solución es utilizar QSvgRenderer dentro delegado.

introducir descripción de la imagen aquí

Es muy fácil de implementar y, a diferencia del GIF, SVG es ligero y soportes transparencia.

    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);
}

Aquí es un buen sitio web donde se puede generar su propio icono hilado y de exportación en SVG.

Otros consejos

Para el registro, que terminé usando QAbstractItemView::setIndexWidget de dentro del método paint de mi delegado, para insertar un QLabel mostrando el QMovie dentro del artículo (ver código de abajo).

Esta solución funciona bastante bien, y mantener los problemas de visualización separados del modelo. Un inconveniente es que la presentación de un nuevo marco en la etiqueta hace que todo el elemento que pasarán a ser de nuevo, dando lugar a las llamadas casi continuas a paint método del delegado ...

Para reducir la sobrecarga inccured por estas llamadas, me trató de minimizar el trabajo realizado para la manipulación de las películas en el delegado mediante la reutilización de la etiqueta existente, si es que existe. Sin embargo, esto resulta en un comportamiento extraño cuando cambiar el tamaño de las ventanas:. La animación se desplaza hacia la derecha, como si dos etiquetas se colocan al lado del otro

Así que bueno, aquí es una posible solución, no dude en comentar sobre las formas de mejorarlo!

// 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 * >();
}

En mi solicitud, que tiene un icono típico círculo giratorio para indicar una espera / procesamiento de estado para algunas de las celdas de una tabla. Sin embargo Terminé usando un enfoque, que es diferente de la que se sugiere en la respuesta aceptada actualmente, la mía es mi punto de vista más simple y algo más performante. El uso de widgets parece como una exageración que destruirá el rendimiento si hay demasiados de ellos. Toda la funcionalidad de mi solución sólo se implementa en mi capa del modelo (descendiente de QAbstractItemModel) clase. No necesito hacer ningún cambio en la vista ni el delegado. Estoy sin embargo, sólo la animación de un GIF y todas las animaciones están sincronizados. Esta es la limitación de la corriente de mi enfoque simple.

La clase de modelo que se utiliza para poner en práctica este necesidades de comportamiento que tienen los siguientes:

  • el vector de QImages - Yo uso QImageReader, que me permite leer todos los cuadros de animación, que los guarde en un QVector<QImage>

  • a QTimer marcando con la periodicidad de la GIF animados -. Se obtiene el período de tiempo usando QImageReader::nextImageDelay()

  • el índice (int) del marco actual (supongo que la trama es la misma para todas las celdas de animación - que son sincronizados, si quieres no sincronizados, puede utilizar un desplazamiento para cada uno de ellos entero)

  • algún conocimiento de que las células deben ser animados y capacidad de traducir la célula para QModelIndex (esto es hasta el código personalizado para implementar esto, dependerá de sus necesidades específicas)

  • anulación QAbstractItemModel::data() parte de su modelo de responder a Qt::DecorationRole para cualquier celda de animación (QModelIndex) y devolver el cuadro actual como QImage

  • una ranura que se activa por la señal de QTimer::timeout

La parte clave es la ranura que reacciona al temporizador. Se debe hacer esto:

1) Aumentar la trama actual, por ejemplo, m_currentFrame = (m_currentFrame + 1) % m_frameImages.size();

2) Obtener la lista de índices (por ejemplo QModelIndexList getAnimatedIndices();) de las células que tienen que ser animada. Este código de getAnimatedIndices() depende de usted para desarrollar - el uso de la fuerza bruta consultar todas las células de su modelo o alguna optimización inteligente ...

3) señal dataChanged() Emit para cada celda de animación, por ejemplo, for (const QModelIndex &idx : getAnimatedIndices()) emit dataChanged(idx, idx, {Qt::DecorationRole});

Eso es todo. Calculo que en función de la complejidad de sus funciones para determinar qué índices están animados, toda la aplicación puede tener algo como 15 a 25 líneas, sin necesidad de alterar la vista ni delegado, sólo el modelo.

Una solución es utilizar QMovie con el GIF. También probé usando SVG (que es ligero y ofrece soporte para transparencia), pero no parecen tanto QMovie y QImageReader al apoyo de animación 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();
            }
        }
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top