Página principal
Artículos y trucos
Catálogo de productos
Ejemplos y descargas
Mis libros
Cursos de formación
Investigación y desarrollo
Libros recomendados
Mis páginas favoritas
Acerca del autor
 
En colaboración con Amazon
 
Intuitive Sight

Primeros pasos con las Open Tools

Open Tools API es la nueva interfaz para la programación de extensiones de los IDE de Delphi y C++ Builder, que acompaña a las últimas versiones de estos productos. Estas extensiones nunca han sido bien documentadas por Borland pero, gracias a Dios, el excelente libro Hidden Paths of Delphi, de Ray Lischner, es todavía la mejor introducción al tema que conozco. Aunque este libro no trata la nueva OpenTools API, puede encontrar ayuda sobre la misma en la página de Mr. Lischner: www.tempest-sw.com. De hecho, mi primera experiencia con OTAPI comenzó por la lectura de la información de esa página. Este artículo no intenta ser una exposición en profundidad de esta interfaz. Pero, siendo de nivel introductorio, puede ayudar a dar los primeros pasos a quien desee aprovechar la OTAPI para desarrollar expertos sencillos para uso personal.

¿Qué vamos a hacer ahora? Voy a explicar, paso a paso, cómo crear un experto sencillo que añada una opción de menú en el entorno de Delphi, y que permita realizar alguna operación sencilla sobre el formulario activo del proyecto activo (si es que hay alguno).

CREAR UN PACKAGE PARA EL EXPERTO

Limpie el espacio de trabajo del IDE con el comando File|Close all. Abra el Almacén de Objetos (File|New) y en la primera página realice un doble clic sobre Package. Guarde este fichero en un directorio aparte y bautícelo con un nombre sonoro, a su elección.

¿Por qué un package? Es que también se pueden crear expertos que residen en DLLs. Pero es más complicado instalarlos y desinstalarlos. Además, veremos que es mucho más fácil acceder directamente a los recursos del propio IDE si utilizamos un package en vez de una DLL.

Lo siguiente es modificar las opciones del package. Pulse el botón Options del editor del package. La opción que debe cambiar obligatoriamente es Usage options, que debe cambiarse a Designtime only. Es conveniente asignar Description (ojo con los apóstrofos que a veces dan problemas; yo los evito).

Ahora bien, para ahorrarme quebraderos de cabeza con toda la porquería que instalo y desinstalo habitualmente, yo tengo un directorio AddOns que cuelga directamente del directorio raíz de Delphi. Ahí es donde pruebo toda la bazofia que genero. Por lo tanto, para mí es muy importante ir a la página de directorios del diálogo de opciones, y asignar a Output directory la siguiente macro:

$(DELPHI)\AddOns

Si se tratase de un runtime package, también modificaría DCP output directory y Unit output directory.

Regresar

CREAR EL OBJETO EXPERTO Y REGISTRARLO

Ejecute nuevamente File|New y realice un doble clic sobre el objeto Unit. Esto creará una nueva unidad vacía; guárdela con un bonito nombre. Tendrá que ir nuevamente al editor del package para añadir la nueva unidad al proyecto. Utilice para esto el botón Add, y seleccione el fichero que contiene la unidad. Dentro de la unidad que acabamos de crear, definiremos el objeto que representará al experto a los ojos de Delphi. Lo haremos en la sección interface de la unidad:

type
  TimExpertTemplate = class(TInterfacedObject, IOTAWizard, IOTANotifier)
    procedure AfterSave;
    procedure BeforeSave;
    procedure Destroyed;
    procedure Modified;
    function GetIDString: string;
    function GetName: string;
    function GetState: TWizardState;
    procedure Execute;
    constructor Create;
    destructor Destroy; override;
  end;

En la cláusula uses de la interfaz debemos añadir, obligatoriamente, la unidad ToolsAPI, que define las interfaces IOTAWizard y IOTANotifier. No voy a entrar en detalles de cómo se implementa una interfaz, ni qué es TInterfacedObject. Hay material suficiente para un libro sobre estos asuntos. El nombre que se la da a la clase no tiene importancia, pero trate por todos los medios que sea poco probable una colisión de nombres: en definitiva, esta clase va a integrarse con las clases de la VCL y del propio entorno de desarrollo de Delphi. Aquí he utilizado mi prefijo registrado: im.

Hay que darle un cuerpo a los métodos anteriormente definidos. Mostraré solamente cómo implementar el conjunto mínimo requerido:

procedure TimExpertTemplate.AfterSave;
begin
end;

