Autor Beitrag
Nait
Hält's aus hier
Beiträge: 10



BeitragVerfasst: Di 31.08.10 22:31 
Hallo liebe Foren-Mitglieder. Ich habe schon ein paar Mal über Google auf eurer Seite Rat gefunden und mich schließlich (wo ich ein nun nicht mehr so einfaches Problem habe) entschlossen hier anzumelden. Hurra.
Ich arbeite mit Delphi 2005 und habe mich heute an die OOP herangewagt. Ein sehr interessantes Thema, nur ist mein Lehrbuch für Turbo Pascal 8, 1991 herausgegeben und arbeitet noch mit object statt mit class. Ansonsten war es aber immer sehr hilfreich. (Ich habe das Buch erst seit wenigen Jahren, falls die Frage aufkommt <_<)
Im Verlauf des Tages hatte ich ein altes unterbrochenes Projekt missbraucht, um den ersten OOP-Testlauf zu machen und Delphi meinte beim Debuggen, einfach 3 Zeilen überspringen zu können und sich danach zu wundern, warum das Objekt nicht initialisiert wurde. Ich habe kurzerhand ein neues Projekt gestartet und die Entwicklung des ersten Objektes war schwierig, aber ich habs geschafft.
ausblenden volle Höhe 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:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
  TSFE = class(TImage) {published: alle mit VCL-Zugriff}
    public {alle}
    private {eigene Unit & abgeleitete}
      FArt: Byte;
      Fright_loaded: Boolean;
      procedure Init(X, Y: SmallInt;
        PathToPic, PicName, PicExt: String; PicIndex: SmallInt;
        PicIndex0, Art: Byte);
      function Bild_laden(PathToPic, PicName, PicExt: String;
        PicIndex: SmallInt; PicIndex0: Byte): Boolean;
      //ImageList durch Overload einbinden
      function verschieben(strecke: SmallInt; Richtung: Byte): Boolean;
      procedure Done;
    protected {eigene Klasse & abgeleitete}
  end;{class.TSFE} //SFE = SpielFeldEinheit

...

procedure TSFE.Init(X, Y: SmallInt; PathToPic, PicName, PicExt: String;
  PicIndex: SmallInt; PicIndex0, Art: Byte);
begin
  FArt:= Art;
  Left:= X;
  Top:= Y;
  AutoSize:= true;
  if Bild_laden(PathToPic, PicName, PicExt, PicIndex, PicIndex0)
  then Fright_loaded:= true;
end;{function.TSFE.Init}

//resettet die Instanz von TSFE (mit neuem Bild); gibt false in
//Fright_loaded, wenn Bild nicht geladen werden konnte, sonst true

function TSFE.Bild_laden(PathToPic, PicName, PicExt: String;
  PicIndex: SmallInt; PicIndex0: Byte): Boolean;
var
  path, nullen: String//Pfad für Dateiauslese
  z, zi, //Größenordnung
  i: Integer; //Laufvarible
  treffer: Boolean;
