27 de noviembre de 2006

Visual FoxPro muestra su transparencia

Artículo original: Visual Foxpro shows its Glass!
http://weblogs.foxite.com/bernardbout/archive/2006/10/28/2765.aspx
Autor: Bernard Bout
Traducido por: Ana María Bisbé York


Vista está casi aquí con su nuevo tema Aero Glass (transparencia) y sus nuevas APIs. Se ha mostrado ya que VFP puede correr en Vista y Craig Boyd ha mostrado cómo lograr algunos de los efectos de transparencia en un formulario VFP. Si no lo ha hecho, vea aquí:

http://www.sweetpotatosoftware.com/SPSBlog/Vista's Aero Glass for Visual FoxPro

Muchos de nosotros no nos habremos actualizado aun posiblemente hasta el SP1 o incluso después, ya que el PC más nuevo y poderoso lo necesitará y ¿Quién tiene tanto suelto como para además agregarle el coste del propio Vista? He investigado en Internet y he encontrado algunos sitios que piden simular los efectos de transparencia en XP. Incluso, he descargado un ejemplo en .NET que realmente realiza ese efecto; pero el formulario es tan lento que era imposible utilizarlo en la práctica. Pero cuidado, Visual FoxPro es rápido. Nosotros sabemos eso. Entonces, decidí intentar por mi cuenta.

¿Qué tal si tomamos un formulario cortamos porciones de el, y hacemos que las partes visibles sean transparentes y colocamos una forma opaca con objetos dentro de ella? Podría entonces controlar su ubicación, movimiento, etc. En una pantalla podría tener una apariencia de un formulario transparente. Entonces, allá vamos ...

(Puede descargar el código completo así como el artículo del enlace adjunto al final de este escrito)

Crear Mainfrom.scx

Comienzo con una forma normal. El formulario tiene configuradas las siguientes propiedades:
BackColour = RGB(64,64,64) 
BorderStyle = 0 - Sin Borde
ShowWindow = 2 - Como formulario de Nivel superior
TitleBar = 0 – Desactivado
El formulario tiene también algunas propiedades y Métodos los cuales se pueden ver en el ejemplo que se incluye. El Nombre del formulario es MainForm.

Luego agregué algunos elementos de diseño (skin) como se muestra:

 (el cual que está estirado) y  en la parte superior.

En el mismo centro coloqué una forma y fijé su color de fondo en RGB(0,255,0) (verde brillante)
Para tener las esquinas redondeadas coloqué 4 imágenes en las 4 esquinas las que tienen esta apariencia, 5 píxeles por 5 píxeles. (agrandado):

 para el borde superior izquierdo y  para el borde superior derecho.

Otros dos similares se colocaron al final.



La imagen final tiene este aspecto:



Craig Boyd tiene un código muy bueno para el diseño del formulario. Lo he leído de aquí:

http://www.sweetpotatosoftware.com/SPSBlog/Skinning a Form in Visual FoxPro
Yo utilicé su método para diseñar mi formulario.
Mi formulario tiene un método de usuario - CreateSkinDefinition que tiene su código a partir del código de Craig:
LOCAL lnFormWidth, lnFormHeight, lnXCoord, lnYCoord, lnStartPixelBlock, ;
  rgbGreenScreen, llContiguousPixels, lcSkinDefinition
