Hallo
und viel Freude mit Lazarus und fpc.
Ich weiß immer nicht warum
Application.ProcessMessages gerade bei Anfängern so hoch im Kurs steht, bei mir war das eigentlich nicht viel anders. Ich vermute, weil man sich von der linearen Vorstellung dessen was das Programm machen soll nicht lösen kann und bei der Realisation "1. Das", "warten", "2. Jenes" und dann "warten", "3. Welches" in Pascal denkt
Code: Alles auswählen
program;
procedure Das;
begin
//...
end;
procedure Jenes;
begin
//...
end;
procedure Welches;
begin
//...
end;
procedure Warten;
begin
//...
end;
begin
Das;
Warten;
Jenes;
Warten;
Welches;
end.
Aber eine nachrichtengesteuerte Anwendung, wie die eines Desktop-Fenster-Formulars funktioniert ganz anders.
Hier schläft die Anwendung die ganze Zeit und wenn jemand etwas von ihr will, dann erledigt sie das ganz schnell und schläft sofort weiter.
Deshalb ist die Idee, man könne das o.a. Beispiel einfach in das Formular werfen, falsch und falls es doch unbedingt nötig ist, ist es auch noch wesentlich komplexer als es zunächst aussieht.
Also zurück auf Los.
Erste Frage, warum willst Du dieses zeitgesteuerte Verhalten?
Zweite Frage, wann soll das Verhalten gezeigt werden.
Grundsätzlich gibt es für Zeitabläufe zwei Varianten.
1.) Timer
2.) Threads
Zu 1.)
Timer bieten sich an, wenn man regelmäßig etwas tun mächte, was auch ein Benutzer der Anwendung tun würde, also aufwecken, etwas erledigen, weiter schlafen.
Beispiele: Etwas soll blinken, eine Eingabe soll erst nach einer kurzen Zeit überprüft werden und nicht nach jedem eingegeben Zeichen.
Ein Timer löst letztendlich ein Ereignis aus, wie ein Klick auf einen Button.
Zu 2.)
Threads bieten sich an, wenn etwas sehr zeitintensiv ist und deshalb im Hintergrund erledigt werden muss, weil sonst die ganze Anwendung ruckelt oder stillsteht und nur hin und wieder mal ein Update an den Benutzer geschickt wird, wie bspw. eine Aktualisierung eines Fortschrittsbalkens.
Ein Thread muss erzeugt werden, man muss in der Klasse die Execute Methode überschreiben und man sorgfältig Sorge dafür tragen, dass der Thread sauber beendet wird. Und natürlich kann er nicht direkt auf die Oberfläche zugreifen, diese Zugriffe müssen synchronisiert werden. Das hört sich wesentlich komplizierter an als es ist. Man stelle sich einfach vor, statt eines Zuges fahren jetzt zwei nebeneinander her, klar, dass man eine Nachricht nur übergeben kann, wenn beide gleich schnell fahren und beide eine Tür an gleicher Stelle auf haben