begin
  if (PicIndex<0or (PicIndex0=0then //Fehleingabe
    Result:= false
  else begin
    path:= PathToPic + PicName; //linker Pfadteil
    z:= 1;
    for i:= 1 to PicIndex0 do z:= 10*z; //z=10^PicIndex0
    treffer:= false;
    i:= 1;
    zi:= 1;
    while (i <= PicIndex0) and not treffer do begin
      zi:= 10*zi; //solange kein Treffer, mult. mit 10 und schau,
      if PicIndex < zi then treffer:= true; //ob der Index kleiner ist
      inc(i); //falls ja, Treffer!
    end;{while.(i<PicIndex0)and not treffer}
    if not treffer then //kein Treffer = PicIndex > z -> Fehleingabe
      Result:= false
    else begin //treffer: Nullen (bei Bedarf) ergänzen
      Result:= true;
      nullen:= '';
      while zi < z do begin //für jede zu kleine Stelle eine Null
        nullen:= nullen + '0';
        zi:= 10*zi;
      end;{while.zi<z}
      path:= path + nullen + IntToStr(PicIndex) + '.' + PicExt;
      try {except}
        Picture.LoadFromFile(path); //Pfad vollenden und Bild laden
      except {try}
        Result:= false; //Bild nicht geladen -> Fehler
      end;{try|except}
    end;{if.treffer}
  end;{ifnot.(PicIndex<0)or(PicIndex0=0)}
end;{function.TSFE.Bild_laden}

//versucht Bild zu laden und gibt bei Erfolg true, sonst false

function TSFE.verschieben(strecke: SmallInt; Richtung: Byte): Boolean;
begin
  if strecke < 0 then //negative Strecke
    Result:= false //-> Fehleingabe
  else begin
    Result:= true;
    case Richtung of
      0: Top:= Top - strecke; //nach oben
      1: Left:= Left + strecke; //nach rechts
      2: Top:= Top + strecke; //nach unten
      3: Left:= Left - strecke; //nach links
    else Result:= false; //ungültige Richtung -> Fehleingabe
    end;{case.Richtung}
  end;{ifnot.strecke<0}
end;{function.TSFE.verschieben}

//verschiebt bei gültigen Parametern das TSFE-Bild und gibt true, sonst
//nur false (ohne Verschiebung)

procedure TSFE.Done;
begin
  Free; //gibt den Speicherplatz frei, wenn er noch existent ist
end;{procedure.TSFE.Done}


eigentlich wollte ich was mit constructor Create(Parameter); virtual; statt dem Init machen, aber Delphi gab warnung, dass dadurch der TIMage-Create gestört werden würde, also hab ichs gelassen.
Dann hab ich den Erben gemacht:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
  TObj = class(TSFE)
    private
      FPunkte: SmallInt;
      procedure Init(X, Y: SmallInt;
        PathToPic, PicName, PicExt: String; PicIndex: SmallInt;
        PicIndex0, Art: Byte; Punkte: SmallInt);
  end;{class.TObj}

...

procedure TObj.Init(X, Y: SmallInt; PathToPic, PicName, PicExt: String;
  PicIndex: SmallInt; PicIndex0, Art: Byte; Punkte: SmallInt);
begin
  FPunkte:= Punkte; //Punkte übernehmen
  TSFE.Init(X, Y, PathToPic, PicName, PicExt, PicIndex, PicIndex0, Art);
    //Rest initialisieren
end;{procedure.TObj.Init}

//resettet die Instanz von TSFE (mit neuem Bild)
//gibt false in Frightloaded, wenn Bild nicht geladen werden konnte,
//sonst true
//(bisher ist es nur das Bild, aber eig. die TSFE.Init)

und bei "//Rest initialisieren" sagt Delphi mir "Diese Form des Methodenaufrufes ist nur für Klassenmethoden erlaubt." Ich verstehe die Aussage, aber nicht, warum das so ist. Meine Frage wäre jetzt, wie ich das ganze umgestalten kann, damits funktioniert.

Ich wäre für Hilfe sehr dankbar. Vielleicht kann ich mich mal revanchieren (man, das sieht falsch geschrieben aus <_<)

PS: Ist Vorbereitung für ein Pacman-Game
PPS: Die Seite hier hab ich schon durch.

EDIT: Dass TPaintBox besser ist, wurde zur Kenntnis genommen.


Zuletzt bearbeitet von Nait am Mi 01.09.10 08:48, insgesamt 1-mal bearbeitet
elundril
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 3747
Erhaltene Danke: 123

Windows Vista, Ubuntu
Delphi 7 PE "Codename: Aurora", Eclipse Ganymede
BeitragVerfasst: Di 31.08.10 22:59 
Hallo und :welcome: im Delphi-Forum!

Ich denke du musst bei der TFSE mal hinter dem hier ein virtual anhängen:
ausblenden Delphi-Quelltext
1:
2:
3:
procedure Init(X, Y: SmallInt;
        PathToPic, PicName, PicExt: String; PicIndex: SmallInt;
        PicIndex0, Art: Byte)


Damit weiß das dass die abgeleitete Klasse diese Methode überschreiben darf. Damit musst du in der abgeleiteten Klasse ein override hinten anfügen. Damit du dann die Methode auch wirklich überschreibst.

Weiters darfst du in der abgeleiteten Klasse nicht TFSE.Init aufrufen sondern musst einfach ein inherited; statt dem TFSE.Init schreiben. Damit sagst du das der Compiler den Code der Elter-Klasse ausführen soll.

Falls du noch Literatur in Sachen OOP brauchst, einiges steht auch in Christian's Crashkurs und im Delphi-Wikibook.

lg elundril

_________________
This Signature-Space is intentionally left blank.
Bei Beschwerden, bitte den Beschwerdebutton (gekennzeichnet mit PN) verwenden.

Für diesen Beitrag haben gedankt: Nait
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19346
Erhaltene Danke: 1754

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mi 01.09.10 03:46 
user profile iconNait hat folgendes geschrieben Zum zitierten Posting springen:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure TObj.Init(X, Y: SmallInt; PathToPic, PicName, PicExt: String;
  PicIndex: SmallInt; PicIndex0, Art: Byte; Punkte: SmallInt);
begin
  FPunkte:= Punkte; //Punkte übernehmen
  TSFE.Init(X, Y, PathToPic, PicName, PicExt, PicIndex, PicIndex0, Art);
    //Rest initialisieren
end;{procedure.TObj.Init}

und bei "//Rest initialisieren" sagt Delphi mir "Diese Form des Methodenaufrufes ist nur für Klassenmethoden erlaubt."
Du hast einmal die Klasse als Bauplan für Objekte von dem Typ und dann tatsächlich erzeugte Objekte. Hier versuchst du Init bei dem Bauplan aufzurufen.
Das ist aber genauso als würdest du versuchen die Haustür bei dem Bauplan eines Architekten zu öffnen statt bei dem danach gebauten Haus. ;-)

Deshalb kommt, dass das nur bei Klassenmethoden geht. Denn es gibt auch von einem konkreten Objekt unabhängige Methoden, die dann aber auch nicht auf in einem Objekt erst gespeicherte Daten zugreifen können. Diese heißen Klassenmethoden. Das aber nur am Rande.

Ja, was du machen willst ist doch Init der Elternklasse aufrufen. Das geht so:
ausblenden Delphi-Quelltext
1:
  inherited Init(X, Y, PathToPic, PicName, PicExt, PicIndex, PicIndex0, Art);					
Dann zum Konstruktor:
Der bekommt bei TImage wie bei jeder VCL-Komponente einen Owner. Wenn du deine neue Komponente auf ein Formular legen können willst, dannn wird dabei dieser Konstruktor aufgerufen, der genau nur diesen Owner bekommt. In diesem Fall ist eine solche Init-Methode passend, weil du keinen Konstruktor mit anderen Parametern aufrufen kannst.

Wenn du selbst die Komponente erzeugst, kannst du auch andere Parameter einführen. Dann wäre es am besten Owner: TComponent als ersten Parameter zu benutzen. Die Warnung, dass der alte Konstruktor verdeckt wird oder sowas ist richtig, lässt sich aber umgehen.

Du kannst dem Konstruktor ein overload anhängen. Damit sagst du, dass dieser Konstruktor mit deinen Parametern zusätzlich zur Verfügung steht. So bleibt der alte erreichbar. Wennn du den alten gar nicht mehr nutzen möchtest, kannst du reintroduce anhängen. Damit sagst du Delphi, dass du den Konstruktor neu einführst und der alte dir egal ist.

Du kannst diesen alten Konstruktor dann in deinem erreichen:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
constructor Create(Owner: TComponent; X: Integer); overload;

...

constructor ...
begin
  inherited Create(Owner);
  FX := X;
end;
Für ein Spiel wiederum eignet sich TImage allerdings nicht so gut. Da wäre eine TPaintBox besser.

Für diesen Beitrag haben gedankt: Nait
Nait Threadstarter
Hält's aus hier
Beiträge: 10



BeitragVerfasst: Mi 01.09.10 08:46 
danke erstmal für die Begrüßung rasche Antwort! Der Hinweis mit inherited war elementar, da hätt ich wohl ewig gesucht. Übrigens wusste ich was Klassenmethoden sind und deshalb auch, dass ich die nicht wollte xD
Okay, wenn es funktioniert, sieht es so aus:
ausblenden volle Höhe 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:
  TSFE = class(TImage)
    private
      ...
      procedure Init(X, Y: SmallInt;
        PathToPic, PicName, PicExt: String; PicIndex: SmallInt;
        PicIndex0, Art: Byte);

      ...
  end;

  TObj = class(TSFE)
    private
      FPunkte: Byte;
      procedure Init(X, Y: SmallInt;
        PathToPic, PicName, PicExt: String; PicIndex: SmallInt;
        PicIndex0, Art: Byte; Punkte: SmallInt);
  end;

...

procedure TSFE.Init(Parameter);
begin
  ...
end;

procedure TObj.Init(Parameter);
begin
  FPunkte:= Punkte; //Punkte übernehmen
  inherited Init(X, Y, PathToPic, PicName, PicExt, PicIndex,
    PicIndex0, Art); //Rest initialisieren
end;


nun kommen folgende Varianten (in der Typvereinbarung von TSFE und TObj) mit Meldungen von Delphi. Achja, die beiden Deklarationen stehen immer noch im jeweiligen private-Teil
ausblenden Delphi-Quelltext
1:
2:
3:
procedure Init(Parameter); virtual;
...
procedure Init(Parameter);
-> nichts, aber da später ja noch weitere Erben kommen, sollte TObj.Init auch virtual sein...

ausblenden Delphi-Quelltext
1:
2:
3:
procedure Init(Parameter); virtual;
...
procedure Init(Parameter); virtual;
-> Warnung: "Methode Init verbirgt virtuelle Methode vom Basistyp TSFE"

ausblenden Delphi-Quelltext
1:
2:
3:
procedure Init(Parameter); virtual;
...
procedure Init(Parameter); overridevirtual;
-> Fehler: "Deklaration von Init unterscheidet sich von vorheriger Deklaration" "Felddefinition nicht erlaubt nach Methoden der Eigenschaften"

ausblenden Delphi-Quelltext
1:
2:
3:
procedure Init(Parameter); virtual;
...
procedure Init(Parameter); virtualoverride;
-> Warnung: "Methode Init verbirgt virtuelle Methode vom Basistyp TSFE" Fehler: "Felddefinition nicht erlaubt nach Methoden der Eigenschaften"

Anscheinend wende ich eure Informationen falsch an -.-

Die overload-Lösung für den Konstruktor behalte ich im Hinterkopf, geplant war aber eher eine Vererbungslösung wie mit den beiden Init-Prozeduren. Es ist an sich also dasselbe Problem.

Dass TPaintBox besser geeignet sein soll, hab ich schon an anderer Stelle gelesen, ich werde mich aber erst später damit auseinander setzen.

Die beiden Links werd ich mir später anschauen, jetzt geh ich mich erstmal aus der Kirche austragen Verdammter Mittwoch, hätte nen Termin machen sollen -.-
Ja, ich schaus mir jetzt an.

EDIT: Moment, das hier reicht?
ausblenden Delphi-Quelltext
1:
2:
3:
procedure Init(Parameter); virtual;
...
procedure Init(Parameter); override;
-> Fehler: "Deklaration von Init unterscheidet sich von vorheriger Deklaration" Hm, hatte es gerade ähnlich hier gelesen... Hier ist vllt. nochmal anzumerken, dass TObj.Init einen Parameter mehr als TSFE.Init hat.


Zuletzt bearbeitet von Nait am Mi 01.09.10 09:10, insgesamt 2-mal bearbeitet
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19346
Erhaltene Danke: 1754

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mi 01.09.10 09:03 
Die Methode ist bereits virtual, beim Überschreiben mit override gehört das nicht mehr hin. ;-)
Nait Threadstarter
Hält's aus hier
Beiträge: 10



