Código en Delphi 6, válido para Delphi for win32 y CodeGear RAD Studio (Delphi win32) | ||
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.
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.