lnFormWidth = THIS.WIDTH
lnFormHeight = THIS.HEIGHT
STORE 0 TO lnXCoord, lnYCoord, lnStartPixelBlock
** Cualquiera que sea el color en la esquina superior izquierda se considera transparente
*rgbGreenScreen = THIS.point(1, lnFormHeight - 1) 
rgbGreenScreen = RGB(0,255,0)
llContiguousPixels = .F.
lcSkinDefinition = ""
thisform.cFormdef = lcSkinDefinition
FOR lnYCoord = 1 TO lnFormHeight
  WAIT WINDOW AT 10,10 "One Moment Please..." + ;
    PADL(TRANSFORM(INT(100 * (lnYCoord/lnFormHeight)))+ "%", 8) NOWAIT
  FOR lnXCoord = 1 TO lnFormWidth
    IF THIS.point(lnXCoord, lnYCoord) = rgbGreenScreen OR ;
      lnXCoord = lnFormWidth OR lnYCoord = lnFormHeight
      IF llContiguousPixels
        llContiguousPixels = .F.
        lcSkinDefinition = lcSkinDefinition + TRANSFORM(lnStartPixelBlock) + ":" + ;
          TRANSFORM(lnYCoord) + ":" + TRANSFORM(lnXCoord) + ":"
      ENDIF
    ELSE
      IF !llContiguousPixels
        llContiguousPixels = .T.
        lnStartPixelBlock = lnXCoord
      ENDIF
    ENDIF
  ENDFOR
ENDFOR
WAIT CLEAR
* Lo GUARDA
LOCAL lcFile
lcFile = PUTFILE("Guarda la definición del diseño como:", SYS(2015) + ".txt")
IF !EMPTY(lcFile)
  SET SAFETY OFF
  STRTOFILE(lcSkinDefinition, lcFile, 0)
  SET SAFETY ON
  MESSAGEBOX("La definición del diseño ha sido guardada satisfactoriamente en el archivo.", ;
    64, "Skin Definition Saved")
  RETURN
ENDIF
Esto creará una definición de diseño para mi formulario. Básicamente cualquier área del formulario que es RGB(0,255,0) será incluida en la definición. Esto lo he guardado en mainform.txt.

Una vez que se fue creada la definición de diseño, puedo eliminar todas las áreas verdes - Los 4 bitmaps de las esquinas y la forma verde del centro.

Y entonces, creé un método nuevo llamado SkipDef que tiene el código:
TEXT TO lcSkin NOSHOW
....
.... 
ENDTEXT
RETURN lcSkin
Copié y pegué todo el contenido de mainform.txt entre comandos TEXT ... ENDTEXT

En el Init del formulario tengo este código que hace toda la magia del diseño del formulario:
Local lnWindowRegion
* corta todas las partes indeseadas de la pantalla - Gracias Craig Boyd
lnWindowRegion = This.readskindefinition()
SetWindowRgn(This.HWnd, lnWindowRegion, .T.)

* Hacerlo transparente
Declare SetWindowLong In Win32Api As _Sol_SetWindowLong Integer, Integer, Integer
Declare SetLayeredWindowAttributes In Win32Api As _Sol_SetLayeredWindowAttributes ;
  Integer, String, Integer, Integer
_Sol_SetWindowLong(Thisform.HWnd, -20,524288) && 0x00080000)
_Sol_SetLayeredWindowAttributes(Thisform.HWnd, 0, 170, 2)
Si ejecuto el formulario las áreas verdes se pueden cortar y el formulario es transparente con contornos redondeados.



Como puede ver del formulario de ejemplo, hay botones invisibles colocados sobre la "X" para cerrar y la "-" para minimizar el formulario. No quiero mostrar todo el código aquí para no hacer este escrito demasiado largo. Puede ver fácilmente al abrir la ventana del código de objeto el código. Aquí también hay código en un contenedor transparente colocado sobre la barra de título al mover el formulario.

Una vez que quedé satisfecho con el resultado final agregué el siguiente código al Init.
*este código debe ser ejecutado sólo después que fue creado el formulario hijo 
* abre el formulario hijo con sus objetos
Do Form childgreenform Noshow With This
* tiene el mismo título que su padre
Thisform.oChild.lblFormCaption.Caption = This.lblMain.Caption
* reordena las ventanas
Thisform.reorderWindows()
La primera sección de código llama al método readskindefinition(), que es un método de usuario que lee en la definición de diseño creada anteriormente. La segunda sección hace la ventana transparente. Luego llega la llamada a la ventana hija abierta, configura su título y llama a reorderWindows(). Esto lo explicaré más tarde.

