Levende Componenten

Theo Hupkens

De meeste componenten die je op een "Form" plaatst komen pas tot leven zodra het bijbehorende programma tot leven komt. "Leven" wil zeggen bewegen, veranderen, geluid maken enzovoorts. Maar Delphi verplicht je niet de componenten dood te houden tot de applicatie loopt! Je kunt ze gerust laten leven zodra ze op een Form geplaatst zijn. Als je bang bent dat de componenten teveel beslag zullen leggen op de computer dan kun je overwegen een extra optie in het snelmenu ('context menu') op te nemen, waarmee de component tijdelijk tot leven kan worden gewekt. Ik zal verderop een component beschrijven die dat zo doet.

 

TLichtkrant

Eerst zal ik een lichtkrant-component beschrijven. Het ligt voor de hand deze component af te leiden van TStaticText of van TLabel. Ik geef de voorkeur aan de laatste, want dan kan transparante tekst gebruikt worden. De naam van de component moet bij voorkeur met een (extra) T beginnen, dan kan Delphi deze naam zonder die T gebruiken bij hints en dergelijke (zie afbeelding).

Er is een property TijdPerStap die de snelheid regelt en een property Richting die aangeeft of de tekst van links naar rechts loopt of omgekeerd. Voor deze properties moet een variabele worden gedeclareerd onder private (zie listing). We hebben nog een Timer nodig waarmee we de snelheid van de lichtkrant kunnen regelen. Deze moet eveneens onder private worden gedeclareerd, want hij mag niet rechtstreeks door de gebruiker worden veranderd. TTimer is gedeclareerd in ExtCtrls en deze unit moet dus worden toegevoegd aan de lijst met units. Zodra TijdPerStap wordt veranderd moet de Timer worden aangepast. Dit gaat met de procedure SetTijdPerStap(Value: integer). Als een gebruiker van de component de waarde van een property wil veranderen dan kan hij dat niet doen via de "private" variabelen. Makers van componenten moeten binnen de functies en procedures van een component hiervoor juist wel de private variabelen gebruiken, anders is de kans op recursie en dus stack overflow erg groot. De Timer moet gecreëerd worden in de constructor. Hij moet eigendom blijven van de component, maar toch mag beslist niet TTimer.Create(AOwner) gebruikt worden (want AOwner is niet de component, maar de owner van de component). We kunnen wel TTimer.Create(nil) gebruiken. De Timer moet na creëren meteen uitgezet worden. In de constructor moet elke property de waarde krijgen die als de default waarde is opgegeven, tenzij deze waarde binair overeenkomt met 0. Ik neem aan dat de werking van TLichtkrant wel duidelijk zal worden uit de listing.

TGolfTekst

De volgende component, een golvende tekst, is veel ingewikkelder. We kunnen deze component het best afleiden van TGraphicsObject. Er is een timer die bepaalt hoe vaak de tekst opnieuw geschreven wordt en er kan nog worden opgegeven hoeveel de verandering per keer bedraagt. Daarnaast is er een golflengte property, die de afstand (in letters) tussen twee toppen van de golf aangeeft. De property Autosize bepaalt of het raam zich aanpast aan de grootte van de tekst of niet. Als AutoSize en Transparent false zijn dan komt de tekst in een gekleurde rechthoek. Via een property Alignment kan de tekst dan in het midden gecentreerd worden. Omdat elke letter afzonderlijk wordt neergezet kunnen sommige letters (vooral bij schuin schrift) gedeeltelijk over elkaar heen vallen. Om deze reden heb ik nog een extra property gemaakt waarmee extra ruimte tussen de letters kan worden opgegeven. Bijkomend voordeel is dat bewegende tekst daardoor beter leesbaar gemaakt kan worden. Er is nog iets op te merken over de waarde van de font-property Height: Height kan positief of negatief zijn, als hij negatief is dan geeft hij niet de volledige lettergrootte. We hebben de volledige lettergrootte nodig, hiervoor kunnen we de Windows functie GetTextMetrics gebruiken. Daar in deze component elke letter toch afzonderlijk moet worden getekend, kunnen we net zo goed voor elke letter een andere kleur nemen. Hiertoe heb ik een property opgenomen waarmee de kleuren kunnen worden ingesteld. Via een andere property kan worden opgegeven hoeveel van de kleuren feitelijk gebruikt worden, voordat het patroon zich gaat herhalen. Ik heb me voor deze component beperkt tot vijf kleuren, maar u kunt dit zonder problemen uitbreiden. Als er voor nul kleuren gekozen wordt, dan wordt niet kleur1 maar de kleur van het font gebruikt, dit is bij monochrome tekst logischer. Je ziet hoe mooi dit alles in Delphi kan worden gedaan: de kleuren zijn properties van de klasse TKleuren en deze klasse wordt gebruikt als type van een property van de klasse TGolfTekst. Op deze wijze ontstaat automatisch een uitklapbaar submenu in de Object Inspector. Bovendien kunnen we, zonder dat we verder iets hebben geprogrammeerd, de kleuren kiezen via de kleurenkiezer van Windows. Aan deze component kan nog veel worden toegevoegd en verbeterd. Zo kun je de golfbeweging gemakkelijk veranderen in speelsere bewegingen.

