Lazarus-Klokje-Alarm

Afleveringen

We moeten nog een alarm mogelijkheid inbouwen.

En dat gaan we in deze aflevering doen.

Alarm instellingen

We gaan een alarm-settings-venster maken dat, met de kennis die u nu heeft, niet zo moeilijk zal zijn.
Om het alarm af te laten gaan moeten we ook weer een nieuw venster maken.
Het alarm zal afgaan op een bepaalde tijd en moet dan een geluid geven en en tekst op het scherm tonen.
We zullen een aantal geluidjes gaan inbouwen. En daar zit de uitdaging!

Resources

In een (Windows) executable zit natuurlijk de code die uitgevoerd moet worden. Maar er zit ook data in, zoals de vensters. Deze worden tijdens het compilatie proces in de executable gelinkt. Nu is het ook mogelijk om andere soorten data mee te linken, zoals afbeeldingen, teksten en ook geluiden. Deze “dingen” worden resources genoemd.
En u heeft al (2x) resources meegelinkt! In een ImageList zitten immers afbeeldingen en die worden als resources meegelinkt in de executable.
Helaas is er geen control om geluiden te linken, dus moeten we dit zelf gaan doen. En ook helaas, is hier geen standaard voor. In iedere programmeer-omgeving werkt dit weer anders.

Kijk voor meer informatie over resources op www.wiskunst.nl: Wiskunst–>Delphi–>Resources.

Wanneer u afbeeldingen, zonder gebruik te maken van een ImageList, wilt meelinken, dan kan dat via het menu Project–>Project Opties…–>Resources. Maar helaas kan dat dus niet voor geluiden. Dit moeten we buiten de ontwikkelomgeving om doen. Gelukkig biedt Lazarus wel een tooltje waarmee we het tocht voor elkaar kunnen krijgen.

Lazres

– Maak even een aparte map sounds aan waarin u een 10-tal sounds van het type WAV neerzet (deze kunt u bv. halen uit c:\Windows\Media). Zorg ervoor dat de namen niet te lang zijn; hernoem ze eventueel.
– Zet in de map ook lazres.exe, die u kunt vinden in de map waar Lazarus zelf is geïnstalleerd, onder de map tools.
– Maak, bv. in het kladblok, de batch maak_lrs.bat aan met de volgende regel:
– lazres sounds.lrs alarm01.wav alarm02.wav alarm03.wav alarm04.wav alarm05.wav alarm06.wav alarm07.wav alarm08.wav alarm09.wav alarm10.wav
– (DENK ERAAN DAT HET ÉÉN REGEL MOET ZIJN, DUS ZONDER HARDE REGELEINDES!!!)
– (alarm01.wav t/m alarm10.wav zijn de namen van de soundjes die ik uit Windows\Media heb gekopieerd)
– Bewaar het bestand en voer het daarna uit.

In de map sounds staat nu ook het bestand sounds.lrs. Dit is het resource-bestand dat we straks gaan gebruiken.

– Kopieer dit bestand naar de map waar u al uw units*.pas heeft staan.

We gaan nu het volgende Form toevoegen:

– Voeg een nieuwe Form toe aan het klokje en zet de volgende properties:
– Name = frmAlarmSettings
– BorderStyle = bsDialog
– Position = poScreenCenter
– Plaats een Label met Caption = Choose alarm.
– Plaats een Listbox met Name = lstAlarm.
– Plaats een Button met Name = btnOtherAlarm en Caption = Other alarm.
– Plaat een Label met Name = lblAlarm.
– Plaat een Label met Caption = Set alarm time.
– Plaats een DateTimePicker (laatste knopje op tab Common Controls) en zet de volgende properties:
– NamedtpAlarmTijd
– Kind = dtkTime
– Plaats een Checkbox met Name = chkFixedTime en Caption = Fixed.
– Plaats een Label met Caption = Enter alarm text.
– Plaats een Editbox met Name = edtAlarm en Text = Wake up!!!
– Plaats een Button met Name = btnOK, Caption = OK en Width = 60.
– Plaats een Button met Name = btnCancel, Caption = Cancel en Width = 60.
– Implementeer btnCancelClick met de code:
  close;

Even een menuoptie toevoegen zodat we bij ons Form kunnen komen.

– Ga naar frmKlokje.
– Voeg in popMenu na het item Font het item Open alarm settings met Name = popAlarmSetting.
– Voeg in de Uses-clausule in het interface-gedeelte de unit Unit4 toe.
– Activeer het popAlarmSettingsClick-event en voeg de volgende regel code toe:
  frmAlarmSettings.ShowModal;
– Run het klokje (Bewaar Unit4…) en kijk of het nieuwe venster (te sluiten met Cancel) er een beetje uitziet.
– Sluit het klokje.

