2010-12-05 5 views
9

He estado luchando con esto por algunas veces, y parece que no puedo encontrar la manera correcta de hacerlo.¿Cuál es la mejor manera de mostrar un icono animado en un QTableView?

Lo que me gustaría es la posibilidad de utilizar un ícono animado como decoración para algunos de mis artículos (generalmente para mostrar que se está procesando algo para este artículo en particular). Tengo un modelo de tabla personalizado, que se muestra en un QTableView.

Mi primera idea fue crear un delegado personalizado que se ocuparía de mostrar la animación. Cuando haya aprobado QMovie para la función de decoración, el delegado se conectará al QMovie para actualizar la pantalla cada vez que haya un nuevo fotograma disponible (consulte el código a continuación). Sin embargo, el pintor no parece seguir siendo válido después de la llamada al método paint del delegado (me aparece un error al llamar al método save del pintor, probablemente porque el puntero ya no apunta a la memoria válida).

Otra solución sería emitir la señal dataChanged del artículo cada vez que hay un nuevo fotograma disponible, pero 1) que provocaría muchos gastos generales innecesarios, ya que los datos no se modifican realmente; 2) no parece realmente limpio manejar la película en el nivel del modelo: debe ser la responsabilidad del nivel de visualización (QTableView o el delegado) manejar la visualización de nuevos marcos.

¿Alguien conoce una forma limpia (y preferiblemente eficiente) de mostrar la animación en las vistas Qt?


Para los interesados, aquí está el código del delegado que 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 * >(); 
} 
+1

he encontrado algo bastante similar en [ 'QxtItemDelegate'] (http://www.koders.com/cpp/fid5911A4434425C7038B6C507A5BF978C82A6294FD.aspx), una extensión a' QtItemDelegate' que permiten dibujar las barras de progreso (entre otras cosas) Para hacerlo, este delegado utiliza un enfoque bastante similar al propuesto en mi pregunta, pero almacena vistas e índices en lugar de pintores; en cada tiempo de espera de un temporizador, el delegado actualiza todas las vistas, preferiblemente solo los elementos que necesitan actualización. –

Respuesta

5

Para el registro, que terminé usando QAbstractItemView::setIndexWidget desde el interior del método de mi delegado paint, para insertar un QLabel mostrando el QMovie en el interior del elemento (véase el código de abajo).

Esta solución funciona bastante bien, y mantiene los problemas de visualización separados del modelo. Un inconveniente es que la visualización de un nuevo fotograma en la etiqueta hace que todo el elemento se vuelva a renderizar, lo que da como resultado llamadas casi continuas al método paint del delegado ...

Para reducir la sobrecarga de estas llamadas, intenté para minimizar el trabajo realizado para el manejo de películas en el delegado mediante la reutilización de la etiqueta existente, si la hay. Sin embargo, esto da como resultado un comportamiento extraño al redimensionar las ventanas: la animación se desplaza hacia la derecha, como si dos etiquetas estuvieran colocadas una al lado de la otra.

Así que bien, aquí hay una posible solución, ¡no dudes en comentar cómo mejorarla!

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

¿Puede proporcionar el código completo, cómo escribió el delegado? Publiqué una pregunta similar para 'QListView' y busco una solución. http://stackoverflow.com/questions/31812979/how-to-show-animated-icon-in-qlistview?noredirect=1#comment51562370_31812979 – zar

+0

@zadane Ya se proporcionó el código completo del delegado en mi respuesta.Solo necesita asegurarse de que su 'QListView' use este delegado al llamar a' QAbstractItemView :: setItemDelegate' o una función similar. –

+0

Creo la misma clase que tú pero no puedo instanciar el delegado 'movieDelegate = new MovieDelegate (this);' da error 'C: \ code \ svn2 \ Sync \ mainwindow.cpp: 72: error: C2664: 'MovieDelegate :: MovieDelegate (QAbstractItemView &, QObject *) ': no ​​se puede convertir el parámetro 1 de' MainWindow * const 'a' QAbstractItemView & '' Mi vista que contiene es MainWindow – zar

Cuestiones relacionadas