24 de Septiembre de 1998

Un programa de dibujo (I)

Indice

  1. Teoría.

  2. Un ejemplo rápido.

  3. Primer problema.

  4. Repintado. Otro problema más grave.

  5. Dejando que Delphi haga el trabajo sucio.

Teoría.

Windows es un sistema operativo gráfico, las ventanas, los controles, los botones no son más que gráficos o dibujos si lo prefieres. Cuando colocamos un control sobre una ventana, este es dibujado sobre la ventana. Todo es posible por la capacidad que tiene Windows para dibujar, pero tiene una pega, si deseas dibujar usando los servicios de Windows, pues es te enfrentas a una gran cantidad de instrucciones y pasos necesarios para llegar a dibujar sobre la ventana. Pero de nuevo Delphi pone a nuestra disposición una seria de recursos que nos facilitan el trabajo.

Pero antes de empezar hay que tener presente un concepto fundamental. Este es como windows interpreta sus ventanas para poder dibujar sobre ellas. Windows solo permite que se dibuje sobre el fondo de las ventanas, o también conocido como el área cliente. Esta zona es lo que queda de la ventana, si le quitamos los bordes y banda superior donde esta el nombre de la aplicación y los controles para minimizar, maximizar o cerrar la ventana. Pues esta zona, cuando hablamos de dibujar, se llama Lienzo (en inglés Canvas). Para poder dibujar sobre el lienzo, debemos indicar donde queremos dibujar, y eso  se consigue indicando las coordenadas del punto donde queremos dibujar. El sistema de coordenadas usado por Windows, es un sistema cartesiano, o sea el sistema de coordenadas con dos ejes, el horizontal representado por al letra X y el vertical representado por la letra Y, con el centro del coordenadas en la esquina superior izquierda del lienzo.

Un ejemplo rápido.

Después de esta parrafada, y sabiendo que el ratón por medio de sus eventos devuelve la posición del cursor sobre el lienzo, es fácil hacer un programa que dibuje. Para ello vamos a hacer un pequeño ejemplo. Para ello debemos tener en cuenta un detalles. Vamos a dibujar sobre la ventana, cuando tengamos el botón izquierdo pulsado del ratón y se mueva. Para conseguir esto, debemos capturar los eventos del ratón, que son tres, OnMouseDown, OnMouseMove y OnMouseUp.Así pues cuando se pulse un botón (OnMouseDown) y este sea el botón izquierdo, pondremos una variable lógica (tipo Boolean) a verdadero (True); cuando se mueva el ratón y antes se haya pulsado el botón izquierdo, lo que es indicado por la variable usada en el evento anterior, pues dibujaremos un punto (pixel) en las coordenadas del ratón; cuando soltemos el botón izquierdo cambiaremos el valor de la variable que hemos usado antes. Como la variable es usada en tres procedimiento diferentes, esta debe ser declarada en la sección privada del formulario. Aquí debajo tenéis el código necesario para realizar esto:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
  private
    { Private declarations }
    Pintando : Boolean;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
Pintando := False;
end;

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
If Button = mbleft Then
   pintando := True;
end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
 If Button = mbLeft Then
  pintando := false;
end;

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
 If Pintando = True Then
   Canvas.Pixels [X,Y] := clblack;
end;
end.

Como podeis ver, la variable que indica si está pulsado el botón izquierdo se llama Pulsando, y toma el valor verdadero, cuando el botón pulsado es el izquierdo. Se conoce cual es el botón pulsado ya que el procedimiento que se ejecuta cuando se produce el evento, contiene una variable que se llama Button, la cual indica el botón pulsado, estos son los valores que puede tomar:

Valor

Descripción

mbRight Botón Derecho pulsado
mbLeft Botón Izquierdo pulsado
mbMiddle Botón Central pulsado

 

Primer problema.

