Este artículo explica como hacer formas o forumalarios sin bordes ni barra de título y que se puedan mover y cambiar de tamaño

"Post-it". Ventanas ajustables sin borde ni título

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

EurekaLog - Capture y registre cada excepción!

¿Por qué un formulario sin borde ni título?

Cuatro hechos son fundamentales en el presente ensayo: El primero de ellos la existencia de programas que imitan los Post-it (aquellas paginitas adheribles que vienen en libretas e inundan las oficinas con colores vistosos). Segundo, el uso de cierta función indocumentada en Windows para arrastrar formularios. Tercero, el problema de cómo crear una aplicación dónde el formulario principal no es visible. Y cuarto, utilizar la barra de tareas de Windows.

Los programas que imitan a los Post-it que tomo como referencias son fundamentalmente TurboNote en Windows y el del KDE de Linux. Ambos programas son fácilmente adquiribles y hacen más que el que aquí muestro. Sin embargo considero este un buen ejercicio docente.

Se me ocurre otro programa que se presentan sin título: El reloj redondo (analógico) que viene con Power Toys de Windows. Será propuesta para un próximo artículo.

Entremos en calor

Evolutivamente lo obvio es imitar la nota post-it de alguna manera. El primer pensamiento es un formulario con una barra de título sin los botones usuales (minimizar, maximizar, etc) y completamente con un color plano. Esta alternativa es posible y requiere una manejo de Windows algo especializado. En particular he preferido una alternativa más económica en cuanto a código (o al menos eso creo); ella es eliminar la barra de título y bordes del formulario (como se haría con un "splash-screen") y colocar un panel y un memo en un formulario, el primero alineado al tope y el segundo al área cliente.

Manejando un formulario sin borde ni barra de título

El formulario con sólo panel y memo responderá sin inconveniente a las acciones de teclado, pero es obvio que no responderá al arrastre y cambios de tamaño con el ratón (sin borde por dónde agarrarlo). El arrastre se resuelve con una acción indocumentada, al menos así era algún tiempo atrás, a través del mensaje SysCommand. La línea aquí mostrada, ejecutada desde un formulario, coloca a este en estado de arrastre.

Perform(wm_SysCommand, $f012, 0)

El asunto con los mensajes no se limita a esto. Los modos de resize también se definen de esta manera. A tal efecto definimos un conjunto de constantes con los valores adecuados empezando con el $f012.

sc_DragMove = $f012;
sc_Leftsize = $f001;
sc_Rightsize = $f002;
sc_Upsize = $f003;
sc_UpLeftsize = $f004;
sc_UpRightsize = $f005;
sc_Dnsize = $f006;
sc_DnLeftsize = $f007;
sc_DnRightsize = $f008;

Nota: Si usted se pregunta de donde salieron estos valores, bueno, que diré. El primero lo conocí por un Libro de Marco Cantú llamado "Delphi Developers' Handbook", los demás surgen de ensayo y error. Déjeme comentarle así mismo que $f040 produce un resultado desastroso. Además $f020 y $f030 minimizan y maximizan respectivamente, esto último no es necesario para nuestra aplicación. Aunque no estaría de más. No me consta si algo de esto funciona fuera de Windows 98

Adicionalmente en el evento MouseDown del panel y del memo, dónde enviamos los mensajes, es necesario llamar ReleaseCapture antes de enviar los mensajes señalados.

No me extenderé mucho con el código. Es más sencillo analizarlo desde Delphi que desde una página HTLM o DOC. Pero nótese que hay que distinguir dónde se hizo clic en el panel para saber si se requiere un resize o sólo arrastrar el formulario. Algo similar sucede con el memo para el resize de los lados e inferior.

procedure TfrmNota.Panel1MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  ReleaseCapture;
  if y<2 then
   begin
    if x<4 then
      Perform(wm_SysCommand, sc_UpLeftsize, 0)
    else if x>(Panel1.ClientWidth-4) then
      Perform(wm_SysCommand, sc_UpRightsize, 0)
    else
      Perform(wm_SysCommand, sc_UpSize, 0)
   end
  else
   Perform(wm_SysCommand, sc_DragMove, 0)
