Este artículo explica como crear formas no rectangulares (por ejemplo, poligonales)

Formas no rectangulares - Parte I

Copyright © 2000 Alirio A. Gavidia B.. Para ver más de
mis artículos visite Programación Orientada en Delphi

LMD-Tools 6.1 - 300+ componentes para varias tareas de desarrollo!

Introducción

El presente ensayo pretende demostrar las posibilidades de usos de las formas no rectangulares bajo Windows. El uso es simple pero las posibilidades parecen ser interesantes. Adicionalmente, en particular en esta primera parte, se hará uso de dos elementos importantes: Canvas (lienzo) y Timer (temporizador).

El primer ejemplo

En el artículo anterior (ver "Post-it". Ventanas ajustables sin borde ni título) hice referencia a la posibilidad de crear un reloj analógico redondo como lo hace el "PowerToys" de Windows. Hacer un formulario redondo es simple y se limita a llamar a dos procedimientos. El reloj debe además dibujar agujas (lo que requiere cierto cálculo matemático) y actualizarse unas 60 veces por minuto (para ello el TTimer).

Nota: Reloj Analógico por contraposición a Digital. El término NO es correcto porque el reloj que aquí creamos aún cuando tenga agujas es digital. Para que el reloj imitara un comportamiento realmente analógico tendría que tener un movimiento continuo, el que aquí creamos se mueve de segundo en segundo por lo que tiene un número de estados definidos y finitos eso lo califica de digital. Parece ser que el término más adecuado es "reloj de agujas".

Sin título

El reloj se basa en un formulario simple sin borde; con ello se elimina la barra de título, que resulta en un estorbo al tener un formulario redondo. Este genera adicionalmente el problema de traslado del formulario. Por este motivo la operación de arrastre debe ser atendida desde el área cliente del formulario. Para resolver este inconveniente hay varios métodos. Opto por el aplicado en el artículo anterior (uso del wm_Syscommand $f012):

procedure TForm1.FormMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  ReleaseCapture;
  Perform(wm_Syscommand, $f012, 0)
end;

Otra técnica es responder al Wm_NcHitTest y retornar como resultado htCaption. Esto simplemente es comunicarle a Windows que se hizo clic sobre el área de título. Me parece que hay al menos otra técnica más pero no es objetivo de este ensayo.

El "timer"

En el formulario se incluye un control TTimer. Este control dispara un evento de manera aproximada cada cierto intervalo (por la propiedad interval dada en milisegundos). Esta bien documentado su uso en Delphi, pero debo agregar algo: no es exacto.

El evento asociado al timer ejecuta tres acciones: eliminar agujas de posición anterior, tomar la hora del sistema y colocar agujas en una nueva posición.

El "Canvas"

El Canvas provee capacidades gráficas para controles gráficos y formularios. Brinda posibilidades para dibujar líneas, elipses, polígonos, textos y otros. Su manejo es simple y práctico.

Hacemos uso de los siguientes elementos del Canvas:
 

  • Pen, para establecer color y modo de trazado de línea.
  • MoveTo, para establecer el punto inicial de trazado de una línea.
  • LineTo, para trazar una línea desde la última posición a la nueva.
  • Rectangle, para trazar un rectángulo según los parámetros especificados.

Es de notar particularmente la propiedad Canvas.Pen.Mode que nos permite utilizar valores como pmXor o pmCopy (entre muchos otros).

Nota: El uso del Xor como operador lógico tiene una propiedad interesante y es que aplicada dos veces se obtiene el valor original, esto es: x xor y resulta en z si a z se le aplica xor y nuevamente resulta en x. Esto es muy útil para cifrados simples donde queremos aplicar una clave para cifrar y la misma clave y el mismo algoritmo para descifrar. También es muy útil para casos donde queremos dibujar algo y luego borrarlo porque para borrarlo sólo se requiere volver a dibujar aplicando xor entre el fondo y el pixel a dibujar.

El evento Paint del formulario ciertamente hace uso de la propiedad Mode y Color de la plumilla obteniendo un curioso efecto. El resultado, al menos para mi, no fue el esperado, sin embargo fue agradable. Todo ello parece ser consecuencia de la manera en que se crea un rectángulo, el mismo es rellenado y luego se trazan los bordes superior e izquierdo, que bajo el operador Xor generan un color blanco, la imagen resultante genera un efecto 3D. Sugiero al lector comentar las lineas de Mode y Color para obtener sus propias conclusiones.

Como comentario adicional Pen.Color sólo afecta al borde del rectángulo. El relleno es definido por la propiedad Brush del Canvas la cual, en este caso, permanece con su valor por defecto.

procedure TForm1.FormPaint(Sender: TObject);
Var
  X1, Y1, i : integer;
  Angle     : double;