ejemplo1.gif (7671 bytes)Prueba el programa; para dibujar mantén pulsado el botón izquierdo y mueve el ratón, para dejar de dibujar suelta el botón. Lo primero que observas es que no genera una línea constante, sino que cuanto más rápido mueves el ratón más separados salen los puntos. Esto se produce porque el evento OnMouseMove no se produce cada vez que se desplaza un pixel el ratón, sino cada vez que hay un desplazamiento significativo. La solución a este problema está en "tomar nota" de donde está el ratón cuando se pulsa el botón izquierdo, y cuando este se mueva dibujar una línea desde donde estaba hasta donde esta ahora el puntero del ratón. Una solución sería colocar las coordenadas del ratón en dos variables, y luego en el evento de movimiento dibujar una línea desde las coordenadas guardadas hasta las actuales, y después guardar de nuevo las coordenadas actuales de ratón. Pues eso es lo que hay que hacer, pero vamos a indicar a Delphi que lo haga él. Windows mantiene la posición actual del "pincel" que pinta sobre el lienzo, y no tiene nada que ver con la posición actual del ratón, son dos punteros diferentes. Así que cuando se pulse el botón izquierdo indicaremos que mueva el pincel a la posición actual del cursor, lo cual se hace con la orden método MoveTo así:

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
If Button = mbleft Then
  Begin
   pintando := True;
   Canvas.MoveTo (x,y);
  end;
end;

Bien, ahora solo queda indicar que se pinte una línea desde donde apunta el pincel hasta la posición actual, lo cual se consigue con el método LineTo, por lo tanto el procedimiento OnMouseMove queda así:

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
 If Pintando = True Then
    Canvas.LineTo (x,y);
end;

Repintado. Otro problema más grave.

Hay otro problema, más complejo, que debemos solucionar, y es que el dibujo que hayamos hecho se pierde si la ventana es tapada por otra. Haz la prueba de dibujar algo, ahora minimiza la ventana, o coloca encima la ventana de otra aplicación, después restaura la ventana de nuestra aplicación. Sorpresa no hay nada. Esto es debido a la filosofía con la cual ha sido construido Windows. Windows no se preocupa por contenido de sus ventanas, simplemente la única responsabilidad que toma es la de indica a la ventana que debe pintar su contenido cuando es restaurada. Esto lo notifica por medio del evento OnPaint, y es ahí donde debemos situar el código encargado de restaurar el contenido de la   ventana. Bien, entonces lo que habría que hacer es guardar el contenido de la ventana, o sea el lienzo, y luego restaurarlo. Quizás estés pensando que esto se complica, y la verdad es que no te falta razón, pero de nuevo hay una salida fácil puesta a nuestra disposición por Delphi.

Dejando que Delphi haga el trabajo sucio.

ejemplo2.gif (4092 bytes)La solución pasa por usar un control que tenga las mismas posibilidad de dibujo y además nos solucione el problema de redibujado de la ventana. Este control se llama Image, y lo encontrará en la pestaña Additional (Adicional) de la paleta de componentes, es ese que parece un cuadro. Así que cierra esta aplicación (guardala si quieres) y abre una nueva. Sobre el formulario sitúa un componente Image, y su propiedad Align con el valor alClient, para que ocupe todo el formulario. Ahora hay capturar los mismos eventos que antes, pero esta vez en lugar de ser los eventos del formulario, son los evento del componente. Y además en lugar de dibujar sobre el lienzo del formulario, debemos dibujar sobre el lienzo del componente Image, las reformas son míminas, sino compara este código fuente con el anterior.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls;

type
  TForm1 = class(TForm)
    Image1: TImage;
    procedure FormCreate(Sender: TObject);
    procedure Image1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure Image1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
    Pintando : Boolean;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
 Pintando := False;
 Image1.Canvas.Refresh;
end;

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
 If Button = mbLeft Then
 Begin
   Pintando := True;
   Image1.Canvas.MoveTo (X,Y);
 end;
end;

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
 If Pintando Then
  Image1.Canvas.LineTo (X,Y);
end;

procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
 If Button = mbLeft Then
  pintando := False;
end;

end.

Fíjate que en lugar de escribir Canvas directamente, ahora aparece Image1, esto es para que Delphi sepa que el lienzo es el de componente Image1. Aparte en el evento Oncreate del formulario aparece una orden nueva, que es Image1.Canvas.Refresh, esta línea tiene como objeto inicializar el lienzo del componente Image1, para evitar el molesto cambio de aspecto que se produce la primera vez que pulsamos para dibujar, prueba a quitar este línea y observa la diferencia.

Hemos visto como dibujar, pero de ahí a un programa de dibujo hay un abismo. A nuestro programa le falta hacer uso de los colores, y no estaría de más que nos echara una mano en lo que se refiere a dibujar formas básicas, como por ejemplo cuadrados, círculos, etc. Todo esto y más cosas las veremos en el siguiente capítulo.