Os modulos relacionados ao canvas, isto é, QCanvas, QCanvasView e QCanvasItem e classes que herdam delas, são usadas para exibir gráfivos 2D. OS gráficos podem ser desde um jogo 2D clássico até gráficos de negócios. A documentação oficial diz: “O canvas é otimizado para um grande número de itens, particularmenbte onde apenas uma pequena porcentagem de itens mudam de uma vez. Se o display inteiro muda muito, você deve considerar usando sua própria sub-classe QScrollView personalizada”.
As classes do módulo canvas formam um padrão de desenho clássico, o model-view. A classe QCanvas pe um documento que guarda alguns QCanvasItems. Cada canvas pode ser visto através de um pu mais QCanvasView. Isso significa que o mesmo canva pode ser visto através de diversas visões independentes como diferentes níveis de zoom, rotações, etc.
As formas básicas
O Qt oferece uma faixa de items de canvas. Alguns desses itens são demonstrados no exemplos mostrado a seguir. Simplesmente coloque o código em um arquivo cpp em um diretório vázio e execute “qmake -project && qmake && make” para gerar um executável. O resultado é janela mostrada na figura abaixo:
#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();
}
As classes usadas nesse exemplo são QCanvasText, QCanvasRectangle, QCanvasPolygon, QCanvasLine e QCanvasEllipse. OS últimos quatro são serivados de QCanvasPolygonalItem que é uma classe do canvas que é o melhor ponto de partida para itens personalizados.
Quando estiver lidando com polígonos, observe que as forma resultante tem que ser posicionada usando os métodos setX e setY. Esses métodos traduzem os pontos, assim o (20,0) torna-se (20+60,0+10)=(80,10) e assim em diante.
Quando estiver criando uma elipse os angulos são especificados em 1/16 de graus, dai o …*16 no construtor. As coordenadas especificam o centro da elipse.
Antes de passar para o próximo exemplo e criar um item personalizado, dê uam olhada na tabela abaixo. A classe de item poligonal introduz as propriedades pen e brush. Essas propriedades não são usadas por todas as sub-classes. Isso é resumido na tabela abaixo, mas também é discutivo em detalhes na descrição de cada item na documentação oficial.
Class | Uses Brush | Uses Pen |
QCanvasRectangle | Yes | Yes |
QCanvasPolygon | Yes | No |
QCanvasLine | No | Yes |
QCanvasEllipse | Yes | No |
Uma forma personalizada
O segundo exemplo introduz um item de canvas personalizado, MyCanvasItem. O exemplo abaixo mostra a implementação da classe MyCanvasItem e o logo a seguir mostra as alterações na rotina main do exemplo anterior.
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;
};
O main é alterado em dois lugares. Primeiro, o tamanho do canvas é diferente, depois o número de linhas tem que ser acréscido para criar a posição e label do item personalizado.
...
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();
...
A implementação do item personalizado consiste de cinco partes. Primeiro, o construtor, que é bem direto. Os parâmetros começam com os especifícos da classe e são seguidos pelos argumentos passados para a classe base. O destrutor, como o construtor, é direto. A documentação oficial do QCanvasPolygonalItem especifica que o detrutor de cada classe herdada precisa ser oculto.
A terceira parte é a implementação de areaPoints. A área contida dentro dos pontos de retorno precisa conter a forma inteira sendo o menor possível. A área retornada dessa implementação é mostrada na figura abaixo em azul junto como a forma em amarelo atual.
A quarta parte é o desenho em si que é feito no membro drawShape. O painter fornecido como parâmetro é configurado como uma caneta fornecida e um pincel e é traduzido para que as coordenadas informadas pelos métodos setX e setY estejam localizadas em (0,0).
A parte final consiste das variáveis privadas m_width e m_height. Em uma implementação apropriada a largura tem que ser editável de qualquer forma usando o método setWidth e acessível através de um método de largura e ser declarada como uma propriedade usando a macro Q_PROPERTY. O mesmo naturalmente se aplica para a altura.
A janela resultante para o segundo exemplo é mostrada abaixo:
Movendo-se pela janela
No terceiro exemplo, a visão do canvas pode ser alterada. Uma versão personalizada será criada através de sub-classe e a classe resultante tornará possível mover as formas usando um dispositivo de apontamento.
O exemplo abaixo mostra o código para a implementação da visualização personalizada do canvas:
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;
};
O construtor simplesmente configurar o modo de arraste do ponteiro para null. Isso indica que nenhum item está sendo arrastado.
O contentsMousePressEvent determina se um clique é o início da operação de arraste ou não, localizando qualquer item sobre o ponteiro do mouse e checando de o item é o não um QCanvasItem. O propósito dessa limitação é mostrar como o método rtti pode ser usado para determinar o que está sendo movido. Se um item válido for encontrado é configurado um ponto e as variáveis de deslocamento são configuradas para as diferenças entre os pontos do item (os pontos que as coordenadas x e y indicam) e a localização do mouse.
Quando o mouse é movido para a posição de um item, se existir, é atualizado. Observe a chamada a canvas()->update() que é necessária para atualizar a visão atual. Quando o botão do mouse é solto a posição do item é ajustada novamente e o membro de arrasto é ajustado para null novamente. Isso finaliza a operação de movimentação.
Para usar essa visão a partir da rotina main é necessário altear a linha de criação do canvas para criar uma versão personalizada do objeto.
Sumário
O código para os exemplos desse capitulo podem ser baixados aqui ex12.tar
Traduzido de http://www.digitalfanatics.org/projects/qt_tutorial/chapter12.html