Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Timerereignis wird nicht ausgelöst


nn1234 - Fr 30.12.05 10:48
Titel: Timerereignis wird nicht ausgelöst
Hi!
Habe einen Timer gesetzt, der einige Dinge, gleich nach dem Aufruf des Formulars erledigen soll, unter anderem Bitmaps laden, da das mit Fomcreate ja nicht functioniert (ok, der war schlecht... :lol: ) Wollte jetzt mal fragen, ob es sein kann, dass der Timer zu viele Aktionen beinhaltet, sodass er, wenn ich die Eigenschaft Enable nach Erledigung der Aktionen auf False setze, gar nichts mehr macht? Hier mal der Quelltext:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
procedure TfKampf.Timer1Timer(Sender: TObject);
var level_Spieler,level_Gegner : Integer;art_Gegner,art_Spieler : String;
begin
  //Mittelpunkt setzen
  SetViewPortOrgEx(Canvas.Handle,fKampf.width DIV 2,fKampf.height DIV 2,nil);

  //Variablen Wert zuordnen
  if fAnfang.RadioGroup1.ItemIndex = 0 then
    art_Spieler := 'Eis';
  if FAnfang.RadioGroup2.ItemIndex = 0 then
    art_Gegner := 'Erde';
  level_Spieler := 1;
  level_Gegner := 1;
  life_max_Spieler := TKreatur.create(level_Spieler,art_Spieler);
  life_max_Gegner := TKreatur.create(level_Gegner,art_Gegner);

  //Kreraturenwahl des Spielers
  if fAnfang.RadioGroup1.ItemIndex = 0 then
  begin
    uNuetzlich.kreatur_zeichnen(fKampf.Canvas,'Kreaturen/Eiselement.Bmp',-330,-70);
    Label5.Caption := 'Eiselement';
    uNuetzlich.life_balken(fKampf.Canvas,life_max_Spieler.life_max,life_max_Spieler.life_max,1);
    Label6.Caption := IntToStr(life_max_Spieler.life_max);
    Label7.Caption := IntToStr(life_akt_Spieler);
    Label10.Caption := IntToStr(level_Spieler);
  end;

  //Kreaturenwahl des Gegners
  if fAnfang.RadioGroup2.ItemIndex = 0 then
  begin
    uNuetzlich.kreatur_zeichnen(fKampf.Canvas,'Kreaturen/Erdelement.Bmp',120,-280);
    Label4.Caption := 'Erdelement';
    uNuetzlich.life_balken(fKampf.Canvas,life_max_Gegner.life_max,life_max_Gegner.life_max,2);
    Label9.Caption := IntToStr(level_Gegner);
  end;

end;

Wenn ich jetzt ganz am Schluss Enable auf False setze, macht er gar nichts mehr. Das Intervall hab ich auf 100 gesetzt, hab's aber auch schon mit anderen Intervallen versucht.
nn


Narses - Fr 30.12.05 10:54

Moin!

Alternative: FormShow und ein globales Flag; spart den Timer. :wink:

cu
Narses


nn1234 - Fr 30.12.05 11:04

...Hab keine Ahnung, was flags sind, und wie ich die anwende... :oops:
nn


Narses - Fr 30.12.05 11:17

Moin!

Aaalso... :wink: Du machst die ganzen Aktionen im Ereignis FormShow(). Da du nicht ausschließen kannst, dass das Ereignis mehrfach auftritt, du aber nur das erste Auftreten bearbeiten willst, mußt du dir in einer globalen Variable vom Typ Boolean (z.B.) merken, dass du das schon getan hast. Konkret: gloablen Boolean anlegen und im FormCreate() auf FALSE setzen, im FormShow() nach allen Aktionen auf TRUE und die Aktionen per IF-THEN kapseln.

Auf diese Weise eingesetzte Bools nennt man auch schon mal Flag ("Signalfähnchen" so ganz grob).

cu
Narses


Gausi - Fr 30.12.05 11:21

Da ist wohl so etwas gemeint

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
var Erledigt: Boolean;

//im OnCreate:
Erledigt := False;

