Ich kann keine eigene Klasse erzeugen

    Ich kann keine eigene Klasse erzeugen

    Hallo Forum,

    ich hoffe mal, ich bin hier richtig. Mache die ganze Programmiererei nur so hobbymäßig (Delphi 7) und habe daher auch nicht viel Ahnung. Habe bisher immer vermieden, selbst Klassen zu erstellen, und wollte es nun doch mal probieren.
    Es klappt aber nicht. Beim Create muss ich was falsch machen. Der Code sieht so aus:

    Delphi-Code

    1. type
    2. TForm1 = class(TForm)
    3. procedure FormCreate(Sender: TObject);
    4. private
    5. public
    6. end;
    7. TSchwebendes_Fenster = Class
    8. private
    9. fFenster : TImage;
    10. fFensterZielPosition : TPoint;
    11. public
    12. constructor Create;
    13. destructor Destroy; override;
    14. property Fenster : TImage read FFenster write Ffenster;
    15. property FensterZielPosition: TPoint read fFensterZielPosition write fFensterZielPosition;
    16. end;
    17. var
    18. Form1: TForm1;
    19. Testbild : TSchwebendes_Fenster;
    20. implementation
    21. {$R *.dfm}
    22. constructor TSchwebendes_Fenster.Create;
    23. begin
    24. inherited create;
    25. //bis hier gehts, dann kommt "access violation..."
    26. fFenster.Left := 100;
    27. fFenster.Top :=50;
    28. fFenster.Width := 100;
    29. fFenster.Height := 100;
    30. fFenster.Canvas.FloodFill(2,2,$000080,fsSurface);
    31. end;
    32. destructor TSchwebendes_Fenster.Destroy;
    33. begin
    34. fFenster.Free;
    35. inherited Destroy;
    36. end;
    37. procedure TForm1.FormCreate(Sender: TObject);
    38. begin
    39. Testbild := TSchwebendes_Fenster.Create;
    40. end;
    41. end.


    Kann einer von euch sehen, was ich nicht verstanden habe?
    Der Konstruktor ist dafür zuständig, dass alle Felder initialisiert werden. Dazu rufst du zunächst Inherited auf, d.h. alle Initialisierungen der übergeordneten Klasse werden ausgeführt. Das ist schon mal gut. Dann möchtest du das Feld fFenster mit Werten belegen, ohne es jedoch vorher zu erzeugen. Das kann natürlich nicht klappen. Also muss zunächst noch ein fFenster := TImage.Create rein. Das wird so allerdings auch nicht gehen, weil TImage eine visuelle Komponente ist. Als Nachfahre von TComponent erwartet dessen Konstruktor also einen Owner (der ebenfalls TComponent-Kind sein muss). Und damit man da überhaupt was sieht, sollte diesem Image auch noch ein Parent zugewiesen werden. Besser ist also, wenn du mal beschreibst, was du überhaupt möchtest.
    Master of the EDH ;)
    @nestrellov

    Was bedeutet "klappt aber nicht" genau? Erhältst du eine Fehlermeldung beim Compilieren? Wenn ja, wie lautet diese?

    Also ich verwende z.B. für eine Bilderliste untenstehende Klasse. Darin wird jeweils ein TImage und ein TLabel erzeugt. Wie du im Constructor siehst, erzeuge ich dort diese beiden Komponenten und weise ihnen NIL zu – der Klasse ist nicht bekannt, unter welchem übergeordneten Objekt dieses Klassenobjekt am Ende eingesetzt werden soll. Das ist übrigens auch genau der Sinn & Zweck von Klassen: Sie können immer wieder eingesetzt werden.

    Delphi-Code

    1. UNIT Startbilder;
    2. INTERFACE
    3. USES
    4. ExtCtrls, StdCtrls, Classes, Graphics;
    5. TYPE
    6. TStartBild = Class
    7. PRIVATE
    8. Var
    9. fBild : TImage;
    10. fTitel : TLabel;
    11. Function GetfBild : TImage;
    12. Procedure SetfBild(Const Value : TImage);
    13. Function GetfTitel : TLabel;
    14. Procedure SetfTitel(Const Value : TLabel);
    15. PUBLIC
    16. Constructor Create();
    17. Destructor Destroy; override;
    18. Property Bild : TImage read GetfBild write SetfBild;
    19. Property Titel : TLabel read GetfTitel write SetfTitel;
    20. END;
    21. IMPLEMENTATION
    22. { TStartBild }
    23. Function TStartBild.GetfBild: TImage;
    24. begin
    25. Result := fBild;
    26. end;
    27. Procedure TStartBild.SetfBild(Const Value: TImage);
    28. begin
    29. fBild.Assign(Value);
    30. end;
    31. Function TStartBild.GetfTitel: TLabel;
    32. begin
    33. Result := fTitel;
    34. end;
    35. Procedure TStartBild.SetfTitel(Const Value: TLabel);
    36. begin
    37. fTitel.Assign(Value);
    38. end;
    39. Constructor TStartBild.Create;
    40. begin
    41. inherited;
    42. fBild := TImage.Create(nil);
    43. fBild.Visible := False;
    44. fBild.Constraints.MaxWidth := 500;
    45. fBild.Constraints.MaxHeight := 500;
    46. fBild.Constraints.MinWidth := 100;
    47. fBild.Constraints.MinHeight := 100;
    48. fBild.AutoSize := False;
    49. fBild.Stretch := True;
    50. fBild.Proportional := True;
    51. fBild.Center := True;
    52. fBild.Picture.Bitmap.Canvas.Brush.Style := bsClear;
    53. fBild.Picture.Bitmap.Canvas.Pen.Color := clRed;
    54. fBild.Picture.Bitmap.Canvas.Pen.Style := psSolid;
    55. fBild.Picture.Bitmap.Canvas.Pen.Width := 5;
    56. fTitel := TLabel.Create(nil);
    57. fTitel.Visible := False;
    58. fTitel.AutoSize := False;
    59. fTitel.Layout := tlCenter;
    60. fTitel.Alignment := taCenter;
    61. fTitel.WordWrap := True;
    62. fTitel.ParentFont := True;
    63. end;
    64. Destructor TStartBild.Destroy;
    65. begin
    66. If Assigned(fBild) Then
    67. fBild.Free;
    68. If Assigned(fTitel) Then
    69. fTitel.Free;
    70. inherited;
    71. end;
    72. end.


    Die Zuweisungen der verschiedenen Eigenschaften (Left, Top, Width, Height usw.) finden dann erst in der aufrufenden Unit statt. Dort verwende ich diese Klasse z.B. in einer Objektliste, die dann eine gewisse Anzahl von Objekten der Klasse TStartBild enthält:

    Delphi-Code

    1. Procedure TFrame_Zentrale.StartbildErzeugen;
    2. Var
    3. Obj : TStartBild;
    4. i : Integer;
    5. begin
    6. Obj := TStartBild.Create;
    7. If DatMod.BlobFeldInImage(DatMod.Qset_Zentrale.FieldByName('MODULBILD'),Obj.Bild.Picture.Bitmap) Then
    8. Begin
    9. Obj.Bild.Parent := Panel_Bilder;
    10. Obj.Bild.OnMouseUp := BildGeklickt;
    11. Obj.Titel.Parent := Panel_Bilder;
    12. Obj.Titel.OnMouseUp := TitelGeklickt;
    13. Obj.Titel.Caption := DatMod.Qset_Zentrale.FieldByName('MODULNAME').AsString;
    14. i := BList.Add(Obj);
    15. BList[i].Bild.Tag := DatMod.Qset_Zentrale.FieldByName('PROGRAMMODUS').AsInteger;
    16. BList[i].Titel.Tag := BList[i].Bild.Tag;
    17. End Else
    18. Begin
    19. Obj.Free;
    20. ShowMessage(GLD.Fehlertext);
    21. End;
    22. end;


    Diese Funktion wird dann ein paarmal aufgerufen, bis die gewünschte (oder vorbestimmte) Anzahl an Objekten in der Objektliste versammelt ist:

    Delphi-Code

    1. Function TFrame_Zentrale.Aufbauen: Boolean;
    2. begin
    3. Try
    4. DatMod.Qset_Zentrale.First;
    5. While Not DatMod.Qset_Zentrale.Eof Do
    6. Begin
    7. StartbildErzeugen;
    8. DatMod.Qset_Zentrale.Next;
    9. End;
    10. Zeichnen;
    11. Result := True;
    12. Except
    13. on e:exception Do
    14. Begin
    15. GLD.Fehlertext := 'Fehler beim Aufbau der Bilderliste';
    16. Result := False;
    17. End;
    18. End;
    19. end;


    Erst beim Zeichnen werden am Ende die Positionen und Größen der Objekte festgelegt:

    Delphi-Code

    1. Procedure TFrame_Zentrale.Zeichnen;
    2. Var
    3. Links, Oben,
    4. i,z : Integer;
    5. begin
    6. If Not Assigned(BList) Then Exit;
    7. Panel_Bilder.Anchors := [akLeft,akTop,akRight];
    8. Panel_Bilder.Height := ScrollBox_Zentrale.ClientHeight -1;
    9. Panel_Bilder.Anchors := [akLeft,akTop,akRight,akBottom];
    10. Links := GLD.BildAbstand;
    11. Oben := GLD.BildAbstand;
    12. z := BList.Count;
    13. If z > 0 Then
    14. For i := 0 To z-1 Do
    15. Begin
    16. BList[i].Bild.Width := GLD.BildMass;
    17. BList[i].Bild.Height := GLD.BildMass;
    18. BList[i].Bild.Left := Links;
    19. BList[i].Bild.Top := Oben;
    20. BList[i].Bild.Visible := True;
    21. BList[i].Titel.Width := GLD.BildMass;
    22. BList[i].Titel.Height := GLD.TitelHoch;
    23. BList[i].Titel.Left := Links;
    24. BList[i].Titel.Top := Oben + GLD.BildMass;
    25. BList[i].Titel.Visible := True;
    26. Links := Links + GLD.BildMass + GLD.BildAbstand;
    27. If (Links + GLD.BildMass + GLD.BildAbstand) > Panel_Bilder.Width Then
    28. Begin
    29. Links := GLD.BildAbstand;
    30. Oben := Oben + GLD.BildMass + GLD.BildAbstand + GLD.TitelHoch;
    31. End;
    32. End;
    33. If (Oben + GLD.BildMass + GLD.BildAbstand + GLD.TitelHoch) > Panel_Bilder.Height Then
    34. Panel_Bilder.Height := Oben + GLD.BildMass + GLD.BildAbstand + GLD.TitelHoch Else
    35. Panel_Bilder.Height := ScrollBox_Zentrale.ClientHeight -1;
    36. Lbl_ScrollClientHeightTxt.Caption := IntToStr(ScrollBox_Zentrale.ClientHeight);
    37. Lbl_ScrollClientWidthTxt.Caption := IntToStr(ScrollBox_Zentrale.ClientWidth);
    38. Lbl_PanelHeightTxt.Caption := IntToStr(Panel_Bilder.ClientHeight);
    39. Lbl_PanelWidthTxt.Caption := IntToStr(Panel_Bilder.ClientWidth);
    40. end;


    Das ergibt dann eine dynamische Liste von quadratischen Bildern mit untenstehendem Label. Und weil die ganze Sache schön in Einzelschritte unterteilt ist, reagiert die Anzeige auch sauber auf Größenänderungen des Formulars bzw. Frames.
    Bilder
    • RechPro.jpg

      409,47 kB, 857×657, 121 mal angesehen

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Perlsau“ ()

    Super!
    mit

    Delphi-Code

    1. fFenster := TImage.Create(nil);

    im constructor und

    Delphi-Code

    1. fenster.parent := Form2;

    in der aufrufenden unit klappt es perfekt!
    Einen Owner habe ich nicht angegeben.
    Aber jedenfalls hab ich jetzt endlich verstanden, was ein parent ist. :idea:
    Vielen Dank für eure Hilfe! :thumbsup:
    Echt ein klasse Forum hier!
    Einen Owner musst du auch nicht angeben.

    Auf eins solltest du aber achten:

    Wenn der Parent über den Styx nach Walhalla zieht, nimmt er alle Kinder mit auf die Reise. Soll heißen, dass wenn du die Form2 freigibst und fFenster.Parent noch auf dieser Form liegt, dann hat dein fFenster zwar noch einen Referenz-Zeiger, die Instanz ist aber zerstört.

    Und nein das ist auch nicht anders, wenn man einen Owner mit angibt.
    War es nicht umgekehrt: Der Owner ist fürs Freigeben zuständig und Parent fürs Zeichnen. Wenn also Owner nil ist, muss man selbst freigeben, wenn Ower gesetzt ist, wird die Instanz freigegeben, sobald der Owner freigegeben wird. In aller Regel setzt man Owner und Parent gleich, es sind aber zwei Aufgaben.


    Kaum macht man's richtig, schon klappts!
    Was mich persönlich ein wenig stört: es wird eine nicht visuelle Klasse benutzt, die visuelle Komponenten verwendet. Wenn es lediglich darum geht, eine Grafik zusammen mit einem Text zu verwalten, würde ich statt TImage TGraphic und statt TLabel einen String verwenden, soll die Klasse das hingegen auch darstellen, könnte man über eine Komponente entweder als Composite Control oder als TGraphicControl/TCustomControl nachdenken. Wenn es ein Composite Control werden soll, könnte man sie als Nachfahre von TWinControl deklarieren, damit kann sie sowohl Parent als auch Owner des Images und des Labels sein.
    10 Minuten Nachdenken ersparen oftmals 10 Stunden Fehlersuche.

    R2C2 schrieb:

    War es nicht umgekehrt: Der Owner ist fürs Freigeben zuständig und Parent fürs Zeichnen. Wenn also Owner nil ist, muss man selbst freigeben, wenn Ower gesetzt ist, wird die Instanz freigegeben, sobald der Owner freigegeben wird. In aller Regel setzt man Owner und Parent gleich, es sind aber zwei Aufgaben.

    Perlsau schrieb:

    In der Tat! Genau so verhält es sich.

    Dann solltet ihr mal darauf drängen, dass die Dokumentation geändert wird, denn die ist da anderer Meinung.

    Oder ihr probiert es einfach mal aus, oder schaut euch die Quellcodes an was der Parent da mit seinen Child-Controls veranstaltet ;)


    Anmerkung: Die in TControl deklarierte Eigenschaft Parent ähnelt der Eigenschaft Owner von TComponent darin, dass die hier angegebenen Objekte für das Freigeben des Steuerelements zuständig sind. Jedoch ist das in Parent enthaltene Objekt immer eine fensterorientierte Komponente, die das Steuerelement visuell enthält und beim Speichern des Formulars für das Schreiben des Steuerelements in einen Stream verantwortlich ist. Owner enthält das Objekt, das beim Instantiieren des Steuerelements als Parameter an den Konstruktor übergeben wurde. Dieser Eigentümer veranlasst das Speichern aller Objekte (einschließlich des Steuerelements und seiner übergeordneten Komponente), wenn das Formular gespeichert wird.

    Interpretieren wir diesen Absatz unterschiedlich?


    Kaum macht man's richtig, schon klappts!
    Anscheinend ja, denn dort steht, dass beide (Owner und Parent) für die Freigabe zuständig sind.
    Die in TControl deklarierte Eigenschaft Parent ähnelt der Eigenschaft Owner von TComponent darin, dass die hier angegebenen Objekte für das Freigeben des Steuerelements zuständig sind.

    Ansonsten hilft wohl nur ausprobieren - ich habe es schon hinter mich gebracht.
    Das war mir zwar auch neu, aber es scheint tatsächlich zu stimmen. Aus der Controls.pas (TWinControl.Destroy):

    Delphi-Code

    1. I := ControlCount;
    2. while I <> 0 do
    3. begin
    4. Instance := Controls[I - 1];
    5. Remove(Instance);
    6. Instance.Destroy; //hiermit hätte ich nicht gerechnet
    7. I := ControlCount;
    8. end;
    10 Minuten Nachdenken ersparen oftmals 10 Stunden Fehlersuche.

    Klausens schrieb:

    Vielleicht kommt ja irgendwann mal Reference counting, dann isses egal ;)

    Inwiefern sollte es dann egal sein? Dann steht dort statt

    Delphi-Code

    1. Instance.Destroy
    einfach ein

    Delphi-Code

    1. Instance.DisposeOf
    und den einzigen Vorteil den du hast ist die Vermeidung eines dangling Pointer denn du kannst jede Referenz prüfen auf

    Delphi-Code

    1. Obj.IsDisposed
    . Die Instanz ist auf jeden Fall nicht mehr zu gebrauchen.
    Da steht dann garnix mehr, weil wenn FControls zerstört wird und sonst auch kein Zeiger keine Referenz mehr das Control braucht verschwindets von selbst.

    edit: Und Referenzen muss man nicht auf Disposed prüfen, weil wenn ich eine Referenz habe verschwindet das Objekt auch nicht. Ausser ich deklarier die Referenz mit WEAK, dann wird sie aber (je nach Implementation) wahrscheinlich auf nil gesetzt.

    D.h. ich hab irgendeine Referenz und die zeigt ins Gemüse ist dann nicht mehr möglich.

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „Klausens“ ()

    Klausens schrieb:

    Da steht dann garnix mehr, weil wenn FControls zerstört wird und sonst auch kein Zeiger keine Referenz mehr das Control braucht verschwindets von selbst.

    edit: Und Referenzen muss man nicht auf Disposed prüfen, weil wenn ich eine Referenz habe verschwindet das Objekt auch nicht. Ausser ich deklarier die Referenz mit WEAK, dann wird sie aber (je nach Implementation) wahrscheinlich auf nil gesetzt.

    D.h. ich hab irgendeine Referenz und die zeigt ins Gemüse ist dann nicht mehr möglich.

    Wenn Emba in dem Zuge das VCL/FMX Framework komplett umbaut, dann werden wir damit wohl rechnen können ... das ist aber wohl eher nicht in diesem Leben zu erwarten.

    Grundproblem ist die generelle Verwaltung der Komonenten. So wird im OI eine Komponente per Owner an die Form gebunden und per Parent eben an die Komponente, wo diese erscheinen soll. Schon hat sich das mit der Freigabe trotz ARC erledigt, denn der Owner kennt immer noch die Komponente.

    Genau aus diesem Grund wird es zu 99,99% beim DisposeOf bleiben.
    Vielleicht versteh ichs jetzt falsch, aber das sollte kein Problem sein.

    Wenn der Owner zerstört wird und seine Control- bzw. Component-Listen löscht verschwinden auch die Komponenten.
    Das Einzige was man dazu tun muss ist die Property Owner und Parent in der Komponente jeweils mit [Weak] zu versehen, damit keine zirkulären Abhängigkeiten entstehen.

    edit: was ich grad gefunden hab (XE7)

    Delphi-Code

    1. TComponent = class(TPersistent, IInterface, IInterfaceComponentReference)
    2. private
    3. [Weak] FOwner: TComponent;


    D.h. die haben das [Weak] (wohl wegen Android, das ja schon ARC benutzt) eh schon vorbereitet

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Klausens“ ()