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