Inicio
 
Creación de un componente paso a paso. IV

Cambios de color.

Vamos a hacer que nuestro componente una vez en el form (con lo que pasará a ser un control) cambie de color cuando se sitúe sobre él el ratón o cuando tenga el foco, es decir, tendremos tres estados posibles: Normal, Enfocado y con el Cursor del ratón sobre el control. Definiremos 2 colores por cada estado uno para el borde y otro para el fondo del control, es decir tendremos que guardar en el componente 6 colores para lograr lo que queremos hacer. Las propiedades vamos a llamarlas : Color, BorderColor para el estado normal, FocusedColor, FocusedBorderColor cuando tiene el foco y OverColor, OverBorderColor cuando tiene situado el ratón sobre él.

Es decir deberemos tener 6 variables que guarden estos valores de propiedades. Por otra parte cuando cambie el valor de estas propiedades (por ejemplo en el inspector de objetos cambiamos cualquiera de ellos), se deberá reflejar de forma automática en la ventana, como ya hemos dicho en múltiples ocasiones para que esto ocurra deberemos escribir un procedimiento SetPropiedad(Value) para cada propiedad. Pero en este caso observamos una cosa, por una parte queremos mantener todas las propiedades de color mencionadas anteriormente pero por otra parte vemos que el tratamiento para cada una de ellas es el mismo :

Procedure SetPropiedad(Value:TColor)
begin
   if value<>propiedad then
   begin
      propiedad:=value;
      invalidate;
   end;
end;

¿Qué hacer para no duplicar código? La solución es crear un array de tipo TColor para guardar los 6 colores, y asignar a cada una de las filas del array una de las propiedades. Por otra parte podemos crear, a mayores, otra propiedad denominada Colors que será el reflejo del array.

  private
     FColors:array[0..5] of TColor;
     ...
     ...
     procedure SetColors(Index:Integer;Value:TColor);
function GetColors(Index:integer):TColor; ... ... public ... property Colors[Index:Integer]:TColor read GetColors Write SetColors; plublished ... property Color:TColor Index 0 read GetColors Write SetColors default clBtnFace; property BorderColor:TColor Index 1 read GetColors Write SetColors default clBlack; property FocusedColor:TColor Index 2 read GetColors Write SetColors default clBtnHighlight; property FocusedBorderColor:TColor Index 3 read GetColors Write SetColors default clBlack; property OverColor:TColor Index 4 read GetColors Write SetColors default clBtnShadow; property OverBorderColor:TColor Index 5 read GetColors Write SetColors default clBlack; ...

En el código anterior hay varias cosas que observar:

1.- Guardamos los colores dentro de la variable FColors.
2.- La propiedad Colors la situamos en el apartado Public y no en el Published. Esto se debe a que el inspector de objetos no permite arrays como propiedades.
3.- Asignamos un índice a cada color. De esta manera nos ahorramos procedimientos distintos para el mismo tratamiento de los datos.
4.- No sólo escribimos un procedimiento SetPropiedad, sino que además debemos escribir una función que nos devuelva el color. Si los procedimientos que escriben se les pasa como parámetro un valor del tipo de la variable, la función debe devolver un valor del mismo tipo que la variable. En este caso en el que utilizamos un array para distintas propiedades, además del parámetro normal, se le pasa un índice que es el que aparece en la definición de la propiedad:

constructor TSelPanel.Create(AOwner:TComponent);
begin
... ... FColors[0]:= clBtnFace; FColors[1]:=clBlack; FColors[2]:=clBtnHighlight; FColors[3]:=clBlack; FColors[4]:= clBtnShadow; FColors[5]:=clBlack; ... ... end; procedure TSelPanel.SetColors(Index:Integer;Value:TColor); begin if FColors[Index]<>Value then begin FColors[Index]:=Value; Invalidate; end; end; Function TSelPanel.GetColors(Index:Integer):TColor; begin Result:=FColors[Index]; end;
Como veis el tratamiento no hace referencia más que al array y a través del índice asociado a cada propiedad se sabe que color estamos cambiando o leyendo.


Figura 4

Una vez hecho esto ya podemos hacer que cambie el color en los casos antedichos, para ello modificaremos el método Paint para que dibuje con unos colores u otros dependiendo de la propiedad Focused y de la variable FOver, que indicarán si el control posee el foco y/o está el cursor del ratón sobre él. Si se da el caso en el que el control tiene el foco y además está el ratón sobre él, ya es decisión nuestra que debe hacer el método Paint, yo he elegido este orden de preferencia de menor a mayor : Normal -> Focused ->FOver, lo que implica que en el caso en el que el control tenga el foco y además tenga el ratón sobre él , el control aparecerá con los colores asignados a Over????Color.

El código completo del componente hasta ahora es :