El código que lee la definición del diseño es:
*readskindefinition
* crea la región del formulario
#DEFINE RGN_OR 2
LOCAL lnXCoord, lnYCoord, lnStartPixelBlock, hOverallRegion, hPartRegion, ;
  llFirstPart, lnCounter, lnTotalElements
hOverallRegion = 0
  STORE 0 TO lnXCoord, lnYCoord, lnStartPixelBlock, hPartRegion
  llFirstPart = .T.
  lnTotalElements = ALINES(aryRegionCoord, ThisForm.SkinDef(), 1, ":") - 3
  lcDecimalsWas = SET("Decimals")
  FOR lnCounter = 1 TO lnTotalElements STEP 3
    lnStartPixelBlock = VAL(aryRegionCoord(lnCounter))
    lnYCoord = VAL(aryRegionCoord(lnCounter + 1))
    lnXCoord = VAL(aryRegionCoord(lnCounter + 2))
    hPartRegion = CreateRectRgn(lnStartPixelBlock, lnYCoord, lnXCoord, lnYCoord + 1)
    IF llFirstPart
      hOverallRegion = hPartRegion
      llFirstPart = .F.
    ELSE
      CombineRgn(hOverallRegion, hOverallRegion, hPartRegion, RGN_OR)
      DeleteObject(hPartRegion) && Liberar la memoria
    ENDIF
  ENDFOR
ENDIF
RETURN hOverallRegion
Existe también código para controlar las acciones de minimizar, cerrar, y otras del formulario. Revise todo ese código, por favor. Es demasiado para explicar aquí.

De esta forma, está completada la armazón externa principal. Ahora puedo guardar el formulario completo como una clase y subclase donde quiera que la necesite. Para este artículo he utilizado el formulario "tal cual" de tal forma que el código es muy sencillo de ver. Solamente abra el formulario en el Examinador de clases y exporte el código para ver el código completo en un único lugar. Esta es la forma en que se ve el formulario en el IDE:



El formulario hijo

Ahora lo que yo necesito es otro formulario con objetos que yo coloque detrás del formulario. Los objetos estarán visibles a través de la sección de interruptor y puede ser seleccionado.

No voy a profundizar en detalles de todo el código para este formulario ya que el código se puede ver abriendo los métodos o abriendo el formulario en el Examinador de clases. Básicamente es únicamente un bitmap verde que es una esquina (superior izquierda) y el formulario de fondo se ha hecho en verde. En la posición exacta del la forma verde está en el MainForm, yo coloqué otra forma con color RGB(64,64,64). En esta área organizo todos mis controles. Vea que si creo una clase desde este formulario los objetos se colocarán después.

Ahora el formulario tiene el siguiente aspecto:



y luego la definición de diseño se inserta de forma similar al formulario de arriba y se ejecuta, se ve como sigue. Las áreas verdes han sido borradas.



Puede ver donde han sido cortadas las partes. Aunque aquí no hay necesidad de hacer su transparencia.

Una vez que esté satisfecho y la definición de diseño está pegada, las partes verdes pueden ser eliminados, dejando el formulario de la siguiente forma. A este formulario le he llamado ChildForm.

Aquí está el aspecto completo, con los objetos, en el IDE.



Ahora agrego código extra y métodos para guardarlo como clase, lista para ser subclaseada y agregar objetos. Ahora explicaré la técnica principal y el código asociado.

La técnica

Recuerde que tengo un formulario medio transparente con un área cortada y un formulario de similar tamaño con el borde y las partes de abajo el título cortó cortadas. Si coloco el formulario principal exactamente sobre el formulario hijo, entonces, la ilusión se completará. Las partes del formulario hijo se verán a través del formulario original. Además cada formulario tendrá una instancia de otro, entonces luego se puede hacer la referencia a los objetos del formulario.

Primero, en el formulario padre añado este código:
* abrir el formulario padre 
Do Form childgreenform Noshow With This
Abrí el formulario hijo oculto y le pasé una instancia de Mainform.

