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.
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;
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.
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.
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.
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.
|