BeitragVerfasst: Mi 01.09.10 10:08 
aso, wenn ich also Init in TObj erneut deklariere, denkt er sich virtual dazu! Trotzdem mag er das Override nicht. Aber ich denke, ich muss die Methoden gar nicht unbedingt virtual gestalten oder?

Hab übrigens inzwischen TMovObj gemacht:
ausblenden volle Höhe 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:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
  TMovObj = class(TObj)
    FTimer: TTimer;
    private
      FState, FFrame, FItv_laenge: Byte; //Itv = Intervall
      FTimer_running: Boolean;
      procedure Init(X, Y: SmallInt;
        PathToPic, PicName, PicExt: String; PicIndex: SmallInt;
        PicIndex0, Art, State: Byte; Punkte: SmallInt;
        Itv_laenge: Byte; Itv_dauer: Cardinal);
      procedure Run;
      procedure OnTime(Sender: TObject);
      procedure Stop;
      procedure Done;
  end;{class.TMovObj}

...

procedure TMovObj.Init(X, Y: SmallInt; PathToPic, PicName, PicExt: String;
  PicIndex: SmallInt; PicIndex0, Art, State: Byte; Punkte: SmallInt;
  Itv_laenge: Byte; Itv_dauer: Cardinal);
begin
  inherited Init(X, Y, PathToPic, PicName, PicExt, PicIndex,
    PicIndex0, Art, Punkte); //Sonstiges initialisieren
  FState:= State; //State übergeben
  FFrame:= 0//Frame startet immer bei 0
  FItv_laenge:= Itv_laenge; //Intervallänge übergeben
  FTimer.Interval:= Itv_dauer; //die Dauer auf den Timer
  FTimer_running:= false; //Timer noch nicht gestartet
