Traduzione a cura di Fabrizio Angius [qtsolutions -A-T- gmx.net]
English TOC.
Il modulo riguardante la tela, cioe' le classi QCanvas, QCanvasView, QCanvasItem con le varie sottoclassi, viene utilizzato per rappresentare grafici 2D. Questi grafici possono essere di ogni tipo, dal videogioco al grafico commerciale. La documentazione ufficiale afferma quanto segue:
La tela e' ottimizzata per un elevato numero di oggetti, in particolare per situazioni in cui solo una percentuale ridotta di elementi cambia in continuazione. Se i cambiamenti riguardano l'intera scena, dovreste considerare l'ipotesi di utilizzare una vostra sottoclasse di QScrollView.
Le classi del modulo Canvas utilizzano un design pattern classico, quello model - view. La classe QCanvas e' un documento contenente un certo numero di QCanvasItem. Ciascuna tela puo' essere visualizzata mediante uno o piu' QCanvasView. Questo significa che potete vedere una stessa scena da diversi punti di vista, con diversi livelli di zoom, angolazioni, ecc.
Qt offre una serie di elementi per la tela. Alcuni di questi sono mostrati nell'esempio 12-2 qui sotto. Basta inserire il codice in un file .cpp in una cartella vuota ed eseguire qmake -project && qmake && make per generare un eseguibile. La figura 12-1 mostra il risultato.
#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();
}
Le classi utilizzate in questo esempio sono QCanvasText, QCanvasRectangle, QCanvasPolygon, QCanvasLine e QCanvasEllipse. Le ultime quattro sono sottoclassi di QCanvasPolygonalItem che e' il miglior punto di partenza per la maggior parte degli oggetti personalizzati.
Quando si ha a che fare con un poligono, e' necessario posizionare la figura prodotta utilizzando i metodi setX e setY. Questi metodi traducono i punti, per cui il punto (20, 0) diventa (20+60, 0+10) = (80, 10) e cosi' via.
Quando si crea un'elisse, gli angoli vengono specificati in sedicesimi di grado, ecco perche' c'e' quel ...*16 nel costruttore. Le coordinate indicano il centro dell'elisse.
Prima di passare al prossimo esempio e creare un oggetto personalizzato, consiglio di dare uno sguardo veloce alla tabella 12-1. La classe per gli oggetti poligonali introduce le proprieta' pen e brush. Queste non sono comunque utilizzate da tutte le sottoclassi. I dettagli si possono vedere nella tabella ma anche nella documentazione ufficiale riguardante i singoli oggetti.
Classe | Utilizza Brush | Utilizza Pen |
QCanvasRectangle | Si | Si |
QCanvasPolygon | Si | No |
QCanvasLine | No | Si |
QCanvasEllipse | Si | No |
Il secondo esempio introduce uo oggetto per tela personalizzato, MyCanvasItem. L'esempio 12-2 mostra la classe che implementa MyCanvasItem e l'esempio 12-3 mostra i cambiamenti alla routine main dell'esempio precedente.
class MyCanvasItem : public QCanvasPolygonalItem
{
public:
MyCanvasItem( int width, int height, QCanvas *c ) : QCanvasPolygonalItem( c )
{
m_width = width;
m_height = height;
}
~MyCanvasItem()
{
hide(); // Necessario per evitare segmentation fault - vedere la documentazione
}
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;
};
Il main cambia in due punti. Per prima cosa, cambiano le dimensioni della tela, inoltre sono state agiunte alcune righe per creare, posizionare ed etichettare l'oggetto personalizzato.
...
QCanvas *c = new QCanvas( 260, 75 );
...
// Oggetto per tela personalizzato
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();
...
L'implementazione dell'oggetto e' suddivisa in cinque parti. Per iniziare c'e' il costruttore, che non necessita di commenti. I parametri iniziano con quelli specifici per la classe seguiti da quelli da passare alla superclasse. Anche il distruttore e' banale. La documentazione ufficiale per QCanvasPolygonalItem indica che il distruttore di una sottoclasse deve invocare il metodo hide.
La terza parte riguarda l'implementazione di areaPoints. L'area delimitata dai punti ritornati deve contenere l'intera figura pur restando il piu' piccola possibile. L'area ritornata dalla nostra implementazione e' mostrata nella figura 12-2 in blu; la figura appare invece colorata di giallo.
La quarta parte e' il disegno vero e' proprio, eseguito all'interno del metodo drawShape. Il painter passato come parametro viene impostato con i pen e brush corretti e viene poi traslato in modo che le coordinate impostate con i metodi setX e setY corrispondano al punto (0, 0).
La parte finale riguarda le variabili private m_width e m_height. In una implementazione corretta, la larghezza dovrebbe essere modificabile mediante un metodo setWidth, leggibile mediante un metodo width e dichiarata come proprieta' utilizzando la macro Q_PROPERTY. Lo stesso vale ovviamente anche per l'altezza.
La finestra prodotta dal secondo esempio e' mostrata nella figura 12-3.
Nel terzo esempio cambieremo il punto di vista utilizzato per visualizzare la scena. Creeremo una versione personalizzata con una sottoclasse e la classe prodotta ci permettera' di muovere le figure utilizzando un dispositivo di puntamento.
L'esempio 12-4 mostra il codice dell'implementazione della vista personalizzata.
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;
};
Il costruttore imposta semplicemente il puntatore dragging a null. Questo ci indica che nessun oggetto e' momentaneamente trascinato.
Il metodo contentsMousePressEvent ci dice se un click e' l'inizio di un'operazione di trascinamento oppure no prendendo tutti gli oggetti sotto al puntatore del mouse e vedendo se sono istanze di QCanvasText. Lo scopo di questa limitazione e' di mostare come sia possibile utilizzare il metodo rtti per determinate che cosa si sta trascinando. Se si trova un oggetto corretto, lo si fa puntare da dragging e si impostano le variabili di offset in base alla differenza tra l'hot-spot (p.es. il punto indicato dalle sue coordinate x e y) e la posizione del mouse.
Quando si sposta il mouse si aggiorna la posizione dell'eventuale oggetto. Notate la chiamata a canvas()->update() necessaria per aggiornare la vista corrente. Quando viene rilasciato il pulsante del mouse, si aggiorna nuovamente la posizione dell'oggetto e si rimette a null il puntatore dragging. In questo modo completiamo l'operazione di spostamento.
Per poter utilizzare questa vista dal main e' necessario cambiare la linea in cui si crea la vista per la tela in modo da creare quella personalizzata.
Il codice sorgente per questo capitolo puo' essere scaricato da qui.
Qt Quarterly ha un articolo sull'implementazione di oggetti raggruppabili.
This is a part of digitalfanatics.org and is valid XHTML.
Copyright (c) 2002-2004 by Johan Thelin (e8johan -at- digitalfanatics.org). This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 (local copy) or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder. Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.