nov 102011
 

Buenas,

Haciendo unos componentes (que a la que estén algo visibles publicaré), me encontré con la necesidad de tener que hacer algo como tienen los TStatusBar con sus paneles o los TDBGrid con sus columnas, es decir, quería tener una propiedad en que pudiera crear objetos del mismo tipo.

Pregunté en mi foro preferido, delphiaccess al respecto y el buen amigo Héctor me abrió los ojos. La solución a mis problemas era la clase TCollection. Marteens explica muy bien su uso en este trick, así que aquí sólo voy a dar 2 pinceladas.

La clase TCollection

Una característica importante de la clase es que deriva de TPersistent, con lo que no está oblidaga a tener un propietario o owner. Por esta razón, cuando derivamos de TCollection tenemos la obligación de redefinir el método GetOwner que en TPersistent devuelve un simple nil y no está redefinido en TCollection.

Una alternativa al problema anterior es derivar de TOwnedCollection, la cual ya se encarga de redefinir ese método (de hecho es lo único que hace).

A parte de este detalle, no se necesita hacer nada más en la clase que derivemos de TCollection (si no lo necesitamos, claro).

Personalizando TCollection

Los objetos que «guarda» TCollection son o tienen que derivar de TCollectionItem. A su vez, TCollection tiene una propiedad Items para devolvernos un elemento de los que contiene. No obstante, no tiene propiedad por defecto y, claro está, Items devuelve un TCollectionItem.

Estos dos detalles hacen o obligan al programador a tener que estar referenciando la propiedad Items cada vez y a tener que usar la conversión de tipos contínuamente, por lo que es aconsejable ocultar la antigua propiedad Items. Sería algo así:

  TMyCollection = class(TCollection)
  private
    function GetItems(I: Integer): TMarker;
    procedure SetItems(I: Integer; const Value: TMarker);
  public
    property Items[I: Integer]: TMarker read GetItems write SetItems; default;
  end;

implementation

function TMyCollection.GetItems(I: Integer): TMarker;
begin
  Result := TMyCollectionItem(inherited Items[I]);
end;

procedure TMyCollection.SetItems(I: Integer; const Value: TMarker);
begin
  inherited SetItem(I, Value);
end;

Con este sencillo código facilitaremos en mucho el trabajo al programador que use nuestro componente.

La clase TCollectionItem

Cómo he comentado más arriba, los objetos que almacena TCollection tienen que ser o derivar de la clase TCollectionItem. En ésta le pondremos las propiedades, métodos y eventos que necesite nuestro objeto y que podrán ser retocados en tiempo de diseño (en el caso de las propiedades published).

Cuando se hereda de esta clase, suele ser buena costumbre añadir a la nueva clase alguna propiedad de tipo string (Name, Caption, Title,… decirla cómo queráis) que nos sirva para ayudar al inspector de objetos a mostrar un nombre coherente o con sentido a simple vista. Para ello tendremos que redefinir el método GetDisplayName haciendo algo así:

  TMyCollectionItem = class(TCollectionItem)
  private
    FTitle: string;
  protected
    function GetDisplayName: String; override;
  published
    property Title: string read FTitle write FTitle;
  end;

implementation

function TMyCollectionItem.GetDisplayName: String;
begin
  if Length(Title) > 0 then
  begin
    if Length(Title) > 15 then Result := Copy(Title, 0, 12) + '...'
    else Result := Title
  end
  else
    Result := inherited GetDisplayName;
end;

Y la última cosa obligada a la que estamos es redefinir el método Assign para que Delphi sepa qué hacer en caso de asignación entre dos objetos del mismo tipo. Algo así:

  procedure TMyCollectionItem.Assign(Source: TPersistent);
begin
  if Source is TMyCollectionItem then
  begin
    Title := TMyCollectionItem(Source).Title;
    //..... el resto de propiedades
  end
  else
    inherited Assign(Source);
end;

Bueno, espero que os sirva esta breve explicación de la clase TCollection y TCollectionItem

Nos leemos

 

nov 022011
 

Buenas,

Haciendo unas aplicaciones que usan el componente TWebBrowser, me surgió la necesidad de interactuar con las páginas cargadas en él, en concreto el manejo de los formularios. Consultando a Google llegué a delphi.about.com donde encontré una serie de artículos que me ayudaron a solucionar mis necesidades.

Gracias a estos artículos he creado una clase (TWebControl) que engloba buena parte de lo expuesto en esos artículos.

Entre las funciones interesantes que tiene esta clase tenemos:

  • procedure WebLinks: se encarga de carturar todas las etiquetas «a href» que contenga la página y las pone en la propiedad Links.
  • procedure WebFormNames: se encarga de capturar todos los formularios que contenga la página y los almacena en la propiedad Forms.
  • procedure WebFormFields: dado el nombre de un formulario, se encarga de rellenar la propiedad Fields con los campos del mismo.
  • procedure WebFormSetFieldValue: dado un formulario y un campo del mismo, establece un valor a dicho campo.
  • procedure WebFormSubmit: dado un formulario, lo acepta.
  • function WebFormFieldValue: dado un formulario y un campo del mismo, devuelve el valor que contiene.
  • function WebHTMLCode: devuelve el código HTML de la página web.
  • function WebContent: devuelve el contenido de la página web.
  • procedure WebSaveAsHTML: dado un nombre de archivo, guarda la página web.
  • procedure WebLoadHTML: dado un código HTML, lo carga directamente en el TWebBrowser.
  • procedure WebAddHTML: dado un código HTML, lo añade a la página existente.
  • procedure WebPrintWithoutDialog: imprime la página web sin mostrar ningún dialogo.
  • procedure WebPrintWithDialog: lanza el diálogo de impresión.
  • procedure WebPrintPreview: realiza una previsualización de la impresión de la página web.
  • procedure WebPrintPageSetup: realiza una llamada al diálogo del setup.

La forma de funcionar de la clase es sencilla. Basta con crear un objeto de la misma pasándole en el constructor el objeto TWebBrowser que contenga la página a controlar.

Como siempre, podéis descargaros el programa demo y la unit con la clase desde aquí.

Nos leemos