Traduction par Jean-luc Biord, du Site de la communauté Qt francophone.
English TOC.

12. Le Canevas

Le module de canevas, par exemple QCanvas, QCanvasView et QCanvasItem et les classes héritées, sont utilisées pour afficher les graphiques 2D. Les graphiques peuvent aller du classique jeu en 2D au graphique commercial. La documentation officielle dit ceci :

"Le canevas est optimisé pour un grand nombre d'éléments, particulièrement lorsqu'un petit pourcentage des éléments change en une seule fois. Si l'affichage entier change vraiment régulièrement, vous pouvez songer à utiliser votre propre classe héritée de QScrollView."

Les classes du module canevas construisent un modèle classique d'affichage, le modèle - Vue. La classe QCanvas est un document accueillant un nombre de QCanvasItems. Chaque canevas peut-être vu avec un ou plusieurs QCanvasViews. Cela signifie que le même canevas peut être affiché avec des vues vraiment indépendantes avec différents niveaux de zoom, rotations, etc.

Les formes de base

Qt offre une panoplie d'éléments de canevas. Une démonstration de plusieurs de ces éléments est faite dans l'exemple 12-1 montré ci-dessous. Coller simplement le code dans un fichier.cpp dans un répertoire vide et lancez qmake -project && qmake && make pour générer un exécutable. La fenêtre du résultat est montrée schéma 12-1.

#include <qapplication.h>
#include <qcanvas.h>

int main( int argc, char **argv )
{
  QApplication a( argc, argv );
  
  QCanvas *c = new QCanvas( 210, 75 );
  QCanvasView *cv = new QCanvasView( c );

  QCanvasRectangle *rect = new QCanvasRectangle( 10, 10, 40, 40, c );
  rect->setPen( Qt::black );
  rect->setBrush( Qt::red );
  rect->show();

  QCanvasText *t = new QCanvasText( "Rect", c );
  t->setX( 30 );
  t->setY( 55 );
  t->setTextFlags( Qt::AlignHCenter );
  t->show();
  
  QPointArray points( 3 );
  points.setPoint( 0, 20, 0 );
  points.setPoint( 1, 0, 40 );
  points.setPoint( 2, 40, 40 );
  
  QCanvasPolygon *poly = new QCanvasPolygon( c );
  poly->setPoints( points );
  poly->setX( 60 );
  poly->setY( 10 );
  poly->setBrush( Qt::blue );
  poly->show();
  
  t = new QCanvasText( "Poly", c );
  t->setX( 80 );
  t->setY( 55 );
  t->setTextFlags( Qt::AlignHCenter );
  t->show();
  
  QCanvasLine *line = new QCanvasLine( c );
  line->setPoints( 110, 10, 150, 50 );
  line->setPen( QPen( Qt::green, 4 ) );
  line->show();
  
  t = new QCanvasText( "Line", c );
  t->setX( 130 );
  t->setY( 55 );
  t->setTextFlags( Qt::AlignHCenter );
  t->show();
  
  QCanvasEllipse *elli = new QCanvasEllipse( 40, 40, 45*16, 225*16, c );
  elli->setX( 180 );
  elli->setY( 30 );
  elli->setBrush( Qt::cyan );
  elli->show();
  
  t = new QCanvasText( "Elli", c );
  t->setX( 180 );
  t->setY( 55 );
  t->setTextFlags( Qt::AlignHCenter );
  t->show();
  
  c->update();

  a.setMainWidget( cv );
  cv->show();
  
  return a.exec();
}

Exemple 12-1

Example 12-1 running

Schéma 12-1 L'exemple 12-1 en exécution.

Les classes utilisées dans cet exemple sont  QCanvasText, QCanvasRectangle, QCanvasPolygon, QCanvasLine et QCanvasEllipse. Les quatre dernières sont héritées de QCanvasPolygonalItem qui est la classe d'élément de canevas la meilleure pour créer de nombreux éléments personnalisés.

