Delphi tools: Project manager

Erik Stok (aka Baldo)

Dynamische applicaties is een van mijn favoriete onderwerpen op NL Delphi. Bij het bouwen van dynamische applicaties wordt vaak gewerkt met project groups. In het vorige deel van Delphi tools heb ik compiler commands besproken die praktisch noodzakelijk zijn om goed te kunnen werken met deze project groups. Deze keer voeg ik nog wat functionaliteit toe aan de project manager om het echt vlot werkbaar te maken.

Het idee

Het komt regelmatig voor dat ik in de project manager moet zijn om te wisselen van project. Ik stap dan echter vervolgens in de editor regelmatig over naar een openstaande unit uit een ander project. Wil ik dan een form of unit selecteren uit het project van de unit die ik in de editor geselecteerd heb, dan ben ik verplicht weer naar de project manager over te schakelen om het overeenkomstige project te selecteren (of het form of de unit hierbinnen). Ik wil dit vanwege RSI preventie met het toetsenbord kunnen doen, maar als je de project manager dockt (wat ik doe voor het overzicht) dan ben je verplicht de muis te gebruiken.

Ook kan ik niet van project wisselen zonder naar de project manager over te schakelen en daarbinnen een ander project te selecteren. Het zou toch wel fijn zijn als ik met een enkele toetsaanslag het vorige of volgende project kon selecteren zonder de editor te verlaten.

En wat bij het bouwen van dynamische applicaties helemaal ideaal zou zijn is dat er een mogelijkheid zou zijn om de shell applicatie (voor meer informatie, zie het artikel over dynamische applicaties) te starten ongeacht welk project actief is.

Tijd dus om die project manager wat beter bedienbaar te maken. De vorige artikelen over de open tools api hebben bij alle technieken besproken die hiervoor noodzakelijk zijn, dus dit artikel is een goede samenvatting van reeds vergaarde kennis.

We gaan de project manager uitbreiden met de volgende opties: “Select project of current unit”, “Select prior project”, “Select next project” en “Run project group executable”.

De OTA

Voor de eerste drie opties kan gebruik gemaakt worden van het IOTAProject interface dat reeds besproken is in het artikel over compiler commands.

Voor de optie “Run project group executable” zal wat meer werk verricht moeten worden. Delphi biedt namelijk vanuit het IOTAProject interface wel faciliteiten om het project te compileren of builden, maar niet om het te starten. Die functionaliteit zit verrassend genoeg in het IOTAEditActions interface verstopt, een interface op de Delphi editor. Maar meer daarover als we de specifieke code van dit onderdeel bespreken.

Select project of current unit

Bij het artikel over compiler commands was al te zien hoe het project van de huidig geselecteerde unit kan worden bepaald. Daarbij dus weinig nieuws. Het enige nieuwe van deze optie is hoe het betreffende project als actief project kan worden geselecteerd. Het IOTAProjectGroup interface kent een ActiveProjectGroup property die gezet kan worden naar een van de projecten uit de project group.

// Als er een project gevonden is, selecteer het dan
if ModuleProjectIndex <> -1 then
begin
// Maak het project van de module het active project
ProjectGroup.ActiveProject := ProjectGroup.Projects[ModuleProjectIndex];
end;

Zoals ook al eerder gezegd is de GetProjectGroup functie vaker opgenomen en is het wellicht handig om een aparte unit met open tools api functies te beginnen waarin al dergelijke functies zijn opgenomen. Om de eenvoud van distributie van de individuele ide menu uitbreidingen te verhogen laat ik dat wederom achterwege.

Select prior project

Ook bij het selecteren van het vorige project kan gebruik gemaakt worden van kennis uit het artikel compiler commands. Het bepalen van het actieve project is reeds uitgezocht, nu is het alleen nog een kwestie van het actief maken van het project ervoor. Dit is terug te vinden in de method TIdeMenuSelectPriorProject.Execute van unit uIdeMenuSelectPriorProject.

// Als er iets gevonden is en het is niet de eerste, selecteer dan
if (CurrentProjectIndex <> -1) and (CurrentProjectIndex <> 0) then
ProjectGroup.ActiveProject := ProjectGroup.Projects[CurrentProjectIndex - 1];

Select next project

Het volgende project selecteren is dan eigenlijk vrijwel hetzelfde, te vinden TIdeMenuSelectNextProject.Execute in van unit uIdeMenuSelectNextProject.