El formulario hijo tiene este código en su INIT:
LPARAMETERS oMainform
Luego el código para configurar la Region (que se corta) luego esto:
Aquí está la instancia del formulario principal está almacenado en una variable de formulario.
IF VARTYPE(oMainform) = "O"
  WITH This
    .omainform = oMainform
    .Left = oMainform.Left 
    .Top = oMainform.Top
    .Visible = .T.
  ENDWITH
  oMainForm.announce(ThisForm)
ENDIF
Llamé entonces al método de usuario Announce() en el formulario principal pasando una instancia al formulario hijo. El único propósito de este método Announce es aceptar la instancia y guardarla en su propiedad de usuario. Ahora, cada formulario guarda una instancia del otro y puede llamar a los métodos y configurar propiedades del otro.

Una vez que los dos formularios se están ejecutando a la vez, existirán dos iconos en la barra de tareas que va a enroscar hacia arriba. No solamente hace el juego pero cada formulario puede ser activado independientemente causando resultados indeseados. Como sabemos una Barra de tareas, a través de las propiedades de un formulario no mostrará su icono en la barra de tareas. Esto es lo que hace el siguiente trozo de código. Convierte el formulario hijo VFP en una barra de tareas removiendo su icono de la barra de tareas.
* ocultar esta ventana desde la barra de tareas
DECLARE SetWindowLong In Win32Api Integer hWnd, Integer style, Integer flags
DECLARE GetWindowLong In Win32Api Integer hwnd, Integer style
DECLARE INTEGER ShowWindow IN user32 INTEGER hwnd,INTEGER nCmdShow 
WS_EX_TOOLWINDOW = 128
SW_HIDE = 0
SW_SHOW = 5
GWL_EXSTYLE = -20
hWnd = ThisForm.hWnd
ShowWindow(hWnd, SW_HIDE)
bb = GetWindowLong(hWnd, GWL_EXSTYLE)
SetWindowLong(hWnd, GWL_EXSTYLE,WS_EX_TOOLWINDOW)
ShowWindow(hWnd, SW_SHOW)
Luego, el título del formulario hijo se hace igual que el formulario principal. Una vez que tengo una instancia, puedo referenciarla fácilmente.
* el mismo título que el padre
Thisform.oChild.lblFormCaption.Caption = This.lblMain.Caption
Finalmente está un método que configura esta ventana detrás del formulario principal. Mírelo en el código para que vea cómo se hace.
* Ordenar las ventanas
Thisform.reorderWindows()
Entonces, para resumir, la primera sección de código llama el método readskindefinition() que es método de usuario que lee en la definición de diseño creada antes y corta zonas del formulario. La segunda sección hace el formulario transparente, si lo necesita. Entonces, abre una ventana hija, configura su título y lo convierte en una ventana estilo Barra de tareas.

Aquí está el código para colocarlo exactamente donde está el otro formulario al configurar sus propiedades Top y Left.

El evento Unload tiene el siguiente código:
IF VARTYPE(This.oMainform) = "O"
  This.oMainForm.Release
ENDIF
*This.oChild.omainform = .null.
This.oMainForm = .NULL.
Es muy importante liberar cualquier instancia de sí misma en mainform pasado antes o el formulario no se cerrará. Mainform tiene además un código similar para liberar la instancia de sí mismo en el formulario hijo.

Queda solamente otro método, para explicar en el formulario hijo, y que está en el método MoveMe():
LPARAMETERS lnDifferenceX,lnDifferenceY
THISFORM.Move(THISFORM.LEFT + m.lnDifferenceX, THISFORM.TOP + ;
  m.lnDifferenceY, Thisform.Width, Thisform.Height)
RETURN
Mainform tiene un código que le permite ser movido. En este código hay una llamada a este método pasando los píxeles que hay que mover. Esto tiene el efecto de mover este formulario donde quiera que sea movido el mainform.

Otro punto. La imagen de la barra de título del formulario hijo tiene código en su evento MouseEnter para activar el mainForm. Esto permite que se activen las ventanas utilizando controles de formulario.

Regresando al formulario principal:

