Delphi tools: Hide non-visual components

Erik Stok (aka Baldo)

In het vorige artikel uit de serie “Delphi tools” heb ik een voorbeeld gegeven van het gebruik van een expert om het menu in de Delphi IDE gemakkelijk uit te kunnen breiden met eigen opties. In dit artikel ga ik gebruik maken van de expert om een nieuwe optie in het menu te maken: Hide non-visual components.

Het idee

De applicaties die ik over het algemeen bouw zijn GUI applicaties. Hierbij zijn forms gevuld met allerlei visual controls. Hoewel het uitlijnen heel gemakkelijk gaat (zie het artikel over de align component editor), wil ik af en toe toch nog wat aan de lay-out van het form wijzigen. En dan liggen die non-visual components me toch steeds in de weg.

Het moet toch mogelijk zijn om die even te verbergen zodat ik aan de slag kan met de visual components. En als ik dan klaar ben dan moet ik ze toch ook weer eenvoudig kunnen tonen.

Een component editor op het form maken is niet echt de oplossing, want zodra het form compleet gevuld is, bijvoorbeeld met panels, dan kan ik niet bij de component editor. Een property editor is ook geen oplossing want het betreft hier geen property. Zo kwam ik terecht bij de in het vorige artikel beschreven expert om een nieuw menu item toe te voegen.

Grenzen van de OTA

Als eerste ben ik eens op zoek gegaan naar wat de Delphi open tools api (OTA) allemaal aanbiedt om bij de components op het form te komen. Ik wil eigenlijk een referentie hebben naar het form dat designtime wordt weergegeven. Natuurlijk kan ik in een designtime package gebruik maken van de application variabele en van daaruit een zoektocht starten naar de form designer, maar de open tools api biedt vast wat hulp.

Het blijkt dat via het IOTAModuleServices interface van de BorlandIDEServices een referentie naar de huidige openstaande module kan worden verkregen. De eerste winst is al geboekt: ik weet de openstaande module. Nu de form designer daarvan te pakken zien te krijgen.

Na wat speurwerk kwam ik er achter dat een module meerdere files kan beslaan. Als ik een nieuwe applicatie aanmaak in Delphi en daarin zit de module unit1, dan bestaat deze module uit unit1.pas, unit1.dfm en unit1.ddp. Via het IOTAModule interface is het mogelijk om een referentie naar de editor van een specifieke file van een module te krijgen. Ik ben alleen geïnteresseerd in de form editor, dus als ik zou controleren of een editor het IOTAFormEditor interface implementeerd, dan weet ik dat ik de form editor van de module te pakken heb. Dit alles resulteert dus in de volgende code:

function GetFormEditor: IOTAFormEditor;
var
Module : IOTAModule;
Editor : IOTAEditor;
FormEditor: IOTAFormEditor;
i : Integer;
begin
// Standaard resultaat
FormEditor := nil;
// Bepaal de huidige module
Module := (BorlandIDEServices as IOTAModuleServices).CurrentModule;
// Als die gevonden is, zoek dan naar de editors ervan
if Assigned(Module) then
begin
// Loop door de module files
for i := 0 to Module.GetModuleFileCount - 1 do
begin
// Probeer een referentie naar de module file editor te krijgen
Editor := Module.GetModuleFileEditor(i);
// Als referentie niet gevonden kan worden, laat dan maar zitten
if not Assigned(Editor) then
Break;
// Anders, controleer of het ook daadwerkelijk een form editor is.
if (Editor.QueryInterface(IOTAFormEditor, FormEditor) = S_OK) then
Break;
end;
end;
// Geef resultaat terug en laat alle interface references los
Result := FormEditor;
Module := nil;
Editor := nil;
FormEditor := nil;
end;

Een referentie naar de form editor is een stap dichter bij het uiteindelijke doel: de non-visual components van dit form benaderen. Mijn eerste gedacht was om voor alle components van het form te controleren of het een non-visual component betrof, en zo ja de visible property daarvan op false te zetten. Toen ik een stuk geprogrammeerd had zag ik wat ik verkeerd aan het doen was. Een TDataSource, een voorbeeld van een non-visual component, heeft helemaal geen visible property. Ik moest niet het component hebben, maar het blokje dat de form designer gebruikt om het component visueel te representeren! En daar biedt de open tools api geen faciliteiten voor.

Good old windows

Door wat te zoeken op het web vond ik de oplossing (met dank aan google). De visuele representatie van een non-visual component heeft een window handle. Tevens is de classname van dit window TContainer. Met deze kennis is het mogelijk om door alle window handles te lopen waar het form owner van is en te controleren of het een non-visual component is.

Het enige wat dan nog moet gebeuren is het zetten van ‘visible’. En daar heeft de windows api een functie voor genaamd ShowWindow. Door aan ShowWindow een handle en een boolean (True is show, False is hide) mee te geven kan de visibility van een window aan en uit gezet worden. Ik gebruikte de functie GetWindow om door de Windows heen te lopen, maar PsychoMark wees me in het forum erop dat de beste functie die je daarvoor kunt gebruiken EnumChildWindows is.

De routine

Al het bovenstaande resulteert dan in de volgende code:

function ToggleNVsCallback(hWnd: HWND; lParam: LPARAM): BOOL; stdcall;
var
cClassName: array[0..255] of Char;
begin
// Bepaal de class van het "window"
FillChar(cClassName, SizeOf(cClassName), #0);
GetClassName(hWnd, @cClassName, SizeOf(cClassName));
// Als het een TContainer is dan is het een non-visual component.
// Toggle deze.
if String(cClassName) = 'TContainer' then begin
if not IsWindowVisible(hWnd) then
ShowWindow(hWnd, SW_SHOW)
else
ShowWindow(hWnd, SW_HIDE); end;
// Geef aan Windows door dat we door willen gaan met het volgende childwindow
Result := True;
end;
procedure ToggleNVs(Form: TCustomForm);
begin
// Vraag alle child window handles aan en voer voor elk van de een procedure uit
EnumChildWindows(Form.Handle, @ToggleNVsCallback, 0);
end;

Al met al een vrij eenvoudige routine die de hele open tools api links laat liggen om zijn doel te bereiken. Gewoon windows api calls zijn voldoende.

Het menu item

De laatste stap is het toevoegen van een menu item waarmee deze nieuwe functionaliteit kan worden gestart. In het vorige artikel kwam naar voren dat een TIdeMenuObject afgeleide maken voldoende is. Zie de unit uIdeMenuNvComponents.pas hoe dat er uiteindelijk in code er uit is komen te zien. Het registreren van het nieuwe item kan worden gedaan in de constructor van de IdeMenuExpert. Natuurlijk moet dan niet worden vergeten om uIdeMenuNvComponents in de uses clause van de IdeMenuExpert op te nemen.

Mocht je zelf geen zin hebben om de IdeMenuExpert aan te passen dan kun je ook het package downloaden waarin ik deze uitbreiding al heb toegevoegd.

Conclusie

Doordat we in het vorige artikel een goede basis hadden gelegd voor het uitbreiden van het Delphi menu, is het een fluitje van een cent gebleken om een eigen feature aan Delphi toe te voegen. En hoewel de open tools api voor het verbergen van non-visual components geen opties biedt, blijkt wederom dat de alledaagse kennis van Delphi en Windows voldoende is om een mooi stuk software neer te zetten.