end;

Los fuentes son partes de este artículo y usted podrá ver detalladamente el funcionamiento de la nota.

En el ejemplo anexo, además del manejo de eventos de ratón se hace algún manejo del registro de Windows para almacenar información relativa a la nota en sí (como su posición, tipo de letra y color). Presumo que si entiende lo de lo que se ha realizado hasta aquí no requiere explicación del uso del registro de Windows (en cualquier caso parece estar bien documentado en Delphi el uso del registro y hay ejemplos e información al respecto en Internet).

Un formulario principal invisible

Cuando hice este programa no había pensado en un problema realmente tonto. El usuario debe poder crear y destruir notas, pero que no destruir el formulario inicial porque eso termina la aplicación. Bien, entonces simplemente con no mostrar el formulario principal debe bastar. Si quiere usted entender este tonto problema cree una aplicación en la que además del formulario principal exista un formulario secundario; luego logre que la aplicación arranque sólo mostrando el formulario secundario. Mis intentos resultaron en fracaso. Ahora sé que eso se logra con

Application.ShowMainForm := False;

en el programa principal antes de crear los formularios, pero en aquel entonces no lo sabía y aproveché el problema para darle una solución original...

No es una falla, es una ventaja

Dándole vuelta al asunto la solución fue convertir la pantalla principal en una pantalla de presentación que dura una cierta cantidad de tiempo y luego se oculta. En un timer se ejecuta este código:

procedure TfrmMain.Timer1Timer(Sender: TObject);
begin
  SaveNotes(Sender);
  if not oculto then
    begin
      oculto := True;
      ShowWindow(Handle, SW_HIDE);
      ShowWindow(Application.Handle, SW_HIDE);
      SetWindowLong( Application.Handle, GWL_EXSTYLE,
        GetWindowLong(Application.Handle, GWL_EXSTYLE)
        or WS_EX_TOOLWINDOW and not WS_EX_APPWINDOW);
      ShowWindow(Application.Handle, SW_SHOW);
    end
end;

Con ello queda oculta la ventana principal (la línea inmediata a la asignación de la variable "oculto", que por cierto por defecto es falsa) y la presencia de la aplicación en la barra de tareas.

El resto del código es relativamente simple y contiene algunas rutinas para salvar y recuperar el contenido y configuración de las notas (lo primero a disco y lo segundo al registro). Creo que no amerita una discusión profunda por ello dejo al lector la interpretación de la rutina.

El proyecto como tal contiene cinco formularios:

  1. note, es la nota con el panel y el memo.
  2. GenConfig, el formulario de configuración de la aplicación, actualmente sólo configura formatos y contador del reloj.
  3. Config, configuración de la nota (color, alineación y fuentes)
  4. Main, es el formulario principal, además de eso funciona como pantalla de presentación.
  5. Acerca, Versión del programa.

Como comentario aparte nótese que debido a la presencia de un Timer es posible obligar un auto save (autoguardar) para el texto en las notas. Esto se logra recorriendo todos los formularios del tipo nota y guardando su contenido desde el memo. La rutina llamada SaveNotes se ejecuta desde el timer (teniendo cuidado de no salvar lo que no ha cambiado). Cada nota tiene la variable pública Changed, la cual es modificada al salvar y en el evento OnChange del memo.

"Pop-up menu"

La aplicación no cuenta con un menú principal porque no tiene un formulario principal. Toda las acciones distintas a la interacción con el campo memo y movimientos usando ratón se realizan por menúes tipo Pop-up. Hay dos: uno en la nota y otro en el ícono de la barra de tarea (junto al reloj). Para éste utilizamos las RX. Así que al menos para esta versión será necesario que usted tenga las RX para poder compilar estos fuentes. Si no las conoce, entonces debe hacerlo, son gratuitas ("freeware") y muy buenas.

RX Library Web Site:
http://www.rxlib.com
Authors:
Fedor Kozhevnikov (fkozh@iname.com)
Igor Pavluk (igorp@mail.com)
Serge Korolev (korolev@usa.net)
 
JfControls Library - para Delphi y C++ Builder