// im OnShow:
if Not Erledigt then
begin
  //Mach dies und das 
  //(halt der ganze Krempel, der zur Zeit im OnTimer steht)
  Erledigt := True;
end;

Sinn: Eine Boolsche Variable, die steuert, ob etwas ausgeführt werden soll, oder nicht. Nach einmaliger Ausführung wird diese Variable so gesetzt, dass das ganze nicht nochmal ausgeführt wird.

Edit: Hab ich wirklich 4 Minuten zum Tippen gebraucht...:gruebel:?


wdbee - Fr 30.12.05 11:25

Das, was du im Timer-Event machst, soll doch nur ein einziges mal ausgeführt werden.
Im FormCreate-Ereignis geht es nicht, wie du sagst. Im FormShow-Ereignis geht es aber. Nur wird dieses Ereignis ggf. mehr als einmal auftreten, anders als FormCreate.
Deshalb musst du dir einfach merken, ob die Objekte, die du da erzeugen willst, schon erzeugt sind oder nicht.
Die Variable life_max_Spieler z.B. sollte nil sein, wenn es das Objekt noch nicht gibt.
Also kannst du einfach abfragen ob life_max_Spieler noch nil ist und nur dann die Initialisierung im FormShow-Ereignis ausführen. Damit verwendest du diese Variable als Flag (Flagge). Flagge oben = Objekt gibt es schon, Flagge unten = Objekt gibt es noch nicht. Ein Flag ist also eine Boolsche-Variable, die als Merker für etwas eingesetzt wird.

Die Verwendung von Timern für soetwas, hat den Nachteil, dass du nicht mehr sicher weißt, wann im Ablauf die Initialisierung ausgeführt wird! Wenn du dennoch einen Timer verwendest, um ein einmaliges Ereignis auszulösen, dann solltest du als erstes im TimerEvent Enabled auf false setzten, dann spielt es keine Rolle mehr, wie lange die Ausführung tatsächlich dauert.


nn1234 - Fr 30.12.05 13:37

Ist mit OnShow die Methode Form2.Show gemeint? Das Ereignis OnShow im Objektinspektor unter Ereignisse lässt sich nicht als Prozedur öffnen... :?
nn


Ja-Pa - Fr 30.12.05 13:44

Genau, Delphi benennt die Ereignisprozeduren immer so (aus Button1.OnClick wird Button1Click).
Kannst sie aber natürlich auch beliebig umbenennen wenn dich das stört :wink:


nn1234 - Fr 30.12.05 13:45


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
var Erledigt : Boolean;
...
procedure TfKampf.FormCreate(Sender: TObject);
begin
  Erledigt := False;
end;

procedure TfKampf.FormShow(Sender: TObject);
var level_Spieler,level_Gegner : Integer;art_Gegner,art_Spieler : String;
begin
  if not Erledigt then
  begin

    //Mittelpunkt setzen
    SetViewPortOrgEx(Canvas.Handle,fKampf.width DIV 2,fKampf.height DIV 2,nil);

    //Variablen Wert zuordnen
    if fAnfang.RadioGroup1.ItemIndex = 0 then
      art_Spieler := 'Eis';
    if FAnfang.RadioGroup2.ItemIndex = 0 then
      art_Gegner := 'Erde';
    level_Spieler := 1;
    level_Gegner := 1;
    life_max_Spieler := TKreatur.create(level_Spieler,art_Spieler);
    life_max_Gegner := TKreatur.create(level_Gegner,art_Gegner);

    //Kreraturenwahl des Spielers
    if fAnfang.RadioGroup1.ItemIndex = 0 then
    begin
      uNuetzlich.kreatur_zeichnen(fKampf.Canvas,'Kreaturen/Eiselement.Bmp',-330,-70);
      Label5.Caption := 'Eiselement';
      uNuetzlich.life_balken(fKampf.Canvas,life_max_Spieler.life_max,life_max_Spieler.life_max,1);
      Label6.Caption := IntToStr(life_max_Spieler.life_max);
      Label7.Caption := IntToStr(life_akt_Spieler);
      Label10.Caption := IntToStr(level_Spieler);
    end;

    //Kreaturenwahl des Gegners
    if fAnfang.RadioGroup2.ItemIndex = 0 then
    begin
      uNuetzlich.kreatur_zeichnen(fKampf.Canvas,'Kreaturen/Erdelement.Bmp',120,-280);
      Label4.Caption := 'Erdelement';
      uNuetzlich.life_balken(fKampf.Canvas,life_max_Gegner.life_max,life_max_Gegner.life_max,2);
      Label9.Caption := IntToStr(level_Gegner);
    end;

    //Erledigt
    Erledigt := True;
  end;