.
Um Dein Beispiel mit Leben zu erfüllen einige konkrete Hinweise.
FormCreate wird bei der Erzeugung des Forms aufgerufen, zu diesem Zeitpunkt wird es aber noch nicht gezeigt. Das ist der richtige Moment um z.B. weitere Elemente zur Laufzeit auf das Form zu bringen, bspw. 100 Checkboxen in einem 10 x 10 Feld, deren Anlage im Formulareditor sehr mühsam wären.
Dagegen wird
FormActivate immer dann aufgerufen, wenn das Form aktiviert wird. Sehr häufig findet man deshalb folgendes
Code: Alles auswählen
procedure TForm1.FormActivate(Sender: TObject);
begin
OnActivate := Nil; // FormActivate nur einmal durchlaufen
Memo1.Lines.Add('Hallo, Datum und Uhrzeit: '+FormatDateTime('YYYY-MM-DD HH:NN:SS',Now)+'.');
Timer1.Enabled := True; // Erklärung folgt im Text
end;
Mittels der Zeile
OnActivate := Nil; schaltet man das Event ab, indem man die Referenz auf diese Methode auf Nil setzt. Sehr praktisch. Danach kann man in einem Aufwasch alles aktualisieren, was jetzt wo das Form angezeigt wird, relevant ist, bspw die Häckchen der o.a. 100 Checkboxen setzen.
Um das Beispiel oben zum Laufen zu bringen, packst Du einen Timer auf Dein Form und trägst bei der Eigenschaft
Interval die Zeit von 4000 Millisekunden ein und entfernst das Häckchen bei der Eigenschaft
enabled, dies verhindert, dass der Timer automatisch losläuft.
In
FormActivate wird dies in der Zeile
Timer1.Enabled := True; nachgeholt.
In den Ereignissen des Timers klickst Du doppelt auf
OnTimer und schreibst
Code: Alles auswählen
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Memo1.Lines.Add('Hallo, Datum und Uhrzeit: '+FormatDateTime('YYYY-MM-DD HH:NN:SS',Now)+'.'); //Zeit eintragen
Timer1.Enabled := False; //Timer aus
end;
Wenn Du das Programm startest wird unmittelbar nach dem Start eine Zeile in das Memo eingetragen und dann nach 4 Sekunden eine weitere hinzugefügt.
Natürlich verhält sich das Programm aus der Sicht des Benutzers immer noch etwas seltsam, denn wenn Du sofort beginnst im Memo zu schreiben, wird Dein Schreibfluss im Memo unterbrochen! Aber es ist ja nur ein Beispiel.
Der Vollständigkeithalber noch das gleiche Beispiel als Thread-Lösung
Code: Alles auswählen
{ TTestThread }
// Eine abgeleitete Klasse eines Thread.
TTestThread = class(TThread)
private
procedure SynchedWrite; // Eine private Methode zum Sunchronen Aufruf
protected
procedure Execute;override; // Die überschriebene Methode für das "Programm" des Threads
end;
{ TForm1 }
TForm1 = class(TForm)
// Diverses Zeug hier
private
FTestThread : TTestThread; // Eine Instanzvariable des Threads
public
end;
{ TTestThread }
procedure TTestThread.SynchedWrite;
begin
// Die synchrone Ausgabe in das Formular.
// Ganz wichtig: Diese Methode darf niemals direkt von Execute auferufen werden!!! Warum? Ausprobieren ;-)
Form1.Memo1.Lines.Add('Hallo from Thread, Datum und Uhrzeit: '+FormatDateTime('YYYY-MM-DD HH:NN:SS',Now)+'.');
end;
// Execute stellt im Grunde ein eigenes Programm dar, es werden einfach die Dinge abgearbeitet
// und wenn man fertig ist, verlässt man die Methode.
// Wichtig: Wenn der Thread von außen beendet werden soll, also die Variable Terminated True ist,
// lässt man alles stehen und liegen, räumt noch das Nötigste auf und verschwindet!
procedure TTestThread.Execute;
var
t0, t1 : DWORD;
begin
t0 := GetTickCount64; // Unser Startzeitpunkt
// Das Folgende ist etwas übertrieben für die gestellte Aufgabe macht es aber leichter
// dieses Beispiel aufzubohren
// Ich mache das immer so:
// Außenrum ein Try..Finally, welches sicherstellt, dass wenn diese Methode verlassen wird
// auch Terminate aufgerufen wird. Macht man das nicht passieren seltsame Dinge
// Innen drin eine Schleife while not Terminated, hier überflüssig, weil ja nur einmal was passieren soll.
try
while not Terminated do
begin
// Eine Warteschleife für 4 Sekunden
repeat
if Terminated then Exit; // Abbruch? Dann nichts wie raus hier
Sleep(10); // Wir schlafen hier 10ms. Lässt man das weg geht die CPU Auslastung für einen Kern auf fast 100%
t1 := GetTickCount64; // Zeit holen
until t1-t0 >= 4000; // Prüfen ob vorbei
if Terminated then Exit; // Abbruch? Dann nichts wie raus hier
Synchronize(Self,@SynchedWrite); //Jetzt sind 4s rum, mit der Methode Synchronize wird die eigene Methode zur Ausgabe aufgerufen
// Das funktioniert so, dass im Hintergrund eine Nachricht an das Hauptfenster geschickt wird und SynchedWrite wird wie ein beliebiges
// OnABC-Ereignis ausgeführt
Break; // Wir sind fertig also raus
end;
finally
if not Terminated then // Sicherstellen, dass der Thread auch terminiert wird
Terminate;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FTestThread := TTestThread.Create(True); // Den Thread erzeugen, aber noch angehalten lassen
end;
procedure TForm1.FormActivate(Sender: TObject);
begin
OnActivate := Nil; // FormActivate nur einmal durchlaufen
Memo1.Lines.Add('Hallo, Datum und Uhrzeit: '+FormatDateTime('YYYY-MM-DD HH:NN:SS',Now)+'.');
FTestThread.Suspended := False; // Den Thread loslaufen lassen
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
// Auch komplizierter als nötig, aber für zukünftige Erweiterungen gut
// Haben wir eine erzeugte Thread-Instanz?
if Assigned(FTestThread) then
begin
if not FTestThread.Terminated then // Wurde der Thread noch nicht terminiert?
begin
FTestThread.Suspended := False; // Böse Falle, wenn der Thread nicht läuft, kann er nicht beendet werden!! Also laufen lassen.
FTestThread.Terminate; // Den Wunsch des Beendens dem laufenden Thread mitteilen
end;
FTestThread.WaitFor; //Warten bis der Thread wirklich fertig ist
FreeAndNil(FTestThread); // Freigeben und Nil, hier überflüssig, aber der Versuch einen Thread zweimal freizugeben führt ins Chaos
end;
end;
Das soweit.
Viel Erfolg.