unit PanelSel;

interface

uses
  Windows, Messages, SysUtils, Classes, Controls, Graphics;

type
  TPanelSel = class(TCustomControl)
  private
    FColors:array[0..5] of TColor;
    FBorder:Boolean;
    FBorderWidth:Integer;
    FOver:Boolean;
    procedure SetColors(Index:Integer;Value:TColor);
    function GetColors(Index:integer):TColor;
    procedure SetBorder(Value:Boolean);
    procedure SetBorderWidth(Value:integer);
    { Private declarations }
  protected
    procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
    procedure WMKillFocus(var Message: TWMSetFocus); message WM_KILLFOCUS;
    procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
    procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
    procedure Paint; override;
    procedure Click;override;
    { Protected declarations }
  public
    constructor Create(AOwner:TComponent);override;
    property Colors[Index:Integer]:TColor read GetColors Write SetColors;
    { Public declarations }
  published
     property Border:Boolean read FBorder Write SetBorder default True;
     property BorderWidth:integer read FBorderWidth Write SetBorderWidth default 1;
     property Color:TColor Index 0 read GetColors Write SetColors default clBtnFace;
     property BorderColor:TColor Index 1 read GetColors Write SetColors default clBlack;
     property FocusedColor:TColor Index 2 read GetColors Write SetColors default clBtnHighlight;
     property FocusedBorderColor:TColor Index 3 read GetColors Write SetColors default clBlack;
     property OverColor:TColor Index 4 read GetColors Write SetColors default clBtnShadow;
     property OverBorderColor:TColor Index 5 read GetColors Write SetColors default clBlack;
     property Font;
     property Tabstop;
    { Published declarations }
  end;

procedure Register;

implementation
constructor TPanelSel.Create(AOwner:TComponent);
begin
   inherited;
   FOver:=False;
   Tabstop:=True;
   FBorder:=True;
   FBorderWidth:=1;
   FColors[0]:= clBtnFace;
   FColors[1]:=clBlack;
   FColors[2]:=clBtnHighlight;
   FColors[3]:=clBlack;
   FColors[4]:= clBtnShadow;
   FColors[5]:=clBlack;
end;
procedure TPanelSel.WMSetFocus(var Message: TWMSetFocus);
begin
   inherited;
   Invalidate;
end;

procedure TPanelSel.WMKillFocus(var Message: TWMSetFocus);
begin
   inherited;
   Invalidate;
end;

procedure TPanelSel.CMMouseEnter(var Message: TMessage);
begin
  inherited;
  FOver:=True;
  Invalidate;
end;

procedure TPanelSel.CMMouseLeave(var Message: TMessage);
begin
  inherited;
  FOver:=False;
  Invalidate;
end;

procedure TPanelSel.SetBorder(Value:Boolean);
begin
   if FBorder<>Value then
   begin
      FBorder:=Value;
      Invalidate;
   end;
end;

procedure TPanelSel.SetBorderWidth(Value:integer);
begin
   if FBorderWidth<>Value then
   begin
      if Value>0 then
         FBorderWidth:=Value;
      Invalidate;
   end;
end;
procedure TPanelSel.SetColors(Index:Integer;Value:TColor);
begin
   if FColors[Index]<>Value then
   begin
      FColors[Index]:=Value;
      Invalidate;
   end;
end;
Function TPanelSel.GetColors(Index:Integer):TColor;
begin
   Result:=FColors[Index];
end;

procedure  TPanelSel.Click;
begin
   inherited;
   SetFocus;   
end;
procedure TPanelSel.Paint;
var
   X, Y, W, H: Integer;
begin
   with Canvas do
   begin
      setbkmode(Handle,TRANSPARENT);
      Pen.Width:=BorderWidth;
      Pen.Color:=BorderColor;
      Brush.Style:=bsSolid;
      Brush.Color:=Color;
      X := Pen.Width div 2;
      Y := X;
      W := Width - Pen.Width + 1;
      H := Height - Pen.Width + 1;
      if Focused then
      begin
         Pen.Color:=FocusedBorderColor;
         Brush.Color:=FocusedColor;
      end;
      if FOver then
      begin
         Pen.Color:=OverBorderColor;
         Brush.Color:=OverColor;
      end;
      FillRect(ClientRect);
      Brush.Style:=bsClear;
      if focused then TextOut(0,0,'FOCUS');
      if Border then Rectangle(X, Y, X + W, Y + H);
      if FOver then TextOut(0,TextHeight('FOCUS')+2,'OVER');
   end;
end;
procedure Register;
begin
  RegisterComponents('Ejemplo', [TPanelSel]);
end;

end.

En el próximo artículo veremos como guardar en el componente una imagen y como dibujarla, así como la escritura de texto dentro del componente.