end;

Funzt nicht, genau, wie wenn ich den Timer drin hab... :?
nn


Ja-Pa - Fr 30.12.05 13:50

user profile iconnn1234 hat folgendes geschrieben:
Funzt nicht


WAS funktioniert denn nicht? Fehlermeldung? Programm hängt? Schleife bricht vorzeitig ab?
Tipp: Einfach mal den Debugger drüber laufen lassen (im Einzelschritt ausführen -> mehrmals F7 drücken).
So kannst du die einzelnen Schritte genau nachvollziehen und siehst, wenn etwas nicht stimmt.


nn1234 - Fr 30.12.05 13:54

Fehlermeldung kommt keine, aber es werden keine Aktionen ausgeführt...
Zitat:
Funzt nicht, genau, wie wenn ich den Timer drin hab...

siehe weiter oben:
Zitat:
...macht er gar nichts mehr


Ja-Pa - Fr 30.12.05 13:58

Meinst du mit gar nichts mehr, dass sich das Programm "aufhängt"?
Dann würd ich mal die Prozeduren im OnCreate-Ereignis (z.B. SetViewPortOrgEx) überprüfen... sonst müsste der Code IMHO funktionieren :?


nn1234 - Fr 30.12.05 14:06

Nein, die Aktionen, die im OnShow stehen, werden einfach nicht ausgefährt.


Narses - Fr 30.12.05 14:16

Moin!

Pack mal ein ShowMessage('Hallo'); mit rein, dann starten. Kommt was?

cu
Narses


nn1234 - Fr 30.12.05 17:30

Unerwarteter Weise ja. Bevor Form2 sichtbar ist, zeigt's mir 'Hallo'. Kann es also sein, das die Aktionen zwar ausgeführt werden, dies allerdings zu früh und dass man sie darum nicht ausgeführt sieht? Wenn ja, wie behebt man solch ein Problem?


DarkHunter - Fr 30.12.05 18:07

Welchen Itemindex haben die Radiogroups?
Schließlich machst du den kompletten Ablauf von den einzelnen Indexes abhängig.
Was willst du damit eigentlich bezwecken? Da diese Aktionen nur beim starten durchgeführt werden, hat der User doch gar keine Möglichkeit das zu ändern, oder sehe ich das falsch?
Daher frage ich mich welchen Sinn das macht die If-Abfrage zu machen, da der Itemindex doch Standartmäßig immer "0" ist, es sein denn es wurde von Programmierer anderes definiert.


nn1234 - Fr 30.12.05 19:07

Vom Grundaufbau läuft das Ganze so ab:
Auf Form1 sind Radiogroups mit denen der User jeweils eine Kreatur für sich und eine für den Gegner aussuchen kann. Da ich noch in der Testphase bin, habe ich zunächst eine Kreatur für den Spieler und eine für den Gegner erstellt. Es sollen natürlich weitere folgen, womit die if-Schleife begründet wäre (bis jetzt nur mitt Null, da erst eine Kreatur da ist - Schleife wird noch erweitert). Die ausgewählten Kreaturen sollen dann mit ihrem dazughörigen Level und der Lifeanzeige abgebildet sein, sobald das neue Formular sich öffnet. Das passiert nach klicken eine Buttons.
Soviel zum Aufbau. Da ich FormCreate für mein Problem nicht nutzen kann, wollte ich Timer einbauen, um die Kreaturen abzubilden. Das geht aber nicht. Genauso, wie das verwenden dieser Variablen Erledigt in FormShow.
Was kann ich noch versuchen?
Ich versteh das absolut nicht, da ich vorher eine ähnliche Prozedur in einem anderen Programm geschrieben hatte. Da brauchte ich nach den Methoden den Timer einfach auf Enable := False; stellen und da hat alles ordnungsgemäß funktioniert... :?:


