Tutorial Qt – Capitulo 11 – Canvas

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