En el evento Resize del formulario mainform.
* Controlar aquí la apertura y el minimizado
IF This.WindowState = 1
  This.oChild.Visible = .F. 
ELSE
  IF This.WindowState = 0
    This.oChild.Visible = .T. 
  ENDIF
ENDIF
Este código va a realizar las acciones de Minimizar / Restaurar en cuanto esta acción ocurra en el formulario principal.

En el evento Unload() libero esta instancia guardada en el formulario hijo y cierro el formulario hijo con este código:
IF VARTYPE(This.oChild) = "O"
  This.oChild.Release
ENDIF
*This.oChild.omainform = .null.
This.oChild = .NULL.
Mainform tiene uno o más métodos que son llamados para colocarle encima de todos y colocar su formulario hijo siguiente en el orden.
*Posición de la ventana traída lo más haciaadelante
DECLARE INTEGER SetWindowPos IN user32;
  INTEGER hwnd, INTEGER hWndInsertAfter,;
  INTEGER x, INTEGER y, INTEGER cx, INTEGER cy,;
  INTEGER wFlags
SWP_NOSIZE = 1
SWP_NOMOVE = 2
SWP_NOSENDCHANGING = 1024
SWP_NOOWNERZORDER = 512
SWP_NOACTIVATE = 16
SWP_NOREDRAW = 8
HWND_TOP = 0
HWND_BOTTOM = 1
HWND_NOTOPMOST = -2
HWND_TOPMOST = -1
* configurar el hijo
swpStyle = SWP_NOSIZE+SWP_NOMOVE+SWP_NOSENDCHANGING+SWP_NOOWNERZORDER+SWP_NOACTIVATE+SWP_NOREDRAW
bb = SetWindowPos(This.oChild.hWnd,HWND_TOPMOST,0,0,0,0,swpStyle)
* configurar esta ventana
swpStyle = SWP_NOSIZE + SWP_NOMOVE + SWP_NOSENDCHANGING
bb = SetWindowPos(This.hWnd,HWND_TOPMOST,0,0,0,0,swpStyle)
Como puede ver, la mayor parte de este código es genérico y puede ser fácilmente colocado en sus formularios respectivos y guardar los formularios como clases. Una vez que esto está hecho es sencillo subclasear los formularios según las necesidades y utilizarlos luego. Ya lo tenemos casi todo hecho. Una vez que está el código en su lugar, ejecute mainform. Va a abrir un formulario hijo y la posición será exactamente debajo dando una apariencia de formulario medio transparente.

He aquí algunas pantallas de cómo se ve en mi escritorio. Vea que los bordes y una parte de arriba son transparentes, mientras el resto es opaco.

¡¡¡ Tenemos transparencia !!!



He aquí más ventanas



Un único icono en la barra de tareas



He aquí otro diseño



y un fondo diferente para el formulario principal.



He aquí algunas advertencias:
  1. El formulario no puede ser redimensionado; pero en mi experiencia, esto no es algo que con frecuencia se hace en muchos formularios.
  2. No he sido capaz de forzar la ventana al fondo cuando el foco va a otra aplicación. Por lo que el formulario está "Siempre arriba". Si alguien puede corregir esto, por favor que me envíe la solución.
  3. Como tenemos 2 formularios involucrados, los eventos Activate / Deactivate de los formularios se disparan cuando activamos unos y otros. No es gran problema, a menos que depende de ello.
Sólo tiene su imaginación como los colores que pueden ser utilizados por la parte transparente así como por el diseño del formulario. Se puede variar el diseño y como antes utilizar un bitmap como fondo del formulario para lograr diferentes efectos. Utilizando esta técnica, algunos efectos se pueden alcanzar y una vez que los formularios se conviertan en clases, será muy fácil implementar e incorporar en su framework.

Espero que ha sido una experiencia agradable para usted tanto como ha sido para mí desarrollar esta técnica. Puede descargar el código completo así como el artículo del enlace que está debajo.

Ahora siga adelante y muestre sus transparencias :)

Adjunto: 2765_vfpaero.zip

No hay comentarios. :

Publicar un comentario