We gaan de klok analoog maken.

In deze aflevering gaan we ons klokje de keus geven om digitaal of analoog te worden weergegeven.

Canvas

Om de klok analoog te maken moeten we kunnen “tekenen”. En tekenen kan in Lazarus (Free Pascal) op een schildersdoek ofwel een Canvas. Veel componenten hebben een Canvas, zo ook het Form zelf. En daar gaan we onze eerste tekeningetjes op maken.

Allereerst moeten we de keuze Analoog gaan inbouwen.

– Ga naar de code editor.
– Voeg de globale variabele Analoog van het type Boolean toe in het implementation-gedeelte (MD staat er al!).
– Voeg een nieuwe menu optie Analog, met Name = popAnalog, toe als tweede item aan popMenu.
– Voeg aan het item Analog een sub menu toe.
– In het submenu moet een item On komen met als Name = popAnalogOn.

Wanneer er op de optie On in het submenu van Analog wordt geklikt, dan moet er een vinkje komen en krijgt de variabele Analoog de waarde True, en anders moet het vinkje worden weggehaald en krijgt de variabele Analoog de waarde False.
Uiteraard moet er, afhankelijk van Analoog, daarna ook nog de juiste klok worden weergegeven.

– Maak de event-handler voor popAnalogOn aan.
– Voeg de volgende regels code toe:
  popAnalogOn.Checked := not popAnalogOn.Checked;
  Analoog := popAnalogOn.Checked;

Omdat de analoge klok straks iedere seconde moet worden ge-update gaan we gebruik maken van een passend event van het form: OnPaint.

– Maak de event-handler OnPaint van het Form aan.
– Voeg de volgende regels code toe (als “raamwerk”):
  if Analoog then
    begin
      // code voor analoge klok
    end
  else
    begin
      // code voor digitale klok
    end;

In de code voor de analoge klok moet in ieder geval de digitale klok worden “uitgezet” en in de code voor de digitale klok moet de digitale klok weer worden “aangezet”. Tevens moet het form ook de juiste afmetingen behouden dan wel krijgen.
Dat aan- en uitzetten is niets meer dan de property Visible de waarde True of False geven.

Om te beginnen gaan we onze analoge klok rond maken en zullen we er vast een wijzer inzetten.

– Implementeer de code voor de analoge klok als volgt:
  lblTijd.Visible := False;
  frmKlokje.Width := 100;
  frmKlokje.Height := 100;
  frmKlokje.Canvas.Ellipse(0,0,100,100);
  frmKlokje.Canvas.Line(50,50,100,50);
– Implementeer de code voor de digitale klok als volgt:
  frmKlokje.Width := lblTijd.Width;
  frmKlokje.Height := lblTijd.Height;
– Voeg tot slot een regel toe (dus als laatste regel) aan popAnalogOnClick:
  frmKlokje.Repaint;

De meeste code zal nu wel voor zich spreken.

Ellipse

U ziet dat met de methode Ellipse op het Canvas van het Form een ellipse (in ons geval een cirkel) kan worden getekend. Ellipse verwacht 4 parameters die twee punten op het Form voorstellen, namelijk de linker bovenhoek en de rechter onderhoek. Het meeste linkse bovenhoek van het form is (0, 0) en het meest rechter onderhoek van de form is (Width, Heigth). In ons voorbeeld willen we de cirkel een zo groot mogelijke straal geven.

Line

Met de methode Line wordt er een lijn getekend van een startpunt naar een eindpunt. In ons voorbeeld is het startpunt (50, 50), het middelpunt van de cirkel, en het eindpunt (100,50). We verwachten dus een horizontale lijn vanuit het midden van de cirkel naar rechts.

Laten we maar eens kijken.

– Start de klok en kies vanuit het menu de optie Analog–>On.
– Kijk of u een cirkel met een lijn ziet.
– Sluit de klok weer af.

Laten we nog eens wat meer lijntjes tekenen.

– Voeg na de Line-regel in FormPaint de volgende regels toe:
  frmKlokje.Canvas.Line(50,50,50,0); // naar boven
  frmKlokje.Canvas.Line(50,50,0,50); // naar links
  frmKlokje.Canvas.Line(50,50,50,100); // naar onder
– Run de klok weer en activeer Analog–>On.
– Sluit daarna de klok weer.

De pen waarmee getekend wordt kan ook worden aangepast qua kleur en dikte.

Lijn- kleur en dikte

We gaan de “wijzers” rood maken en de lijn naar onder ook wat dikker.

– Ga naar het “analoge” gedeelte van FormPaint.
– Voeg na de regel met de ellips de volgende code toe:
  frmKlokje.Canvas.Pen.Color := clRed;
– Voeg voor de laatste Line de volgende code toe:
  frmKlokje.Canvas.Pen.Width := 3;
– Run het klokje (sluit daarna weer af).

Het resultaat is wellicht niet wat u verwacht had:

