Desenvolvimento - C/C++
Desenhando em Cpp Builder - parte 5
Uma das partes mais fáceis e divertidas de se mexer no C++ Builder é a que lida com gráficos.
por Wanderley Caloni JrUma das partes mais fáceis e divertidas de se mexer no C++ Builder é a que lida com gráficos. A abstração da VCL toma conta da alocação e liberação dos objetos gráficos da GDI e nos fornece uma interface para desenhar linhas e figuras geométricas, mexer com bitmaps, usar fontes etc. E ao mesmo tempo você tem acesso ao handles crus da Win32 para que você possa chamar alguma função esotérica da API necessária para o seu programa.
Um Personal PaintBrush usando Canvas
Vamos fazer da área da janela principal uma tela onde possamos desenhar. Para isso, só precisamos fazer duas coisas em nosso programa: saber quando o mouse está com algum botão pressionado e desenhar quando ele estiver sendo "arrastado". Saber o estado dos botões é trivial, podemos capturar isso nos eventos OnMouseDown e OnMouseUp e guardar em alguma variável.
//... private: bool mouseDown; // essa variável guarda o estado do mouse... //... __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { mouseDown = false; // ... e é importante iniciá-la } void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { mouseDown = false; } void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { Canvas->PenPos = TPoint(X, Y); // mais tarde veremos o porquê disso mouseDown = true; }
Para desenhar, todo formulário e mais alguns controles gráficos possuem um objeto chamado Canvas, do tipo TCanvas. Essa classe representa uma superfície de desenho que você pode acessar a partir de seus métodos. Isso é a abstração do conhecido device context da GDI, tornando a programação mais fácil. O desenho de uma linha, por exemplo, é feito literalmente em uma linha de código.
void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { if( mouseDown ) { Canvas->LineTo(X, Y); } }
O método LineTo() desenha uma linha do ponto onde está atualmente a caneta de desenho até a coordenada especificada. Esse é o motivo pelo qual no evento OnMouseDown alteramos a propriedade PenPos do Canvas para o ponto onde o usuário pressiona o botão do mouse.
E Voila! Temos o nosso Personal PaintBrush, com toda a tosquisse que menos de 10 linhas de código podem fazer. OK, ele não é perfeito, mas pode ser melhorado (pois temos o fonte).<p>
O Windows não se lembra do que você desenhou
<p>Um dos problemas nele reflete o comportamento de gráficos em janelas no Windows. Seja o que for que tenhamos desenhado sobre uma janela, seu conteúdo é perdido ao ser sobrescrito por outra janela. Isso porque a memória de vídeo da área de trabalho é compartilhada entre todas as janelas do sistema (isso irá mudar com o "Avalon" - Windows Vista - nota do editor). Precisamos, então, sempre repintar o que é feito durante a execução do programa.Se precisamos repintar, logo precisamos saber tudo o que o usuário fez até então. Uma das técnicas mais baratas no quesito memória para salvar o estado gráfico de uma janela é guardar um histórico das operações realizadas sobre sua superfície e executá-las novamente ao repintar a janela. A GDI é rápida o bastante para que o custo de processamento não seja sentido na maioria dos casos. Para o nosso Paint, apenas um array de coordenadas origem-destino já dá conta do recado:
//... private: bool mouseDown; // essa variável guarda o estado do mouse std::vector<TRect> mouseHistory; // um TRect guarda duas posições XY //... void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { if( mouseDown ) { // guardando a pincelada para reproduzí-la depois mouseHistory.push_back( TRect(Canvas->PenPos, TPoint(X, Y)) ); Canvas->LineTo(X, Y); } }
Quando o Windows precisa que a superfície de uma janela ou parte dela seja repintada ele envia uma mensagem para ela. Essa mensagem é capturada pela VCL e traduzida no evento OnPaint. Nesse evento podemos então usar o nosso histórico de operações gráficas e refazer o estado da janela antes dela ter sido sobreposta:
void __fastcall TForm1::FormPaint(TObject *Sender) { for( size_t i = 0; i < mouseHistory.size(); ++i ) { // primeiro colocamos o objeto pen no lugar origem... Canvas->PenPos = TPoint(mouseHistory[i].Left, mouseHistory[i].Top); // ... e depois reproduzimos nossa pincelada passada Canvas->LineTo(mouseHistory[i].Right, mouseHistory[i].Bottom); } }
Isso é o suficiente para que agora aquelas janelas pop-up não incomodem mais o trabalho do usuário-pintor durante a confecção de sua obra-prima. Os futuros Portinari"s agradecem.
A vida não seria fácil se eu quisesse ser desenhista.
Artigo original retirado de "Desenhando em C++ Builder", por Wanderley Caloni.