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 Jr



Uma 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.

Wanderley Caloni Jr

Wanderley Caloni Jr