// Als er iets gevonden is en het is niet de laatste, selecteer dan
if (CurrentProjectIndex <> -1) and (CurrentProjectIndex <> (ProjectGroup.ProjectCount - 1)) then ProjectGroup.ActiveProject := ProjectGroup.Projects[CurrentProjectIndex + 1];

Run project executable

Het interessante gedeelte zit in de “Run project executable” optie. Hierbij wordt door alle projecten van de project group heen gelopen om te bepalen of het project een executable is.

Executables onderscheiden zich van packages doordat de project extensie .dpr is in plaats van .dpk. Maar het vervelende is dat dll’s ook de .dpr extensie gebruiken, en dat zijn nu juist niet de projecten die gestart moeten worden.

De oplossing voor dit probleem is te vinden in de project options. Het IOTAProject interface biedt toegang tot de project options van het project. Als de project option met de naam “GenDll” op True staat, dan betekent dat voor Delphi dat het project als dll gecompileerd moet worden. Door hierop te controleren bij het zoeken naar de executable kan worden bepaald welke .dpr de eigenlijke executable is.

// Als het geen dll betreft, dan is het de exe en kan de loop doorbroken worden
if not ProjectGroup.Projects[p].ProjectOptions.Values['GenDll'] then
begin
ExeProjectIndex := p;
Break;
end;

Op deze manier is het dus mogelijk om de eerste executable uit de project group te vinden. Ik ga er van uit dat er in een project group maar één executable is die gestart moet worden (wat bij dynamische applicatie project groups veelal het geval is).

Het starten van de executable is weer een heel ander probleem. Delphi beschouwd de “Run” functionaliteit als een actie op de editor. Om deze functionaliteit te kunnen benaderen zal het IOTAEditActions interface gebruikt moeten worden.

Het IOTAEditActions interface kan echter alleen benaderd worden via het IOTAEditView interface, een interface op de code editor. En een interface op de code editor kan worden vekregen via het IOTAEditorServices interface.

Door aan BorlandIDEServices te vragen of het IOTAEditorServices interface momenteel beschikbaar is (met andere woorden: of de editor beschikbaar is) kan een referentie naar EditorServices verkregen worden. Door vervolgens een referentie naar het interface van het actieve venster in de editor op te vragen kunnen we bij een IOTAEditView interface komen. In de code is dit geïmplementeerd in de GetTopView functie.

function GetTopView: IOTAEditView;
var
EditorServices: IOTAEditorServices;
begin
if Supports(BorlandIDEServices, IOTAEditorServices, EditorServices) then
Result := EditorServices.TopView
else
Result := nil;
end;

Het IOTAEditView interface biedt de method RunProgram door gebruik te maken van het IOTAEditActions interface. RunProgram is echter alleen van toepassing op het actieve project. Het is dus van belang om het project van de executable als actief project te zetten en we hebben zojuist kunnen zien hoe dat moet.

// Als er een project gevonden is, selecteer dit dan als actief project en
// run het project
if ExeProjectIndex <> -1 then
begin
// Maak exe project actief
ProjectGroup.ActiveProject := ProjectGroup.Projects[ExeProjectIndex];
// Run
(TopView as IOTAEditActions).RunProgram;
end;

De menu opties

Om de nieuwe compiler opdrachten beschikbaar te maken wordt opnieuw gebruik gemaakt van de IDE menu uitbreiding al eerder beschreven. En weer geldt dat het toevoegen van de units uIdeMenuSelectUnitProject, uIdeMenuSelectPriorProject, uIdeMenuSelectNextProject, uIdeMenuRunExeProject en het aanmaken van een instance van de overeenkomst classes in de constructor van de expert voldoende is. Natuurlijk is voor de mensen die liever niet zelf de code aanpassen weer een aangepaste versie van het IdeMenu package beschikbaar.

Conslusie

De project manager mist op een aantal punten faciliteiten om er prettig mee te werken, maar wederom bewijst de open tools api haar nut door voldoende mogelijkheden te bieden om zelf oplossingen te maken. De in de vorige artikelen opgedane kennis is zo goed als voldoende om nog een aantal zinvolle toevoegingen op de Delphi IDE te maken. Ik hoop dat iedereen die zich beperkt voelt door de faciliteiten die Delphi standaard biedt inmiddels inziet dat wat ontbreekt op eenvoudige wijze kan worden toegevoegd.