procedure TimExpertTemplate.BeforeSave;
begin
end;

constructor TimExpertTemplate.Create;
begin
  imExpertModule := TimExpertModule.Create(nil);
  // Ver más adelante
end;

destructor TimExpertTemplate.Destroy;
begin
  imExpertModule.Free;
  // Ver más adelante
end;

procedure TimExpertTemplate.Destroyed;
begin
end;

procedure TimExpertTemplate.Execute;
begin
end;

function TimExpertTemplate.GetIDString: string;
begin
  Result := 'Su empresa.Nombre del experto';
end;

function TimExpertTemplate.GetName: string;
begin
  Result := 'El nombre descriptivo de su experto';
end;

function TimExpertTemplate.GetState: TWizardState;
begin
  Result := [wsEnabled];
end;

procedure TimExpertTemplate.Modified;
begin
end;

Como ve, la mayor parte de los métodos tienen implementaciones triviales. Pero me he adelantado un poco al implementar el constructor y el destructor. En esos procedimientos estoy creando y destruyendo un objeto de la clase TimExpertModule. Bien, esta clase corresponderá al módulo de datos que crearemos en el siguiente paso. Es decir, cada vez que nuestro experto se instale, creará una instancia del módulo que definiremos en breve.

Falta aún registrar el objeto como un experto para que Delphi pueda utilizarlo. En la sección interface tenemos que declarar el siguiente método:

procedure Register;

La implementación será así de sencilla:

procedure Register;
begin
  RegisterPackageWizard(TimExpertTemplate.Create as IOTAWizard);
end;
Regresar

AÑADIR UN MODULO DE DATOS

Es el momento de añadir el módulo de datos que antes mencionábamos. Vaya a File|New, y realice un doble clic en Data module. Guarde la unidad y su DFM con un nombre que pueda posteriormente recordar. Y no olvide regresar a la unidad del paso anterior para incluir la referencia a la nueva unidad. Finalmente, cambie la propiedad Name del módulo a TimExpertModule.

¿Por qué necesitamos un módulo de datos? La respuesta es que nos servirá de contenedor a objetos componentes que necesitaremos añadir comandos de menú, definir imágenes o lo que nos apetezca.

Regresar

CREAR LA ESTRUCTURA DE MENU DEL EXPERTO

¿Cuántos comandos de menú añadirá nuestro experto? Por cada comando que añadamos, necesitaremos una acción. Por lo tanto, traiga un componente TActionList sobre el módulo de datos. Cree una acción por cada comando de menú que vaya a definir. No se preocupe de las propiedades ImageIndex (luego veremos por qué) ni Hint (me parece que Delphi no la utilizará). Tenga mucho cuidado con la propiedad ShortCut, pues puede provocar un conflicto de teclado con las opciones nativas de Delphi. Lo mismo le digo respecto a utilizar una letra subrayada en Caption. Para mi ejemplo, definiremos una sola acción, cuyo nombre será ac_im_ArrangeLabels, y su Caption será "Organizar etiquetas".

Lo siguiente es ir a la página Standard y añadir un TPopupMenu al módulo. Es ahí donde debemos crear los menu items que añadiremos al IDE. Si lo desea, puede utilizar separadores, submenús o el truco que le de la gana. Para este ejemplo, crearé solamente dos elementos de menú: el primero será un vulgar separador (Caption = '-'). El segundo comando debe ir inmediatamente debajo del separador, y debemos asociarlo a la única acción que hemos creado. A este elemento lo llamaremos mi_im_ArrangeLabels.

Detalle importante: ¿quiere utilizar un dibujo en su opción de menú? Cree el dibujo en un fichero BMP, a 16 colores y con 16x16 píxeles de área. Utilice la propiedad Bitmap del menu item que acabamos de crear (mi_im_ArrangeLabels) para cargar la imagen.

Cuando el experto se instale, debemos añadir estos comandos a la estructura de menú de Delphi.

procedure TimExpertModule.imExpertModuleCreate(Sender: TObject);
var
  I, InsertPosition: Integer;
  DataMenu, Item: TMenuItem;
begin
  with BorlandIDEServices as INTAServices do
  begin
    DataMenu := MainMenu.Items[7];  // ¡¡Cuente con cuidado!
    ac_im_ArrangeLabels.ImageIndex := 
      AddMasked(mi_im_ArrangeLabels.Bitmap, clSilver);
  end;
  InsertPosition := DataMenu.Count;
  for I := PopupMenu1.Items.Count - 1 downto 0 do
  begin
    Item := PopupMenu1.Items[I];
    PopupMenu1.Items.Delete(I);
    DataMenu.Insert(InsertPosition, Item);
  end;
