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