Als je wil dat de hier boven beschreven componenten al in de ontwerpfase tot leven komen, dan kun je de Timer aanlaten nadat de timer gecreëerd is: If not (csDesigning in ComponentState) then Timer.Enabled := false; (De timer wordt nu alleen uitgezet als de component niet in de ontwerpfase wordt gecreëerd.) Hier zit wel het risico aan dat er al iets gedaan wordt voordat de component bestaat, daarom kunt je dit beter doen in de procedure Loaded (dit valt verder buiten het bestek van dit artikel, zie voor meer informatie de Delphi documentatie).

TFilm

Nu wil ik kort ingaan op twee aspecten van een andere levende component: TFilm. Eerst zal ik beschrijven hoe items worden toegevoegd aan het snelmenu dat je krijgt als je met rechts op een geplaatste component klikt en vervolgens zal ik ingaan op een alternatieve manier om de waarde van een property te kunnen opgeven.

Het toevoegen van een extra menu-item is betrekkelijk eenvoudig. Hiertoe moet een klasse worden afgeleid van een reeds bestaande component editor (bijvoorbeeld TComponentEditor). Vervolgens moeten de functies GetVerbCount, GetVerb en ExecuteVerb worden 'overridden'. GetVerbCount moet het aantal nieuwe menu-items teruggeven, dus Result := 4 (aangenomen dat vier nieuwe items worden toegevoegd). De parameter Index van GetVerb en ExecuteVerb correspondeert met het volgnummer van de nieuwe items, te beginnen met de waarde 0. GetVerb moet de tekst teruggeven, die bij dat item moet worden gezet. In ExecuteVerb moet gespecificeerd worden welke actie moet worden uitgevoerd. Nu doet zich het volgende probleem voor: de actie die bij zo'n menu-item hoort zal bijna altijd iets doen met de betreffende component, maar welke variabele daarbij hoort is pas bekend nadat de component op het "Form" is geplaatst. We kunnen dit oplossen door de vaste naam "Component" te gebruiken. Omdat de klasse niet kan weten wat het type van deze variable is, moeten we deze expliciet converteren, in dit geval naar TFilm, dus With TFilm(Component) do . . .

Een component editor moet altijd worden geregistreerd: RegisterComponentEditor(TFilm, TFilmEditor).

De manier waarop properties tijdens de ontwerpfase hun waarde kunnen krijgen wordt bepaald door een property editor. De property editor moet worden afgeleid van een van de default editors, bijvoorbeeld

TGeluidProperty = class(TStringProperty). Er moeten twee methoden worden overridden: GetAttributes en Edit; GetAttributes geeft terug wat voor soort editor we gaan gebruiken. In dit geval wil ik een geluidsbestand kunnen uitkiezen via een dialoogvenster, dus Result := [paDialog]. De procedure Edit doet het echte werk. Hierin wordt de dialoog geopend, waarin alle geluidsbestanden te zien zijn. Het resultaat (in dit geval de naam van het bestand) wordt doorgeven via de procedure SetValue, maar alleen als de gebruiker op Ok heeft geklikt dus:

If GeluidsDialoog.Execute then SetValue(GeluidsDialoog.FileName);

De klasse TGeluidProperty moet worden geregistreerd. Hierbij kan men nog opgeven of de property Editor alleen voor een bepaalde property geldt of voor alle properties van dat type. In dit geval hoeft hij alleen gebruikt te worden voor de property met de naam Geluid, dus volstaat

RegisterPropertyEditor(TypeInfo(AnsiString), TFilm, 'Geluid', TGeluidProperty);

Omdat het type van deze property een string is, wordt de tijdens het ontwerpen opgegeven filenaam automatisch bewaard in de executable en hoeven we verder niets te doen. In programma's die TFilm gebruiken worden alle bestandsnamen bewaard, maar niet de bij die namen horende bestanden. Als zo'n programma wordt verspreid, dan moeten die bestanden dus worden meegegeven en dat programma moet ze kunnen vinden. Een voor de hand liggende uitbreiding van TFilm is een mechanisme waarmee de geluids- en beeldbestanden wel worden opgeslagen in programma's die TFilm gebruiken. Het niet opslaan heeft overigens ook voordelen, zo blijven de programma's klein en hanteerbaar en kunnen bestanden gedeeld worden door meerdere programma's.

TKlok

Tenslotte iets over de component TKlok.

Ik heb deze component gebaseerd op een gewijzigde versie van TShape. Deze gewijzigde versie maakt het mogelijk voor- en achtergrond vrij te kiezen en bovendien zijn er extra vormen mogelijk. Nadeel van deze constructie is wel dat vaak onnodig de hele vorm opnieuw wordt getekend, bijvoorbeeld een maal per seconde als de secondewijzer zichtbaar is.

Alle hier beschreven componenten kun je vrij gebruiken en veranderen. De componenten zijn echter vooral bedoeld om ideeën te geven, en ze zijn niet uitgebreid getest. Ik geef geen enkele garantie dat ze onder alle omstandigheden zullen werken zoals bedoeld. Het gebruik van deze componenten is dan ook voor je eigen rekening.

Om de demo te kunnen runnen moet je misschien via Options/Directories/Conditionals het pad naar de directory van de componenten opgeven.