Naar alle waarschijnlijkheid zal de compiler (die altijd de meest efficiënte code wil maken) eerst de instellingen van de pen uitvoeren en pas daarna de tekening gaan maken. Een “ongelukkigheidje” van de compiler. Het kan natuurlijk ook aan de versie van de compiler (lees de versie van Lazarus) liggen, maar dat weet ik niet zeker.
Het gaat wel goed als we de volgende regels (op de juiste plaats) toevoegen.

– Voeg de volgende regels toe voor de regel met Ellipse:
  frmKlokje.Canvas.Pen.Color := clBlack;
  frmKlokje.Canvas.Pen.Width := 1;
– Run het klokje nogmaals (en weer sluiten, natuurlijk).

Nu is, als het goed is, het resultaat zoals verwacht:

Is u nog iets anders aan de analoge klok opgevallen?
Wellicht twee zaken: De analoge klok is niet verplaatsbaar en de form van de klok is vierkant en dus niet rond.
Het verplaatsbaar maken van de klok is heel simpel en zullen we zo direct gaan doen.
Het rond maken van het form is iets ingewikkelder en zullen we aan het eind van deze aflevering gaan doen.

Verplaatsen analoge klok

– Selecteer in de Object Inspector het Form en ga naar de tab Gebeurtenissen.
– Ga naar OnMouseDown en kies uit de combobox de optie lblTijdMouseDown.
– Ga naar OnMouseMove en kies uit de combobox de optie lblTijdMouseMover.
– Ga naar OnMouseUp en kies uit de combobox de optie lblTijdMouseUp.
– Run de analoge klok en test of deze verplaatsbaar is.

U ziet dat ook event-handlers herbruikt kunnen worden.

Wijzers laten lopen

Om nu wijzers te laten lopen zullen we beginnen met de seconde-wijzer. En ik doe een beroep op uw wiskundige kennis.
Zoals u hopelijk (nog) weet is een cirkel onderverdeeld in 360 graden. De secondewijzer tikt 60 keer per minuut. Dus in 60 stapjes moet de seconde-wijzer 1 keer rond zijn geweest. Dit betekent dat de seconde-wijzer iedere seconde 6 graden (= 360[graden] / 60[seconden per minuut]) moet doorschuiven.
Verder weet u waarschijnlijk ook nog (van de zogenaamde eenheidscirkel) dat de x-coördinaat met de cosinus moet worden berekend en de y-coördinaat met de sinus.
Daar de computer met radialen zal werken moeten deze dus worden omgezet van graden naar radialen.

Eén en ander leidt tot de volgende formules:

x-coördinaat-seconde wijzer = cosinus(seconde * 6)
y-coördinaat-seconde wijzer = sinus(seconde * 6)

En voor Lazarus zal het er ongeveer zo uit gaan zien:

sx := Round(Cos(DegToRad(sec * 6)) * 40);
sy := Round(Sin(DegToRad(sec * 6)) * 40);

waarbij sec het aantal seconden van de huidige tijd is. De functies Sin en Cos verwachten als parameter een radiaal. Met de functie DegToRad zetten we graden om naar radialen. De ‘* 6‘ in de formules komt dus van het feit dat de wijzer iedere seconde 6 graden moet opschuiven. De ‘* 40‘ in de formule heeft te maken met het feit dat de sinus en de cosinus altijd een waarde tussen de -1 en de 1 opleveren en met de Round-functie wordt dit dus -1, 0 of 1, wat een zeer kleine wijzer oplevert. Vandaar een vergrotings-factor van 40.

Laten we e.e.a. maar gaan implementeren.

– Voeg aan de uses-clausule in het implementation-gedeelte de unit Math toe (is nodig voor DegToRad).
– Bij FormPaint: Haal alle code vanaf Ellipse t/m de laatste Line weg.
– Ga naar het event tmrTijdTimer.
– Voeg de volgende variabelen toe (voor de begin):
  var
    h,m,s,ms: Word;
    wx, wy: integer;
– Voeg na de regel frmKlokje.Repaint de volgende regels code toe:
  DecodeTime(Now,h,m,s,ms);
  wx := Round(Cos(DegToRad(s * 6)) * 40) + 50;
  wy := Round(Sin(DegToRad(s * 6)) * 40) + 50;
  frmKlokje.Canvas.Line(50,50,wx,wy);
Run het klokje.

Er loopt een wijzer!
Maar loopt de wijzer wel goed? Het loopt in ieder geval in de goede richting, maar geeft het ook de juiste secondes aan. Dat moeten we even testen.
We gaan er (even) voor zorgen dat de digitale klok ook in beeld blijft.