Als eerste gaan we onze lstAlarm vullen. Dat doen we in het FormActivate-event. Hierna maken we een begin met het Click-event van lstAlarm.

– Activeer het FormActivate-event.
– Voeg de volgende variabele toe:
  var
    i: integer;
– Voeg na begin de volgende regels code toe:
  lblAlarm.Caption := ‘Alarm01’;
  lstAlarm.Clear;
  for i := 1 to 10 do
    if i < 10 then
      lstAlarm.Items.Add(‘Alarm0’ + IntToStr(i))
    else
      lstAlarm.Items.Add(‘Alarm10’);
  lstAlarm.ItemIndex := 0;
– Activeer het lstAlarmClick-event.
– Voeg de volgende regel code toe:
  lblAlarm.Caption := lstAlarm.Items[lstAlarm.ItemIndex];
– Run het klokje en kijk of e.e.a. werkt.

Wanneer de gebruiker geen gebruik wenst te maken van de ingebouwde geluidjes, dan moet hij/zij de mogelijkheid hebben om een eigen geluidje te laden. Dat doen we middels de knop Other alarm.

– Zet op het Form een OpenDialog-control neer met Name = dlgOpenFile.
– Activeer het event btnOtherAlarmClick.
– Voeg de volgende code toe:
  dlgOpenFile.Filter := ‘WAV|*.WAV’;
  if dlgOpenFile.Execute then
    begin
      lstAlarm.ItemIndex := -1;
      lblAlarm.Caption := dlgOpenFile.FileName;
    end;
– Run het klokje en kies dan een ander geluidje.
– Sluit de klok weer.

Nu komen we bij echte nieuwe zaken.
We gaan de knop Play alarm implementeren. Hiervoor is het nodig dat we de resources met de geluidjes gaan meelinken en dat we een mogelijkheid krijgen om de meegelinkte geluidjes te kunnen afspelen.

Resources linken en geluidjes spelen

Om de resource-file mee te linken moet deze via een zogenaamde compiler-optie worden meegenomen. Dit gebeurt in de initialization-sectie van de unit.
Vervolgens moeten we kunnen verwijzen naar zo’n gelinkt geluidje. Daarvoor hebben we de method LazarusResources.Find nodig die in de unit LResources zit.
Ten slotte gebeurt het afspelen met de method PlaySound die in de unit MMSystem zit.

– Ga in de code (van Unit4) op de regel voor end. staan.
– Tik de volgende regels in:
  initialization
    {$I sounds.lrs}
– Voeg in het implementation onder {$R *.lfm} de volgende regels toe:
  Uses
    Unit1, LResources, MMSystem;
– Activeer het btnPlayClick-event.
– Voeg de volgende variabele toe:
  var
    s: AnsiString;
– Voeg de volgende regels code toe:
  s := LazarusResources.Find(lstAlarm.Items[lstAlarm.ItemIndex]).Value;
  if s <> ” then
    PlaySound(@s[1], 0, SND_ASYNC or SND_MEMORY);
– Run het klokje, selecteer een paar keer een sound en klik op Play sound.

Het werk! Maar we zijn er nog lang niet. Uiteindelijk moet ook tijdens het afgaan van het alarm het geluid klinken. Daarom moeten we de code van btnPlayClick generiek gaan maken in een aparte function. Tevens moet het alarm blijven klinken totdat de gebruiker het alarm afzet. Voor dit afzetten moeten we ook een aparte function maken. Dit gaan we allemaal in Unit4 doen.

Nog even wat uitleg over de code.
Met LazarusResources.Find wordt er gekeken in de gelinkte resources of de opgegeven resource-naam bestaat. Zo ja dan wordt er een pointer naar gezet. De .Value zorgt ervoor dat alleen de naam van de resource wordt teruggegeven.
Deze naam komt in een variabele van het type AnsiString te staan. Dit is een type string met “oneindige” lengte (voor de kenners: zit in het heap-geheugen). De variabele levert een pointer, een geheugenadres, op. In PlaySound wordt er d.m.v. de @ naar dat adres verwezen. De derde parameter van PlaySound geeft aan wat voor soort geluid het is en hoe het moet worden gespeeld. SND_ASYNC betekent dat tijdens het afspelen het programma gewoon verder moet werken. SND_MEMORY betekent dat het geluid in het geheugen staat.

– Zet in het interface-gedeelte na de class-definitie en voor de var de volgende regels neer:
  function PlayAlarm(AlarmNaam: String): Boolean;
  function StopAlarm: Boolean;
– Implementeer beide functies in het implementation gedeelte na de Uses-clausule als volgt:

–   function PlayAlarm(AlarmNaam: String): Boolean;
    var
      s: AnsiString;
    begin
      Result := True;
      if AlarmNaam = ” then
        Result := False;
      try
        s := LazarusResources.Find(AlarmNaam).Value;
        if s <> ” then
          PlaySound(@s[1], 0, Snd_Async or Snd_Memory or Snd_Loop)
        else
          Result := False;
      except
        Result := False;
      end;
    end;

–   function StopAlarm: Boolean;
    begin
      Result := True;
      try
        PlaySound(nil, 0 ,0);
      except
        Result := False;
      end;
    end;
– Haal de variabele (inclusief var) weg bij btnPlayClick.
– Vervang de code van btnPlayClick als volgt:
  if btnPlay.Caption = ‘Play alarm’ then
    begin
      btnPlay.Caption := ‘Stop alarm’;
      lstAlarm.Enabled := False;
      if not PlayAlarm(lstAlarm.Items[lstAlarm.ItemIndex]) then 
        begin
          ShowMessage(‘Alarm niet gevonden’);
          btnPlay.Caption := ‘Play alarm’;
          lstAlarm.Enabled := True;
        end;
    end
  else
    begin
      btnPlay.Caption := ‘Play alarm’;
      lstAlarm.Enabled := True;
      StopAlarm;
    end;

Opmerking: Wanneer u in Lazarus try klikt en naar de volgende regel entert, dan er komt automatisch finally-end; te staan. Vervang dus finally door except.

Omdat ook een extern geluid moet kunnen worden afgespeeld en omdat PlaySound ook vanuit frmKlokje moet werken hebben we een globale variabele nodig binnen Unit4 en later ook nog een globale variabele in frmKlokje.

– Declareer in het implementation-gedeelte (van Unit4) na de Uses-clausule de volgende variabele:
  var
    SoundNameTmp: String;
– Voeg, als laatste regel, de volgende code toe aan lstAlarmClick:
  SoundNameTmp := lblAlarm.Caption;
– Voeg als laatste regel in het begin-end;-blok van btnOtherAlarmClick de volgende code toe:
  SoundNameTmp := ‘FILE:’ + lblAlarm.Caption;

Met de toevoeging ‘FILE:‘ geven we aan dat de SoundName een extern bestand is. Bij PlaySound moeten we hier dan op gaan testen.

– Vervang (pas aan/voeg toe) de code in PlayAlarm tussen try en except door onderstaande code:
  if LeftStr(UpperCase(AlarmNaam),5) = ‘FILE:’ then
    begin
      PlaySound(PChar(Copy(AlarmNaam,6,Length(AlarmNaam))), 0,Snd_Async or Snd_FileName or Snd_NoDefault or Snd_Loop)
    end
  else
    begin
      s := LazarusResources.Find(AlarmNaam).Value;
      if s <> ” then
        PlaySound(@s[1], 0, Snd_Async or Snd_Memory or Snd_Loop)
      else
        Result := False;
    end;
– Vervang in btnPlayClick de regel
  if not PlayAlarm(lstAlarm.Items[lstAlarm.ItemIndex]) then
– door:
  if not PlayAlarm(SoundNameTmp) then
– Voeg in FormActivate de volgende regel toe:
  SoundNameTmp := ‘Alarm01’;
– Run het klokje en experimenteer met de geluidjes (ook extern!).

De geluidjes werken. We gaan verder met het alarm.

Het alarm gaat af

Het alarm moet afgaan op de tijd die gezet wordt in dtpAlarmTijd. Dan moet het geluid klinken en de alarm tekst getoond worden. We hebben dus een aantal globale variabelen nodig die dit gaan regelen. In frmKlokje moeten we in het Timer-event nu ook gaan kijken of er een alarm moet afgaan. Laten we maar eens beginnen.

– Maak de code van frmKlokje (Unit1) actief.
– Voeg de volgende globale variabelen toe in het interface-gedeelte (na FaceIndex: integer;):
  SoundName: String;
  AlarmAan: Boolean;
  AlarmTijd: TDateTime;
  AlarmTekst: String;
  AlarmFixed: Boolean;
– Voeg op het eind aan tmrTijdTimer de volgende code toe:
  if AlarmAan then
    if Time > AlarmTijd then
      begin
        AlarmAan := False;
        ShowMessage(AlarmTekst);
      end;

Nu moeten de globale variabelen in frmAlarmSettings nog de juiste waardes krijgen. Dit gebeurt in het btnOK-event.

– Activeer btnOkClick en voeg de volgende regels code toe:
  SoundName := SoundNameTmp;
  AlarmAan := chkAlarmOn.Checked;
  AlarmTijd := dtpAlarmTijd.Time;
  AlarmTekst := edtAlarmText.Text;
  AlarmFixed := chkFixedTime.Checked;
  close;
