Bordes.
Borde y tamaño
Con lo hecho hasta ahora hemos conseguido que nuestro control sea un
rectángulo en el form. Lo siguiente que vamos a hacer es conseguir
quitar o poner bordes a nuestra voluntad así como un ancho para
esos bordes. Para ello deberemos guardar en algún sitio dentro
del componente si queremos borde y el tamaño de éste, esto
lo vamos a conseguir añadiendo dos variables al componente y dos
propiedades que son las que van a modificar esas variables:
TPanelSel = class(TCustomControl)
private
FBorder:Boolean;
FBorderWidth:Integer;
{ Private declarations }
protected
...
Como podéis comprobar las variables/atributos que manejará
el componente se definen en la parte privada del mismo, esto permitirá
que sólo el propio componente pueda modificar sus valores. ¿Quién
se encarga de modificar estos valores? El valor de estas variables se
modificarán o a través de las propiedades definidas en el
componente o mediante métodos o funciones.
...
published
property Border:Boolean read FBorder Write FBorder default True;
property BorderWidth:integer read FBorderWidth Write FBorderWidth default 1;
{ Published declarations }
...
Observad, en el código anterior, que la propiedad Border lee su
valor de FBorder y escribe valores en FBorder, lo mismo ocurre con la
propiedad BorderWidth y la variable FBorderWidth.
Si compilamos el paquete y después selecionamos nuestro componente
de la paleta ejemplo y dibujamos un rectángulo en un form vemos
que en pantalla aparece el mismo rectángulo, pero que en el inspector
de objetos aparecen dos nuevas propiedades, las definidas por nosotros.
Figura 3
Pero pasa algo, si os fijáis en la definición de las propiedades
habíamos puesto que por defecto el borde deberá ser true
y el ancho 1, pero vemos que las propiedades son false y 0 y a pesar de
ser 0 el valor del ancho del borde, el control se ha dibujado con borde.
La cláusula default de la definición de las propiedades
no implica que éstas se inicien a ese valor, sino que está
indicando que cuando se guarde la definición del componente en
un fichero (.dfm por ejemplo) no guarde aquellas propiedades que tengan
"su valor por defecto", ya que éste debería asignarse
en la creación del componente, lo que implica que deberemos
asignarles nosotros esos valores por defecto. ¿Dónde hacemos
esto? Pues en el Constructor del componente. Todo componente tiene
un método Create que es el encargado de crear el objeto.
En nuestro caso, como no habíamos definido ninguno, el componente
ha utilizado el de su ancestro (TCustomControl), pero ahora deberemos
sobreescribir este método Create para asignar valores a las variables
del componente que queramos tengan un valor inicial :
...
public
constructor Create(AOwner:TComponent);override;
...
...
implementation
constructor TPanelSel.Create(AOwner:TComponent);
begin
inherited; // Hace que se llame al método Create del ancestro (TCustomControl)
FBorder:=True;
FBorderWidth:=1;
end;
Si compilamos el paquete y dibujamos un nuevo control veremos en el inspector
de objetos que ahora sí que tienen el valor inicial que deben tener, pero
si cambiamos estos valores en el form no se ve reflejado nada y el control
sigue apareciendo con borde de tamaño 1. Como es lógico
no se hace nada porque no se le ha dicho qué tiene que hacer cuando
cambie el valor de estas propiedades, para conseguir lo que queremos debemos
realizar unos cambios en la definición de las propiedades :
published
property Border:Boolean read FBorder Write SetBorder default True;
property BorderWidth:integer read FBorderWidth Write SetBorderWidth default 1;
{ Published declarations }
Ahora las propiedades leerán el valor de las variables directamente,
pero a la hora de cambiar el valor de éstas deberán utilizar
dos procedimientos, estos los definiremos en la parte privada del componente
:
private
FBorder:Boolean;
FBorderWidth:Integer;
procedure SetBorder(Value:Boolean);
procedure SetBorderWidth(Value:integer);
{ Private declarations }
Estos procedimientos tienen como parámetro un valor que es del
mismo tipo que la variable que pretenden modificar. Su implementación
es :
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;
Hay varias cosas comunes en ambos procedimientos, por una parte comprobar
que las variables no contienen ya el mismo valor que el que se quiere
cambiar, asignar el valor e invalidar (invalidate) el control,
invalidar el control es lo que va a indicar que el control debe redibujarse.
Por otra parte en estos procedimientos podemos controlar los valores
que permitimos para las propiedades/variables del mismo, en el caso del
Borde sólo permitimos valores mayores que 0.
Además de esto deberemos decirle que es lo que debe dibujar dependiendo
del valor de estas propiedades, para ello cambiamos el procedimiento Paint
visto en el capítulo anterior :
procedure TPanelSel.Paint;
begin
Canvas.Pen.Width:=BorderWidth;
if Border then
Canvas.Rectangle(ClientRect)
else
Canvas.FillRect(ClientRect);
end;
El lienzo (Canvas) contiene dos objetos fundamentales para el dibujo
: Pen y Brush, pluma y brocha. En el procedimiento anterior cambiamos
el tamaño de la pluma al tamaño que nos indica la propiedad
del componente y después dibujamos o no el borde (Rectangle y FillRect).
Podemos comprobar al cambiar los valores de las propiedades Border y
BorderWidth que el control cambia en el form, pero hay un pequeño
detalle, dependiendo del valor que se le ponga al tamaño del borde
este se dibuja mal. Esto es debido a que el canvas sólo puede dibujar
dentro del área cliente (ClientRect) del control, entonces ajusta
el centro del ancho del pluma (Pen) con el borde del control, con lo que
sólo dibuja la mitad del grosor de la pluma. Para solucionar esto
definimos el método Paint de la siguiente forma:
procedure TPanelSel.Paint;
var
X, Y, W, H: Integer;
begin
with Canvas do
begin
Pen.Width:=BorderWidth;
X := Pen.Width div 2;
Y := X;
W := Width - Pen.Width + 1;
H := Height - Pen.Width + 1;
FillRect(ClientRect);
if Border then Rectangle(X, Y, X + W, Y + H);
end;
end;
O sea, que desplazamos los bordes hacia dentro del control dependiendo
del tamaño de la pluma (Pen).
Color del borde y del interior.
Para cambiar el color del borde y del interior deberemos definir variables
nuevas y propiedades para modificarlas. Siguiendo los pasos anteriores
tenemos el código completo del componente, después de los
cambios-en azul-, sería :
unit PanelSel;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Graphics;
type
TPanelSel = class(TCustomControl)
private
FBorder:Boolean;
FBorderWidth:Integer;
FColor:TColor;
FBorderColor:TColor;
procedure SetBorder(Value:Boolean);
procedure SetBorderWidth(Value:integer);
procedure SetColor(Value:TColor);
procedure SetBorderColor(Value:TColor);
{ Private declarations }
protected
procedure Paint; 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;
{ Published declarations }
end;
procedure Register;
implementation
constructor TPanelSel.Create(AOwner:TComponent);
begin
inherited;
FBorder:=True;
FBorderWidth:=1;
FColor:=clBtnFace;
FBorderColor:=clBlack;
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.Paint;
var
X, Y, W, H: Integer;
begin
with Canvas do
begin
// Pluma
Pen.Width:=BorderWidth;
Pen.Color:=BorderColor;
// Brocha
Brush.Color:=Color;
Brush.Style:=bsSolid; //Relleno Sólido
X := Pen.Width div 2;
Y := X;
W := Width - Pen.Width + 1;
H := Height - Pen.Width + 1;
FillRect(ClientRect);
setbkmode(Handle,TRANSPARENT);
Brush.Style:=bsClear; //Relleno Sólido
if Border then Rectangle(X, Y, X + W, Y + H);
end;
end;
procedure Register;
begin
RegisterComponents('Ejemplo', [TPanelSel]);
end;
end.
La llamada setbkmode(Handle,TRANSPARENT),
hace que determinadas funciones de dibujo no 'dibujen' el relleno como
es el caso de Rectangle, es decir, que sea transparente (siempre y cuando
el estilo de la brocha no sea Sólido).
|