end;{procedure.TMov.Obj}

//resettet die Instanz von TMovObj inkl. der Felder der Vaterinstanzen
//Frightloaded ist true bei korrekter (Bild-)Ladung, sonst false

procedure TMovObj.Run;
begin
  FTimer.OnTimer:= OnTime; //Prozedur wird Timer zugeordnet
  FTimer_running:= true; //Timer läuft (bessere Abfrage)
end;{procedure.TMovObj.Run}

//startet den Timer

procedure TMovObj.OnTime(Sender: TObject);
begin
  verschieben(FItv_laenge,FState{Mod 4}); //Mod 4 bei Directioneinbau
    //Testbefehl
end;{procedure.TMovObj.OnTime}

//Timerevent

procedure TMovObj.Stop;
begin
  FTimer.OnTimer:= nil//Event entfernen
  FTimer_running:= false; //Timer läuft nicht mehr
end;{procedure.TMovObj.Stop}

//stoppt den Timer

procedure TMovObj.Done;
begin
  if FTimer_running then Stop; //Timer anhalten
  FTimer.Free; //Timer lösen
  inherited Done; //Rest lösen
end;{procedure.TMovObj.Done}

//stoppt ggf. den Timer, befreit ihn und den Rest der TMovObj-Instanz

procedure TForm1.FormCreate(Sender: TObject);
begin
  DoubleBuffered:= true;
  MovObj1:= TMovObj.Create(Self);
  MovObj1.Parent:= Self;
  MovObj1.FTimer:= TTimer.Create(Self);
  MovObj1.Init(20,30,'C:\','Kirby','bmp',0,4,1,1,200,10,100);
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  MovObj1.Done;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if MovObj1.FTimer_running then MovObj1.Stop else MovObj1.Run;
end;

Ich finds lustig, Bewegung auf Knopfdruck :D Konnte ich auch schon vorher gestalten, aber so find ich es i-wie einfacher. Achja, der Kirby ist aus nem anderen Projekt mit inzwischen 5 Units, die ohne OOP auskommen mussten ;_;

Btw, ist es günstiger, den Timer über Enabled zu stoppen, anstatt die Prozedur-Zuweisung zu ändern?

EDIT: Noch ne Frage, wenn ich später Methoden schreibe wie
function schnitt2Einheiten(Einh1, Einh2: TSFE): Boolean;kann ich dann auch sowas
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var
  Obj1: TObj;
  MovObj1, MovObj2: TMovObj;

...
begin
  schnitt2Einheiten(MovObj1,MovObj2);
  schnitt2Einheiten(MovObj1,Obj1);
machen, da TObj von TSFE und TMovObj von TObj abgeleitet sind?
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19346
Erhaltene Danke: 1754

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Do 02.09.10 06:10 
user profile iconNait hat folgendes geschrieben Zum zitierten Posting springen:
aso, wenn ich also Init in TObj erneut deklariere, denkt er sich virtual dazu!
Die Methode ist dann ja bereits virtual.

Was im Hintergrund passiert ist, dass virtuelle Methoden an einer anderen Stelle liegen, die stehen in einer Tabelle (die Virtual Method Table). Darüber wird dann beim Überschreiben die neue eingefügt. Wenn eine Methode nicht virtuell ist und da drin steht, kannst du sie nicht überschreiben.

Ja, wenn da ein zusätzlicher Parameter drin ist, musst du overload nehmen. Weil du eine Methode mit dem selben Namen, aber anderen Parametern hinzufügst. Auch reintroduce sollte gehen, ich kann es im Moment nicht testen.

Zur reineditierten Frage:
Ja, das geht.

Für diesen Beitrag haben gedankt: Nait