– Run de klok, zet de alarmtijd op een half minuutje later dan nu en klik Alarm on aan.
– Klik hierna op de knop OK en wacht tot de tijd verstreken is.

Als het goed is krijgt u op de ingestelde tijd de tekst te zien:

We moeten nog een paar zaken regelen.
De MessageBox moet worden vervangen door een eigen venster dat ook het geluid afspeelt.
Bij het openen van de settings is het handig dat de tijd alvast in de nabije toekomst staat, bijvoorbeeld een (afgerond) half uur later dan dat het nu is.
Alle nieuwe globale variabelen moeten nog in het cfg-bestand worden opgenomen.
De checkbox Fixed moet nog worden geïmplementeerd.

Alarm-box

We beginnen met een eigen Form voor het alarm en we maken er iets “aparts” van:

– Maak een nieuw Form.
– Zet de volgende properties:
– BorderStyle = bsDialog
– Color = clYellow
– FormStyle = fsSystemStayOnTop
– Name = frmAlarm
– Position = poScreenCenter
– Plaats een Label op het Form.
– Zet de volgende properties:
– Alignment = taCenter
– AutoSize = False
– Font.Color = clBlue
– Font.Style = [fsBold]
– Layout = tlCenter
– Zet twee Labels op het Form.
– Zet de volgende properties:
– Alignment = taCenter
– AutoSize = False
– Caption = Snooze (en) OK
– Font.Style = [fsBold]
– Layout = tlCenter
– Name = lblSnooze (en) lblOK

Bij het starten van dit Form moet de tekst worden getoond en het geluid worden gespeeld. Als er op OK wordt geklikt moet het scherm weer weg en moet het geluid stoppen.

– Zet in het implementation-gedeelte onder {$R *.lfm} de volgende code neer:
  Uses
    Unit4, Unit1;
– Activeer het FromActivate-event.
– Voeg de volgende regels code toe:
  lblAlarmText.Caption := AlarmTekst;
  PlayAlarm(SoundName);
– Activeer het FromClose-event.
– Voeg de volgende regel code toe:
  StopAlarm;
– Activeer het lblOKClick-event.
– Voeg de volgende regel code toe:
  close;
– Ga naar de code van frmKlokje (Unit1).
– Voeg Unit5 toe aan de Uses-clausule in het interface-gedeelte.
– Ga naar tmrTijdTimer.
– Vervang ShowMessage(AlarmTekst) door:
  frmAlarm.ShowModel;
– Run de klok (bewaar Unit5), zet het alarm en wacht af.

Als het goed is, is het alarm afgegaan:

De knop Snooze gaan we in de laatste aflevering implementeren.

Afronden van de tijd

We gaan de dtpAlarmTijd bij het openen van de settings op een tijd zetten in de buurt van de huidige tijd. Dit mag alleen als het alarm niet geactiveerd is.

– Ga naar de code van frmAlarmSettings (Unit4).
– Ga naar FormActivate.
– Voeg de volgende variabelen toe:
  u,m,s,ms: Word;
– Voeg de volgende code toe aan het eind, dus voor end;
  if not AlarmAan then
    begin
      DecodeTime(Now,u,m,s,ms);
      ms := 0;
      s := 0;
      if m >= 30 then
        begin
          u := u + 1;
          m := 0;
        end
      else
        m := 30;
      dtpAlarmTijd.Time := EncodeTime(u,m,s,ms);
    end;

Als nu de settings actief worden en het alarm is niet gezet dan wordt dtpAlarmTijd gezet op het eerste halve uur na nu.

Voor de netheid:

– Zet in de Object Inspector de property Time van dtpAlarmTijd op 12:00:00.
– Run de klok en open de settings.

De checkbox chkFixedTime moet nog worden geïmplementeerd. Wanneer deze aangevinkt is dan mag de tijd in dtpAlarmTijd niet worden veranderd. Dit gebruikt u als u elke dag op dezelfde tijd het alarm wilt laten afgaan.

– Pas in de FormActivate van frmAlarmSettings de regel if not AlarmAan then als volgt aan:
  if not AlarmAan and not AlarmFixed then

Ten slotte moet u er nog voor zorgen dat alle nieuwe globale variabelen worden verwerkt in/uit het cfg-bestand. Maak daartoe de sectie [ALARM] als volgt:

[ALARM]
Sound=Alarm01.wav
Text=Wake up!
Time=12:00:00
Fixed=0
On=0

– Breid SchrijfIni en LeesIni uit.

Zo, dit was heel wat werk, maar we zijn nu bijna klaar. In de laatste aflevering gaan we de puntjes op de i zetten en zal de volledige code van alle Forms worden gegeven.

Naar de volgende aflevering…