marzo 07, 2006

Alternativas a los skins

Existen muchos y muy variadas herramientas en el mercado que permiten hacer cosas bastante llamativas con las interfaces de los programas, VCLSkin, SuiSkin, entre otros con sus ventajas y desventajas entre ellos

Algunos usan sus propios componentes, es decir su propio TEditSkineado, TPanelEskineado, etc, el problema con eso es que obviamente cuando los usas te obligas a reescribir tu aplicacion o al menos pasarle un experto que cambie los nombres de las clases de los controles que ahora serán del nuevo tipo; y tambien con eso ya te estas jodiendo comprometiendo de alguna manera con ese proveedor de la herramienta, pues algunas características que ofrezcan estos componentes no se encontrarán en las clases genéricas.

Otros como VCLSkin son mas flexibles, con solo poner un simple componente en tu formulario principal te cambia la apariencia de la aplicación por completo, abarcando incluso cuadros de dialogo y demás chunches propios de windows

Aqui vamos a discutir una alternativa al uso de estas herramientas que son de pago (aunque algunas realmente valen lo que cuestan si la apariencia es tan importante para ti), y lo haremos usando una técnica ancestral que nos provee Delphi y a la que se le llama "Clases Sobrepuestas". Es increible cuanto mejora la apariencia de una ventana tan solo usando colores agradables y redondeando las esquinas de los componentes contenedores, aqui va el ejemplo vamos a estudiar como hacer que esto en diseño:

se vea en ejecución como esto:

Sin usar componentes de terceros y sin tener que cambiar los componentes que ahora estas usando en tu aplicación común y corriente de Delphi.

La idea es muy simple, vamos a hacer una unidad que será la que pongas al final de la sección uses de tu forma en la parte de la interfase; ahora, esto es importante, la unidad que contenga las clases sobrepuestas debe ser la última en la lista del uses, sino podria no funcionar el truco, por lo menos no para las clases que esten contenidas en las unidades que se encuentren despues de la nuestra.

Primero creamos una nueva unidad llamada GTPaneles.pas, y definamos algunos parámetros configurables al gusto de cada quien...

const
CnstArco = 35;
CnstBotonNormal = clMoneyGreen;
CnstBotonPress = clGray;
CnstTagDisable = 99;


la constante CnstArco definirá que tan pronunciados queremos que sean los arcos de las esquinas redondeadas, las siguientes dos constantes definen los colores que tendrán los botones en los estados normal y cuando estan presionados, y por último definimos un valor para diferenciar a aquellos componentes a los que no quisieramos que se les aplique el proceso de "embellecimiento", esta técnica la usa VCLSkin, donde si a algun panel no quisieras que se le aplique el Skin solo basta con poner el valor 99 en la propiedad Tag de dicho componente.

Yo suelo utilizar la propiedad Tag de los componentes para algunos trucos al nivel de la interface de las aplicaciones, si ese es tu caso tambien tendrías que hacer algunos malabares para que esto no te ocasione problemas


Para ejemplificar el trabajo lo haremos con la clase TPanel, y mostraremos la parte mas simple del asunto, aqui hacemos la sobreposición de la clase:


TPanel = class(ExtCtrls.TPanel)
protected
procedure Paint; override;
end;


Definamos un par de procedimientos genéricos que usaremos despues en la implementacion de los métodos de las clases sobrepuestas:

Este procedimiento borra el fondo donde se ha de dibujar el control y prepara el terreno para dibujar nuestra obra de arte.

procedure QuitarFondo(Control: TControl; Dest: TCanvas);
var
SaveIndex: Integer;
DC: HDC;
Position: TPoint;
begin
with Control do
begin
if Parent = nil then
Exit;
DC := Dest.Handle;
SaveIndex := SaveDC(DC);
{$IFDEF PDJ_2}
GetViewportOrgEx(DC, @Position);
{$ELSE}
GetViewportOrgEx(DC, Position);
{$ENDIF}
SetViewportOrgEx(DC, Position.X - Left,
Position.Y - Top, nil);
IntersectClipRect(DC, 0, 0, Parent.ClientWidth,
Parent.ClientHeight);
Parent.Perform(WM_ERASEBKGND, Integer(DC), Integer(0));
Parent.Perform(WM_PAINT, Integer(DC), Integer(0));
RestoreDC(DC, SaveIndex);
end;
end;