En manipulant le polygone, notez que la forme obtenue doit être positionné en utilisant les méthodes setX et setY. Ces méthodes transposent les points, ainsi le point (20, 0) devient (20+60, 0+10) = (80, 10).

En créant l'ellipse les angles sont spécifiés en 16ème de degrés, d'où le ...*16 dans le constructeur. Les coordonnées indiquent le centre de l'ellipse.

Avant de continuer avec l'exemple suivant et de créer un élément personnalisé, une rapide consultation du tableau 12-1 est recommandée. L'élément polygone introduit les propriétés stylo (pen) et brosse (brush). Elles ne sont pas utilisées par toutes les classes héritées. C'est résumé dans le tableau ci-dessous, mais c'est également exposé dans la description détaillée de chaque élément dans la documentation officielle.

Classe Utilise Brush Utilise Pen
QCanvasRectangle Oui
Oui
QCanvasPolygon Oui Non
QCanvasLine Non Oui
QCanvasEllipse Oui Non

Tableau 12-1

Une Forme Personnalisée

Le deuxième exemple introduit un élément personnalisé de canevas, MyCanvasItem. L'exemple 12-2 montre la classe implémentée MyCanvasItem et l'exemple 12-3 montre les changements dans la fonction main depuis l'exemple précédent.

class MyCanvasItem : public QCanvasPolygonalItem
{
public:
  MyCanvasItem( int width, int height, QCanvas *c ) : QCanvasPolygonalItem( c )
  {
    m_width = width;
    m_height = height;
  }
  
  ~MyCanvasItem()
  {
    hide(); // Required to avoid segfault - see docs
  }
  
  QPointArray areaPoints() const
  {
    QPointArray res(8);
    
    res.setPoint( 0, QPoint( (int)x() + m_width/4, (int)y() ) );
    res.setPoint( 1, QPoint( (int)x() + 3*m_width/4, (int)y() ) );
    res.setPoint( 2, QPoint( (int)x() + m_width, (int)y() + 3*m_height/8 ) );
    res.setPoint( 3, QPoint( (int)x() + m_width, (int)y() + 5*m_height/8 ) );
    res.setPoint( 4, QPoint( (int)x() + 3*m_width/4, (int)y() + m_height ) );
    res.setPoint( 5, QPoint( (int)x() + m_width/4, (int)y() + m_height ) );
    res.setPoint( 6, QPoint( (int)x(), (int)y() + 5*m_height/8 ) );
    res.setPoint( 7, QPoint( (int)x(), (int)y() + 3*m_height/8 ) );
    
    return res;
  }
  
protected:
  void drawShape( QPainter &p )
  {
    p.drawEllipse( (int)x()+m_width/4, (int)y(), m_width/2, m_height );
    p.drawRect( (int)x(), (int)y()+3*m_height/8, m_width, m_height/4 );
  }
  
private:
  int m_width;
  int m_height;
};

Exemple 12-2

Le main est modifié à deux endroits. Premièrement, la taille du canevas est différente, deuxièmement un certain nombre de lignes a été ajouté pour créer, positionner et nommer l'élément de canevas.

  ...  
  
  QCanvas *c = new QCanvas( 260, 75 );
  
  ...
  
  // Custom canvas item
  MyCanvasItem *my = new MyCanvasItem( 40, 40, c );
  my->setX( 210 );
  my->setY( 10 );
  my->setBrush( Qt::yellow );
  my->setPen( Qt::black );
  my->show();

  t = new QCanvasText( "Custom", c );
  t->setX( 230 );
  t->setY( 55 );
  t->setTextFlags( Qt::AlignHCenter );
  t->show();
  
  ...

Exemple 12-3

L'implémentation de l'élément personnalisé consiste en cinq parties. Premièrement, le constructeur, qui est simple. Les paramètres commencent avec ceux spécifiques à la classe puis sont suivis des paramètres transmis à la classe de base. Le destructeur est également simple. La documentation officielle pour QCanvasPolygonalItem indique que chaque destructeur de classe héritée doit appeler hide.