– In het tmrTijdTimer-event:
– Maak van de regel if… commentaar (zet er // voor).
– Maak van de regel else commentaar.
– Maak van alle interne begin-regels en end-regels commentaar.
– In het FormPaint-event:
– Maak van de regel lblTijd.Visible := False; commentaar.
– Run de klok.

Wat valt op?

De secondewijzer loopt 15 seconden voor!
Dit komt omdat Lazarus de o-graden in het “Noorden” legt, terwijl de wiskunde de 0-graden in het “Oosten” legt. Onze formules zijn wiskundig en moeten dus worden aangepast voor Lazarus. Gelukkig is ook dat niet zo moeilijk.

– Pas in de event tmrTijdTimer de volgende regels aan:
  wx := Round(Cos(DegToRad(s * 6 – 90)) * 40) + 50;
  wy := Round(Sin(DegToRad(s * 6 – 90)) * 40) + 50;
– Run de klok.

En nu loopt de secondewijzer wel goed.

– Haal alle toegevoegde commentaar-tekens (//) weer weg.

Met de procedure DecodeTime wordt uit een TDateTime-waarde (hier de functie Now) de uren, minuten, seconden en milliseconden gehaald.

In de volgende stap zullen we de minuten- en de uren-wijzer gaan tekenen.

– Ga naar het trmTijdTimer-event.
– Voeg de volgende regels toe voor de secondewijzer:
  wx := Round(Cos(DegToRad(m * 6 – 90)) * 30) + 50;
  wy := Round(Sin(DegToRad(m * 6 – 90)) * 30) + 50;
  frmKlokje.Canvas.Line(50,50,wx,wy);

Voor de minutenwijzer geldt hetzelfde als de secondewijzer, alleen is het door de factor 30 iets kleiner.

Tot slot de urenwijzer.

– Ga naar het trmTijdTimer-event.
– Voeg de volgende regels toe voor de minutenwijzer:
  wx := Round(Cos(DegToRad(h * 30 + (m div 6) * 3 – 90)) * 20) + 50;
  wy := Round(Sin(DegToRad(h * 30 + (m div 6) * 3 – 90)) * 20) + 50;
  frmKlokje.Canvas.Line(50,50,wx,wy);

Hier zien we een iets andere formule. Er zitten maar 12 uren in de analoge klok. Per uur betekent dat een sprong van 30 graden. Als we het hierbij zouden laten dan zou de urenwijzer slechts 1 keer per uur van positie veranderen en dat is lastig; dat betekent dat als het 5 voor 2 is de urenwijzer nog steeds op de 1 staat.
De urenwijzer moet dus in een uur van de ene positie “kruipen” naar de volgende positie. En daarvoor is de aanpassing (m div 6) * 3 gebruikt, dat er voor zorgt dat de urenwijzer elke 6 minuten 3 graden opschuift, en aangezien er 10 * 6 minuten in een uur gaan, verloopt de urenwijzer dus precies 30 graden per uur.

– Run de klok en kies voor analoog.

De basis is er. Het is alleen nog niet goed zichtbaar welke wijzer wat is. Kleurtjes en diktes kunnen wellicht de zaken verduidelijken.

– Zet in de code voor iedere wijzer de regels:
  frmKlokje.Canvas.Pen.Color := clBlue;
  frmKlokje.Canvas.Pen.Width := 3;

Uiteraard mag u zelf de kleur en de dikte bepalen. Uw klokje zou er zo kunnen uitzien:

Natuurlijk kunnen we de analoge klok nog verder verfraaien, en dat gaan we ook doen in één van de volgende afleveringen. We hebben nu een goede basis gelegd!

Ronde Form

Tot slot van deze aflevering gaan we de vierkante Form wijzigen in een ronde Form.

Lazarus/Free Pascal kent hiervoor de opdracht SetShape. Als parameter kan een bitmap worden meegegeven en dan krijgt de form de vorm van de bitmap. En dat is wat we gaan doen. We gaan een bitmap maken in de vorm van een cirkel en gaan dan met SetShape het Form “verbouwen” tot een cirkel.

– Ga naar het FormPaint-event.
– Declareer de variabele aBitmap als volgt:
  var
    aBitmap: TBitmap;
– Voeg de volgende regels code toe in het analoog stuk direct na begin:
  aBitmap := TBitmap.Create;
  aBitmap.Width := frmKlokje.Width;
  aBitmap.Height := frmKlokje.Height;
  aBitmap.Canvas.Brush.Color := clWhite;
  aBitmap.Canvas.Ellipse(-1,-1,frmKlokje.Width+1,frmKlokje.Height+1);
  SetShape(aBitmap);
  aBitmap.Free;
– Voeg de volgende regels code toe in het else stuk voor lblTijd.Visible := True:
  aBitmap := TBitmap.Create;
  aBitmap.Width := lblTijd.Width;
  aBitmap.Height := lblTijd.Height;
  aBitmap.Canvas.Brush.Color := clWhite;
  aBitmap.Canvas.Rectangle(0,0,lblTijd.Width,lblTijd.Height);
  SetShape(aBitmap);
  aBitmap.Free;
– Run de klok in analoge stand en merk op dat de hoeken rondom de cirkel zijn verdwenen.

Bij de analoge klok wordt de ellips aan alle kanten van de klok 1 groter gemaakt. Dit is gedaan zodat de getekende ellips er keurig in past.

Er moet nog veel gebeuren, maar dat doen we in een volgende aflevering.

Naar de volgende aflevering…