Este otro dibuja ya formalmente los rectangulos redondeados de modo que de la perspectiva 3D.

procedure DibujaRedondeado(const C: TCanvas; R1:
TRect; Color: TColor);
var R2: TRect;
begin
//Guardamos el Rectangulo original
R2 := R1;
with C do
begin
Pen.Width := 1;
//Dibujamos la sombra
inc(R1.left, Pen.Width);
inc(R1.top, Pen.Width);
pen.Color := clbtnhighlight;
Brush.Style := bsSolid;
Brush.Color := Color;
RoundRect(R1.Left, r1.top, r1.Right, r1.Bottom,
CnstArco, CnstArco);

pen.Color := color;
Brush.Style := bsclear;
inc(R1.left, Pen.Width);
inc(R1.top, Pen.Width);
RoundRect(R1.Left, r1.top, r1.Right, r1.Bottom,
CnstArco, CnstArco);

//Dibujamos la base...
pen.Color := clBlack;
Brush.Style := bsclear;
r1 := R2; //Recuperamos el rectangulo original...
RoundRect(R1.Left, r1.top, r1.Right, r1.Bottom,
CnstArco, CnstArco);
Brush.Style := bsSolid;
end;
end;


Despues de eso solo basta con codificar la nueva versión del método Paint del componente mandando a llamar a los dos métodos que ya vimos:

procedure TPanel.Paint;
begin
if (Tag <> CnstTagDisable) then
begin
QuitarFondo(Self, Canvas);
DibujaRedondeado(Canvas, GetClientRect, Color);
end else inherited;
end;


Para el caso de los botones y los TGroupBoxes el procedimiento es similar con la variante de desplegar el texto o el caption del componente en cuestión, e incluso se puede hacer una variante que dibuje tambien los Glyphs de los botones, pero yo por lo menos mi versión la he dejado hasta ahi; si alguien continua con este desarrollo hagamelo saber. También sobre BDS 2006 se pueden hacer uso de los nuevos eventos OnMouseEnter y OnMouseLeave para dar más efectos a los componentes.

En la sección de archivos de este blog esta disponible para descargar un demo completo de lo aqui explicado, seguro que con un poco mas de dedicación se puede ampliar la unidad para que soporte mas clases y con mejor apariencia.

¿cuestion de inspiración??

Hay quienes tienen la idea de que esto de la programación es muchas veces cuestión de "momentos", que existen esas horas en que algún ser divino toca tu conciencia y es cuando vienen los más grandes portentos de creatividad en la resolución de un problema o del diseño de alguna aplicación que traia dando tantas vueltas en la cabeza; vaya que es crear un arte...

Esto que algunos llaman suerte creo que esta más bien orientado a la inspiración... pero tambien esa inspiración pudiera estar conllevada con el ambiente, la situación social, economico, familiar que el susodicho programador haya tenido en su momento. Porque es bien sabido que por mas brillante que suela uno ser, en un momento de aflicción o sobrecogimiento no se rinde igual, ni fluyen las ideas con tanta facilidad como cuando todo en la vida es miel sobre hojuelas.

Seria bueno que se comentara aqui que tanto piensan que influye realmente el entorno en el desempeño de un programador, que tanto influye el tener un buen sueldo, una buena esposa (porque no?), minimos problemas en general para tener un destacable desempeño creativo.

Ahora que si a inspiraciones vamos, yo debo declararme como un fan de la música de trova y en mi opinión es una música de inspiración sublime. Para muestra un botón, aqui está un fragmento de una canción de "El Mago" Abel Velasquez, ya me dirán si esto no es inspiración; son palabras que muchas veces no sabe uno a ciencia cierta como decir, descritas de la manera mas sublime, asi que tomen nota... con dedicación especial para mi chaparra...