nn1234 - Sa 31.12.05 02:39

So, hab heute früh viertel vor elf das Problem beschrieben. Nun ist es 13 1/2 Stunden und eine Lösung später... :D
Ich vermute mal, dass der Timer von mir zu schnell auf Enable:=False; gesetzt wurde - das Intervall zu klein war. Hab ich dieses verändert, hat sich aber nichts getan.
Der Fehler lag darin, dass die Eigenschaft Enable direkt nach dem Aufruf der davorigen Methode auf False gesetzt wurde. Somit hat der Timer praktisch keine Zeit, die Methode auszuführen. Methoden, die am Anfang des Timers aufgerufen werden, funktionieren ja (siehe ShowMessage).
Problembehebung:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
procedure Form2.Timer1Timer(Sender: TObject);

//Springender Punkt!!
procedure Pause(ms: Cardinal); // (c) 2002, Michael Winter
var Stop,Timer: Cardinal;
begin
  Timer := SetTimer(00, ms, nil);
  Stop := GetTickCount + ms;
  repeat
    Application.HandleMessage;
  until Application.Terminated or (Integer(GetTickCount - Stop) >= 0);
  KillTimer(0, Timer);
end;

begin
  {sämtliche Methoden, die der Timer ausführen soll}
  Pause(500);
  Timer1.Enabled := False;
end;

So funktioniert's und ich kann beim Timer bleiben :roll: :lol:
Vielen Dank, für die zahlreichen Anregungen!
nn


Opa - Sa 31.12.05 09:07

Die Lösung mit den 500 mms Pause kann eigentlich nur falsch sein, sie macht nicht wirklich einen Sinn. Wenn dann sollte dort

Application.ProcessMessages oder
Repaint, entweder von der Form bzw. von einem Panel
ggf. auch Panel.DoubleBuffered := true

stehen.

Soll die Procedure Form2.Timer1Timer sofort ausgeführt werden kann man sie u.a. aus Create sofort aufrufen Form2.Timer1Timer(self) oder Form2.Timer1Timer(nil).

Ich denke das der Fehler in Application.HandleMessage steckt ich würde am Ende Application.ProcessMessages wählen.

Quelltext
1:
2:
3:
4:
5:
begin  
  {sämtliche Methoden, die der Timer ausführen soll}  
  Application.ProcessMessages 
  Timer1.Enabled := False;  
end;


MSCH - Sa 31.12.05 09:37

grausam. tse tse

also
versuch's mal Damit


Delphi-Quelltext
1:
2:
const
  wm_StartFlag = wm_Application + 1;



in der Klassendeklaration


Delphi-Quelltext
1:
procedure WMStartFlag(var Msg:TMessage);Message wm_StartFlag;                    


in der OnCreate()-Methode


Delphi-Quelltext
1:
postMessage(Self.handle, wm_StartFlag,0,0)                    


die Funktion


Delphi-Quelltext
1:
2:
3:
4:
[Klasse].WMStartFlag;
begin
  //hier alles ausführen was der Timer machen sollte
end;


Das Vorgehen ist simple, eine Nachricht wird beim Erstellen der form in die
Nachrichtenschleife eingesetzt und erst wenn der Rest abgearbeitet ist, folgt der Aufruf
der Funktion WMStartFlag().

Das funktioniert nur einmal und auch nur via PostMessage().

Vergiss den Kram mit Timer und Variablen.

grez
msch


nn1234 - So 01.01.06 13:26

Am Ende ist doch die einfachste Lösung, die beste. Hab jetzt einfach das zweite Formular nur mit Show statt ShowModal angezeigt. So kann ich den ganzen Timer Quatsch in die ButtonKlick Methode rein setzen, die das Formular auch anzeigt.
-Problem geklärt- :D
nn