end;

¿De que manga me he sacado el número 7 anterior? Mi interés es añadir el menú al final del submenú Database de Delphi. Y este submenú es el octavo elemento de la barra. Observe cómo podemos manipular directamente, sin necesidad de proxies, los propios objetos internos del IDE de Delphi. Observe además como añado una imagen a la lista global de imágenes de Delphi (AddMasked) y le asigno la posición en la que es añadida a la propiedad ImageIndex de la acción.

¿Por qué utilizo el ImageIndex de la acción y no me conformo con la propiedad Bitmap del propio comando de menú? Muy sencillo: si me limito a lo último, el bitmap no aparecerá desactivado cuando la acción desactive al comando por no ser aplicable.

Regresar

FUNCIONES AUXILIARES QUE UTILIZAN LA OTAPI

Voy a definir una función sencilla que devuelva el puntero directo al formulario activo del proyecto activo, y que retorne el puntero vacóo nil cuando no exista tal objeto:

function CurrentForm: TCustomForm;
var
  CurrMod: IOTAModule;
  FormEdt: IOTAFormEditor;
  CurrCom: INTAComponent;
  Comp: TComponent;
  I: Integer;
begin
  Result := nil;
  CurrMod := (BorlandIDEServices as IOTAModuleServices).CurrentModule;
  if CurrMod <> nil then
    for I := 0 to CurrMod.GetModuleFileCount - 1 do
      if CurrMod.GetModuleFileEditor(I).QueryInterface(IOTAFormEditor,
          FormEdt) = S_OK then
        if FormEdt.GetRootComponent <> nil then
          if FormEdt.GetRootComponent.QueryInterface(INTAComponent,
            CurrCom) = S_OK then
          begin
            Comp := CurrCom.GetComponent;
            if Comp is TCustomForm then Result := TCustomForm(Comp);
            Exit;
          end;
end;

Si el lector conoce algo del modelo COM, se extrañará de que esté utilizando QueryInterface para averiguar información sobre interfaces, en vez de utilizar los operadores de alto nivel de Delphi is y as. La explicación es que con QueryInterface mato dos pájaros de un tiro: sé si el objeto dado soporta la interfaz indicada y además obtengo el puntero a dicha interfaz. En caso contrario, tendría primero que llamar a is, para evitar una excepción, y luego a as. Esta función puede modificarse muy fácilmente si lo que necesitamos saber es cuál es el proyecto activo.

Regresar

LAS ACCIONES DEL MENU

Una vez definida la función anterior, podemos indicar muy fácilmente cuándo es aplicable la acción que ejecuta nuestro experto. Debemos interceptar el evento OnUpdate de la acción:

procedure TimExpertModule.ac_im_ArrangeLabelsUpdate(Sender: TObject);
begin
  TAction(Sender).Enabled := CurrentForm <> nil;
end;

Igual de fácil es ejecutar la acción:

procedure TimExpertModule.ac_im_ArrangeLabelsExecute(Sender: TObject);
begin
  OrganizarEtiquetas(CurrentForm);
end;

OrganizarEtiquetas es la función que contiene el meollo del experto. Este es el código de ejemplo:

function CanFormat(C: TControl): Boolean;
begin
  Result := (C is TCustomEdit) and not (C is TCustomMemo)
    or (C is TCustomComboBox)
    or (C is TDBLookupComboBox);
end;

procedure OrganizarEtiquetas(AForm: TCustomForm);
var
  I: Integer;
  ALabel: TLabel;
begin
  for I := 0 to AForm.ComponentCount - 1 do
    if AForm.Components[I] is TLabel then
    begin
      ALabel := TLabel(AForm.Components[I]);
      if (ALabel.FocusControl <> nil)
        and CanFormat(ALabel.FocusControl) then
        begin
          if not ALabel.AutoSize then ALabel.Alignment := taLeftJustify;
          ALabel.Left := ALabel.FocusControl.Left;
          ALabel.Top := ALabel.FocusControl.Top - ALabel.Height - 3;
        end
    end;
end;

Como podemos ver, OrganizarEtiquetas se limita a cambiar la posición de los componentes TLabel de un formulario que están asociados a cuadros de edición y combos, de modo que aparezcan por encima del control al que están enlazadas. Por supuesto, se trata de un algoritmo muy sencillo, pero constituye un buen punto de partida para que usted pueda montarse asistentes más sofisticados.

Regresar