La troisième partie est l'implémentation de areaPoints. La zone contenue à l'intérieur des points renvoyés doit contenir la forme entière tout en étant aussi petite que possible. La zone renvoyée depuis cette implémentation est montrée dans le schéma 12-2 comme ensemble bleu avec la forme réelle en jaune.

The area and the shape

La zone et la forme.

La quatrième partie est le dessin réel qui est effectué dans la fonction membre drawShape. Le painter passé en paramètre est configuré avec le stylo et la brosse donnés et est translaté de sorte que les coordonnées fournies en utilisant les méthodes setX et setY soit localisées en (0, 0).

La partie finale comprend les variables privées m_width et m_height.Dans une implémentation appropriée la largeur devrait être modifiable en utilisant une méthode setWidth et accessible par une méthode width et serait déclarée comme propriété en utilisant la macro Q_PROPERTY. La même chose naturellement s'applique à la hauteur.

La fenêtre du résultat du deuxième exemple est montrée sur le schéma 12-3.

A custom shape

Schéma 12-3 Une forme personnalisée.

Déplacement Autour

Dans le troisième exemple, la vue du canevas sera changée. Une version faite sur mesure sera créée en sous-classant et la classe résultante permettra de déplacer les formes autour en utilisant un dispositif de pointage.

L'exemple 12-4 montre le code pour l'implémentation de la vue faite sur mesure.

class MyCanvasView : public QCanvasView
{
public:
  MyCanvasView( QCanvas *c, QWidget *parent=0, const char *name=0, WFlags f=0 ) : QCanvasView( c, parent, name, f )
  {
    dragging = 0;
  }
  
protected:
  void contentsMousePressEvent( QMouseEvent *e )
  {
    QCanvasItemList il = canvas()->collisions( e->pos() );
    for( QCanvasItemList::Iterator it=il.begin(); it!=il.end(); ++it )
    {
      if( (*it)->rtti() != QCanvasText::RTTI )
      {
        dragging = (*it);
      
        xoffset = (int)(e->x() - dragging->x());
        yoffset = (int)(e->y() - dragging->y());
        
        return;
      }
    }
  }
  
  void contentsMouseReleaseEvent( QMouseEvent *e )
  {
    if( dragging )
    {
      dragging->setX( e->x() - xoffset );
      dragging->setY( e->y() - yoffset );

      dragging = 0;
      
      canvas()->update();
    }
  }
  
  void contentsMouseMoveEvent( QMouseEvent *e )
  {
    if( dragging )
    {
      dragging->setX( e->x() - xoffset );
      dragging->setY( e->y() - yoffset );

      canvas()->update();
    }
  }
  
private:
  QCanvasItem *dragging;
  int xoffset, yoffset;
};      

Exemple 12-4

Le constructeur positionne simplement le pointeur dragging à null. Ceci indique qu'aucun élément n'est déplacé actuellement.

contentsMousePressEvent détermine si un clic est le début d'une opération de déplacement ou pas en identifiant tout élément sous le pointeur de la souris et en vérifiant que l'élément ne soit pas un QCanvasText. Le but de cette limitation est de montrer comment la méthode membre rtti peut-être employée pour déterminer ce qui doit être déplacé. Si un élément valide est trouvé dragging est définit pour pointer sur lui et les variables xoffset et yoffset reçoivent la différence entre la position de l'élément (c.-à-d. le point que x et y indiquent) et l'endroit de la souris.

Quand la souris est déplacée, la position de l'élément, le cas échéant, est mise à jour. Notez l'appel à canvas()->update() qui est nécessaire pour mettre à jour la vue réelle. Quand le bouton de souris est relâché la position des éléments est ajustée de nouveau et le membre dragging est repositionné à null. Cela termine l'opération de mouvement.

Pour utiliser cette vue depuis la routine main  il est nécessaire de changer la ligne créant la vue de canevas pour créer la version personnalisée à la place.

Résumé

Le code du dernier chapitre peut être téléchargé ici.

Lecture Recommendée

Qt Quarterly à un article sur l'implémentation des éléments de canevas groupés.

This is a part of digitalfanatics.org and is valid XHTML.