ˇRecomienda esta página a tus amigos!
 
Inicio
 
Creación de un componente paso a paso. III

Foco.

En este apartado vamos a ver como conocemos cuando nuestro control recibe el foco de la aplicación (Focus) y como debemos reaccionar ante la consecución o pérdida del mismo en cuanto a su resultado en pantalla.

Uno de los ancestros de nuestro componente, en concreto TControl, posee un método denominado Focused que nos indica si el control posee el foco, pero nosotros necesitamos que se nos avise en el momento de conseguirlo o perderlo para no estar constantemente preguntando a este método. En este punto entran en juego los mensajes que se producen a consecuencia de ciertos eventos. Los mensajes que nos servirán para saber cuando debemos actuar son : WM_SETFOCUS, WM_KILLFOCUS que nos dicen cuando adquirimos y perdemos el foco. Debemos capturar estos mensajes para ello definimos los siguientes procedimientos en la parte protegida (Protected) de nuestro componente :

  protected
    procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
    procedure WMKillFocus(var Message: TWMSetFocus); message WM_KILLFOCUS;
	   ...

El control en el momento que reciba cualquiera de los mensajes, reaccionará con la ejecución de estos procedimientos. Ahora, ¿qué es lo que debemos hacer nosotros ante la llegada de estos mensajes?, pues simplemente invalidar el control para que se vuelva a dibujar en la pantalla y dentro del método Paint preguntaremos a Focused para saber si poseemos el foco o acabamos de perderlo. En caso de que no hubiéramos tenido este método, podríamos haber definido una variable FFocused que pondríamos a true en el procedimiento WMSetFocus y a false en WMKillFocus.

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

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

En el método paint de momento para comprobar que esto que hemos hecho funciona, haremos que escriba Focus dentro del control cuando este tenga el foco de la aplicación

procedure TPanelSel.Paint;
var
   X, Y, W, H: Integer;
begin
   with Canvas do
   begin
      setbkmode(Handle,TRANSPARENT);
      Pen.Width:=BorderWidth;
      Pen.Color:=BorderColor;
      Brush.Color:=Color;
      Brush.Style:=bsSolid;
      X := Pen.Width div 2;
      Y := X;
      W := Width - Pen.Width + 1;
      H := Height - Pen.Width + 1;
      FillRect(ClientRect);
      Brush.Style:=bsClear;
      if focused then TextOut(0,0,'FOCUS');
      if Border then Rectangle(X, Y, X + W, Y + H);
   end;
end;

Compruebe el resultado creando un form e incluyendo varios controles de este componente. Ejecute. Verá que mediante las teclas del cursor y la de tabulación consigue cambiar el foco. Pero ¿qué ocurre con el ratón que no responde?. en ningún momento le hemos indicado que debe hacer cuando se pulsa con el ratón sobre el control. De momento en nuestro caso sólo queremos que el control sobre el que se pulsa obtenga el foco.

Para hacer esto, podemos o capturar los mensajes del ratón o podemos sobreescribir un método de uno de los ancestros : Click (como podéis ver la herencia nos ahorra mucho trabajo), el código de este método va a ser una llamada para que ejecute el código del ancestro y después asignaremos el foco al control :

  protected
    procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
    procedure WMKillFocus(var Message: TWMSetFocus); message WM_KILLFOCUS;
    procedure Paint; override;
    procedure Click;override;
	   ...
	   ...
procedure  TPanelSel.Click;
begin
   inherited;
   SetFocus;   
end;

Vuelva a ejecutar el programa y verá que el foco cambia también cuando se pulsa con el ratón.

Controlando al ratón

Entrada y salida.

Hasta ahora el único control del ratón que tiene nuestro componente es la respuesta a la pulsación del mismo. Como recordaréis lo que nos habíamos propuesto era que el control cambiara de color según tuviera el cursor del ratón encima o no.

Al igual que con el foco, su cambio provoca una cadena de mensajes, cualquier movimiento del ratón provoca también una serie de mensajes. A nosotros nos interesa capturar aquellos que nos informen sobre si el cursor del ratón se encuentra sobre el control o no, estos mensajes son : CM_MOUSEENTER, CM_MOUSEENTER, como es fácil ver uno nos avisará sobre la entrada del cursor en el control y otro sobre la salida.

  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;
	   ...

Estos mensajes nos avisan, pero el método Paint al ejecutarse, no sabe si el ratón está o no sobre el control, por esto deberemos guardar en algún sitio estas situaciones, por lo cual vamos a crear una variable/atributo que llamaremos FOver (en la parte privada de nuestro componente) que se pondrá a true cuanto se ejecute CMMouseEnter y a false cuando se ejecute CMMouseLeave. En el constructor Create la iniciamos a false.

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

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

en el método paint, para comprobar que funcionan estas modificaciones escribiremos Over cuando se encuentre el ratón sobre el control.

Hasta el momento el código del componente es el siguiente :

unit PanelSel;

interface

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

type
  TPanelSel = class(TCustomControl)
  private
    FBorder:Boolean;
    FBorderWidth:Integer;
    FColor:TColor;
    FBorderColor:TColor;
    FOver:Boolean;
    procedure SetBorder(Value:Boolean);
    procedure SetBorderWidth(Value:integer);
    procedure SetColor(Value:TColor);
    procedure SetBorderColor(Value:TColor);
    { 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;
    { Public declarations }
  published
     property Border:Boolean read FBorder Write SetBorder default True;
     property BorderWidth:integer read FBorderWidth Write SetBorderWidth default 1;
     property Color:TColor read FColor Write SetColor default clBtnFace;
     property BorderColor:TColor read FBorderColor Write SetBorderColor default clBlack;
     property Tabstop;
    { Published declarations }
  end;

procedure Register;

implementation
constructor TPanelSel.Create(AOwner:TComponent);
begin
   inherited;
   FOver:=False;
   Tabstop:=True;
   FBorder:=True;
   FBorderWidth:=1;
   FColor:=clBtnFace;
   FBorderColor:=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.SetColor(Value:TColor);
begin
   if FColor<>Value then
   begin
      FColor:=Value;
      Invalidate;
   end;
end;
procedure TPanelSel.SetBorderColor(Value:TColor);
begin
   if FBorderColor<>Value then
   begin
      FBorderColor:=Value;
      Invalidate;
   end;
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.Color:=Color;
      Brush.Style:=bsSolid;
      X := Pen.Width div 2;
      Y := X;
      W := Width - Pen.Width + 1;
      H := Height - Pen.Width + 1;
      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 haremos que cambien los colores del control cuando tiene el foco y/o el ratón pasa por encima.