24 de Septiembre de 1998
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.
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 |
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;
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.
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.