begin
  Canvas.Pen.Mode := pmXor;
  Canvas.Pen.Color := clBtnFace;
  for i:=1 to 12 do
  begin
    Angle := i * 2 * Pi / 12;
    X1 := trunc(X0 + Len * Sin(Angle));
    Y1 := trunc(Y0 - Len * Cos(Angle));
    Canvas.Rectangle(X1-4,Y1-4,X1+4,Y1+4)
  end;
  Canvas.Pen.Mode := pmCopy;
  Canvas.Pen.Color := clBlack;
end;

De aritmética

El sistema de coordenadas tiene el punto cero en la esquina superior izquierda. Para determinar la posición de la aguja necesitamos el centro y el extremo. El centro es la mitad del ancho y la altura. Para la longitud de las agujas tomamos una fracción del mínimo entre el ancho y la altura.

Conocido el centro y largo de la aguja sólo resta conocer la posición de su extremo, para ello se determina el ángulo. Aquí terminamos recurriendo a la trigonometría. El ángulo es determinado con cero en el 12, 90 grados a las 3, 180 grados a las 6 hasta 360 a las 12. El seno del referido ángulo nos da la apertura en la coordenada "X", el coseno la apertura en la coordenada "Y". Como nota adicional el sistema de coordenadas en el eje "Y" esta invertido con respecto a los sistemas trigonométricos usuales. Todos los ángulos deben ser expresados como Radianes para efectos de la función seno y coseno. El procedimiento DrawWatchHand efectúa los cálculos (también en FormPaint)

procedure Tform1.DrawWatchHand;
  :
  :

  // Now draw Second
  Canvas.MoveTo(X0, Y0);
  Angle := S * 2 * Pi / 60;
  X1 := trunc(X0 + 9*Len * Sin(Angle)/10);
  Y1 := trunc(Y0 - 9*Len * Cos(Angle)/10);
  Canvas.LineTo(X1, Y1);
  :
  :

Nótese que cada aguja utiliza una proporción del valor de Len. Así los segundos son 9/10 de Len, los minutos 3/4 y las horas 1/2.

Redondo

Hasta ahora se ha utilizado el Canvas para dibujar, se ha eliminado el título y sustituido su funcionalidad, se ha efectuado cierta aritmética y se ha hecho uso de un timer. El punto central que da título a este artículo es crear una forma no rectangular. Bien esto es tremendamente simple y se resuelve con dos llamadas a Windows en el FormCreate" del formulario principal:

  hRegion := CreateEllipticRgnIndirect(R);
  SetWindowRgn(handle, hRegion, true);

CreateEllipticRgnIndirect es una versión de CreateEllipticRgn con los parámetros (un rectángulo) empaquetados en una sola estructura. Esta rutina retorna un "handle" (manejador) a una región elíptica circunscrita al rectángulo dado. Si se quiere se pueden definir regiones no circulares usando CreatePolygonRgn para polígonos, CreateRectRgn para rectángulos (sin sentido en el caso que nos atañe, pero es posible), CreateRoundRectRgn para rectángulos de esquina redondeadas, o CombineRgn para combinar los resultados de otras regiones.

SetWindowRgn define el área en la cual es visible una ventana. Es interesante que bajo este concepto la ventana rectangular sigue existiendo y sólo hemos definido algo así como un agujero por donde verla.

Un "Bug", un segundo ejemplo

Mi primer intento de forma no rectangular fue hace un par de años en Delphi 3. Fue una travesura motivado a que conozco personas que detestan terriblemente a las cucarachas (y aquí en el trópico las hay espectaculares). Así que creé una cucaracha que se pasea por el escritorio de Windows. La forma de la misma es elíptica y yo fundamentalmente creé unos dibujos para distintos ángulos (cuatro: dos verticales y dos horizontales). El resultado es algo tosco pero disfruté al crear este "bicho". Sé sobradamente que hay mejores maneras de hacer esto pero viene al caso por el tema de este artículo.

Lo siguiente

Bien, esto abre posibilidades de uso interesantes (como ya antes mencioné). Creo que la que más adecuada parece ser la de "Skins" como los de algunas aplicaciones. Para referencia están "Winamp", "NeoPlanet", "Windows Media Player" (versión nueva). La técnica que permite formas no rectangulares es aplicable a botones y otros controles.

Bibliografía

  • Delphi Developer's Handbook,
    Marco Cantù, Tim Gooch, John F. Lam
    SYBEX
    ISBN: 0-7821-1987-5
    1.998

  • La Biblia de Delphi 5
    Marco Cantù
    ANAYA
    ISBN: 84-415-0994-8
    2.000

 
Los fuentes de este artículo están a su disposición para descargar.

JfControls Library - para Delphi y C++ Builder