Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Delphi Bei einer Komponente ein Panel zwischenschalten (https://www.delphipraxis.net/131602-bei-einer-komponente-ein-panel-zwischenschalten.html)

blackdrake 27. Mär 2009 08:35


Bei einer Komponente ein Panel zwischenschalten
 
Hallo.

Ich möchte eine Komponente ableiten, sie jedoch bezüglich des Parents in ein Panel zeichnen. Das eigentliche Objekt soll also in einem Panel liegen, das zwischengeschaltet ist. Eine Wrapper-Klasse möchte ich nicht verwenden, da alle Dinge, die ich benötige direkt abgeleitet werden sollen. Ansonsten würde z.B. die Schreibarbeit zum Einen ins unendliche gehen, zum Anderen gäbe es dann keine IS-A-Beziehung mehr.

Ich habe folgenden Code geschrieben, jedoch erhalte ich im Create-Teil stets eine AV. Was ist da falsch?

Delphi-Quellcode:
type
  TPaneledImage = class(TImage)
  private
    FPanel: TPanel;
    FParent: TWinControl;
    FVisible: boolean;
    procedure SetVisible(Value: boolean);
  protected
    procedure SetParent(AParent: TWinControl); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Parent: TWinControl read FParent write SetParent;
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
    property Visible: boolean read FVisible write SetVisible;
  end;

constructor TPaneledImage.Create(AOwner: TComponent);
begin
  FPanel := TPanel.Create(AOwner);

  inherited Create(FPanel);

  // Eigenschaften für das Image im Panel festlegen
  Parent := FPanel;
  Align := alClient;
  Visible := true;
end;

destructor TPaneledImage.Destroy;
begin
  FPanel.Free; // OK?

  inherited;
end;

procedure TPaneledImage.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
  // Das Image im Panel soll bei (0, 0) bleiben. Nur das Panel bewegt sich.
  inherited SetBounds(0, 0, AWidth, AHeight);

  FParent.Left := ALeft;
  FParent.Top := ATop;
  FParent.Width := AWidth;
  FParent.Height := AHeight;
end;

procedure TPaneledImage.SetParent(AParent: TWinControl);
begin
  if FParent <> AParent then
  begin
    FParent := AParent;
    FPanel.Parent := AParent;
  end;

  inherited SetParent(FPanel);
  // inherited Parent := FPanel;
end;

procedure TPaneledImage.SetVisible(Value: boolean);
begin
  if FVisible <> Value then
  begin
    FVisible := Value;
    FPanel.Visible := Value;
  end;
end;
Gruß
blackdrake

DeddyH 27. Mär 2009 09:49

Re: Bei einer Komponente ein Panel zwischenschalten
 
In welcher Zeile knallt es denn genau? Ich rate mal und sage, beim inherited, oder?

blackdrake 27. Mär 2009 11:22

Re: Bei einer Komponente ein Panel zwischenschalten
 
Die Exception findet nach dem Create; statt. Es ist also keine exakte Zeile vom Compiler markiert. Auch die Exception-Meldungen sind sehr vielfälig (am Anfang kommt zum Beispiel eine externe Exception, danach die AV und danach der Windows-Programmabsturz).

angos 27. Mär 2009 12:06

Re: Bei einer Komponente ein Panel zwischenschalten
 
hi,

also wie ich das sehe hat deddyh recht (einfach mal den source genommen und in eine leere anwendung gepackt) Die exception (zumindest die erste) findet im inherited create statt. Hier wird nämlich setbounds ausgelöst, in welchem du das noch nicht gesetzte FParent nutzen willst.

Ich weis auch gar nicht, wie man das sinnvoll lösen soll. Wenn du das Panel nachher freigeben willst, knallts ja auch, weil das panel ja auch das PaneledImage freigeben will (innerhalb des destroys von selbigem ;) )

Aber vielleicht gibt es da eine sinnvolle Lösung

Gruß
angos

blackdrake 27. Mär 2009 20:33

Re: Bei einer Komponente ein Panel zwischenschalten
 
Hallo.

Um eine sinnvolle Lösung wäre ich sehr dankbar, denn ich möchte wirklich gerne von TImage vererben und lediglich ein Panel dazwischenschalten, damit ich ein WinControl-Handle zum Zeichnen bekomme.

Das mit der Exception verstehe ich nicht ganz:

Delphi-Quellcode:
constructor TPaneledImage.Create(AOwner: TComponent);
begin
  FPanel := TPanel.Create(AOwner);

  inherited Create(FPanel); // **

  // Eigenschaften für das Image im Panel festlegen
  Parent := FPanel;
  Align := alClient;
  Visible := true;
end;
Bei ** ist ja das FPanel bereits mittels Create() erstellt worden. Wieso soll dann ** fehlschlagen? Und welches SetBounds (von TPaneledImage oder TPanel) wird warum aufgerufen? Ich kann den Gedankengang noch nicht ganz nachvollziehen, wieso es fehlschlägt.

Beim Destroy (hat aber nichts mit dem Create-Fehler zu tun) könnte es zum Problem kommen, wenn das TPanel freigegeben wird, da es das TPaneledImage own'ed und somit mit freigibt, bevor der Rest des Destructors von TPaneledImage aufgerufen wird.

Gruß
blackdrake

blackdrake 30. Mär 2009 07:48

Re: Bei einer Komponente ein Panel zwischenschalten
 
Hat denn keiner eine Lösung für das Problem?

jaenicke 30. Mär 2009 08:08

Re: Bei einer Komponente ein Panel zwischenschalten
 
Naja, der genannte Fehler an sich sollte sich ja leicht beheben lassen indem du prüfst, ob FParent nil ist:
Delphi-Quellcode:
  if FParent <> nil then
  begin
    FParent.Left := ALeft;
    FParent.Top := ATop;
    FParent.Width := AWidth;
    FParent.Height := AHeight;
  end;
Allerdings sehe ich nicht so recht wie das ansonsten klappen könnte die Objekthierarchie im Konstruktor so hinzubiegen wie du dir das vorstellst. Was dabei passiert müsste ich einmal testen, aber ich sehe da andere Probleme.

// EDIT:
  • Warum benutzt du da überhaupt FParent statt FPanel?
  • SetBounds reicht nicht, da bei dem Setzen der einzelnen Werte die anderen aus der Komponente selbst ausgelesen werden.
  • Wozu setzt du in Create Parent? Das muss danach doch ohnehin passieren, vielleicht soll es bei dem Create auch noch gar nicht passieren in der Anwendung.
  • Free darfst du nicht aufrufen, wenn FPanel (deinem Aufruf nach ja so gewollt) einen Owner hat.

blackdrake 30. Mär 2009 11:09

Re: Bei einer Komponente ein Panel zwischenschalten
 
Hallo.

Ich habe den Code nochmal ganz genau analysiert, Diagramme gemacht (Stichwort Hirnknoten) und einiges verbessert. Nun klappt es ohne AV beim Create. Im Destroy habe ich aber aufgrund der Owner Probleme. Selbst wenn das Destroy funktioniert, kommt die AV am Programmende. Ich habe verschiedene Varianten durchprobiert und im Quellcode kurz kommentiert. Könnt ihr mir bitte weiterhelfen?

Delphi-Quellcode:
type
  TPaneledImage = class(TImage)
  private
    FPanel: TPanel;
    procedure SetVisible(Value: boolean);
    function GetParent: TWinControl;
    function GetOwner: TComponent; reintroduce;
    function GetVisible: boolean;
  protected
    procedure SetParent(AParent: TWinControl); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
    property Parent: TWinControl read GetParent write SetParent;
    property Visible: boolean read GetVisible write SetVisible;
    property Owner: TComponent read GetOwner;
  end;

constructor TPaneledImage.Create(AOwner: TComponent);
begin
  // Host-Panel erstellen
  FPanel := TPanel.Create(AOwner);

  // Eigenschaften für das Panel
  FPanel.BevelOuter := bvNone;

  // Das eigentliche Image erstellen
  inherited Create(FPanel); {1}
  // inherited Create(AOwner); {2}
  // inherited Create(Self); {3}

  // Unveränderliche Eigenschaften für das Image im Panel festlegen
  inherited SetParent(FPanel);
  Align := alClient;
  Visible := true;
end;

destructor TPaneledImage.Destroy;
begin
  // Zuerst das Panel freigeben. Das Panel wird zuerst Nil gesetzt, dann freigegeben.
  // Das Panel wird widerum uns zuerst freigeben, weswegen dieses Destroy ein zweites
  // Mal aufgerufen wird und die korrekte Freigabe von TObject einleitet.

  if Assigned(FPanel) then
  begin
    FreeAndNil(FPanel) // Diese Zeile wird 1x zuerst aufgerufen
  end
  else
  begin
    // Diese Zeile wird 1x danach aufgerufen
    inherited;
    // Hier wird zusätzlich einmal TPaneledImage.SetParent aufgerufen
  end;

  // Aber NACH Destroy gibts eine AV!
end;

function TPaneledImage.GetOwner: TComponent;
begin
  result := FPanel.Owner;
end;

function TPaneledImage.GetParent: TWinControl;
begin
  result := FPanel.Parent;
end;

function TPaneledImage.GetVisible: boolean;
begin
  result := FPanel.Visible;
end;

procedure TPaneledImage.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
  // Das Image im Panel soll bei (0, 0) bleiben. Nur das Panel bewegt sich.
  inherited SetBounds(0, 0, AWidth, AHeight);

  FPanel.Left := ALeft;
  FPanel.Top := ATop;
  FPanel.Width := AWidth;
  FPanel.Height := AHeight;
end;

procedure TPaneledImage.SetParent(AParent: TWinControl);
begin
  if not Assigned(FPanel) then exit; // Verhindern einer AV bei Destroy

  if FPanel.Parent <> AParent then
  begin
    FPanel.Parent := AParent;
  end;
end;

procedure TPaneledImage.SetVisible(Value: boolean);
begin
  if FPanel.Visible <> Value then
  begin
    FPanel.Visible := Value;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  x: TPaneledImage;
begin
  x := TPaneledImage.Create(Self);
  try
    x.Parent := Self;
    x.AutoSize := true;
    x.Picture.LoadFromFile('C:\WINDOWS\Präriewind.BMP');
  finally
    x.Free; // Verursacht eine AV, wenn Programm später beendet wird
  end;
end;
Create-Versuch {1}

Self gehört Self.Panel
Self.Panel gehört Self.Owner

Create-Versuch {2}

Self gehört Self.Owner
Self.Panel gehört Self.Owner

Create-Versuch {3}

Self gehört Self (hä? :drunken: )
Self.Panel gehört Self.Owner

Weitere Idee 4

Self gehört Self.Owner
Self.Panel gehört Self

Weitere Idee 5

Self gehört Self.Panel
Self.Panel gehört Self


Gruß
blackdrake

jaenicke 30. Mär 2009 16:34

Re: Bei einer Komponente ein Panel zwischenschalten
 
Wenn du den Owner des Panels auf den übergebeben setzt und den deines Images auf das Panel, dann brauchst du dich um die Freigabe nicht mehr zu kümmern.
Delphi-Quellcode:
constructor TPaneledImage.Create(AOwner: TComponent);
begin
  FPanel := TPanel.Create(AOwner);
  inherited Create(FPanel);

  // Eigenschaften für das Image im Panel festlegen
  Align := alClient;
end;

procedure TPaneledImage.SetParent(AParent: TWinControl);
begin
  FPanel.Parent := AParent;
  if csDestroying in ComponentState then
    inherited SetParent(AParent)
  else
    inherited SetParent(FPanel);
end;
Das Parent brauchst du mit diesem SetParent nicht in Create setzen, denn das passiert nach der Erzeugung ohnehin erneut. Deshalb bringt das dort rein gar nix.

Den Destruktor brauchst du so wie hier geschrieben gar nicht schreiben.

Bei dem SetBounds machst du einen Denkfehler, wenn Left usw. einzeln gesetzt werden, schau dir dann die Werte einmal an.

blackdrake 31. Mär 2009 21:06

Re: Bei einer Komponente ein Panel zwischenschalten
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo.

Vielen Dank für deine Antwort.

Es funktioniert leider immer noch nicht. Jetzt bekomme ich einen Stacküberlauf.

Ich hatte gestern sehr lange daran gearbeitet und etliche Workarounds beim SetBounds() durchgeführt, sodass das mit den Algignments, Stretch, AutoSize etc alles einigermaßen funktioniert (aber es scheint noch Ausnahmen zu geben). Beispielsweise musste ich in SetBounds() Width und Height verändern, damit AutoSize funktioniert. Jedoch durfte ich Left und Top nicht dort verändern, da sonst ein Image wieder bei (0, 0) war, wenn ich den Parent geändert habe. Das ganze ist extrem kompliziert... Diesbezüglich bin ich für Verbesserungsvorschläge dankbar!

Ich habe mal das komplette Testprogramm + aktuellem Codestand gepostet.

Delphi-Quellcode:
type
  TPaneledImage = class(TImage)
  private
    function GetVisible: boolean;
    procedure SetVisible(const Value: boolean);
    function GetParent: TWinControl;
    procedure SetLeft(const Value: Integer);
    procedure SetTop(const Value: Integer);
    function GetLeft: Integer;
    function GetTop: Integer;
    function GetHeight: Integer;
    function GetWidth: Integer;
    procedure SetHeight(Value: Integer);
    procedure SetWidth(Value: Integer);
    function GetAlign: TAlign;
    procedure SetAlign(Value: TAlign);
  protected
    Panel: TPanel;
    procedure SetParent(AParent: TWinControl); override;
  public
    // Color...
    // Handle
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure SetBounds(ALeft: Integer; ATop: Integer; AWidth: Integer; AHeight: Integer); override;
    property Visible: boolean read GetVisible write SetVisible;
    property Parent: TWinControl read GetParent write SetParent;
    property Left: Integer read GetLeft write SetLeft;
    property Top: Integer read GetTop write SetTop;
    property Width: Integer read GetWidth write SetWidth;
    property Height: Integer read GetHeight write SetHeight;
    property Align: TAlign read GetAlign write SetAlign;
  end;

{ TPaneledImage }

constructor TPaneledImage.Create(AOwner: TComponent);
begin
  (* inherited Create(AOwner);

  Panel := TPanel.Create(Self);

  // Eigenschaften für Panel
  Panel.BevelOuter := bvNone;
  Panel.Color := clRed;

  // Eigenschaften für Image
  inherited Align := alClient;
  inherited SetParent(Panel); *)


  Panel := TPanel.Create(Self);

  inherited Create(Panel);

  // Eigenschaften für Panel
  Panel.BevelOuter := bvNone;
  Panel.Color := clRed;

  // Eigenschaften für Image
  (* inherited *) Align := alClient;
end;

destructor TPaneledImage.Destroy;
begin
  (* if Assigned(Panel) then
  begin
    inherited SetParent(Panel.Parent);
    FreeAndNil(Panel);
  end; *)

  inherited;
end;

function TPaneledImage.GetVisible: boolean;
begin
  result := Panel.Visible;
end;

procedure TPaneledImage.SetVisible(const Value: boolean);
begin
  if Panel.Visible <> Value then
    Panel.Visible := Value;
end;

procedure TPaneledImage.SetBounds(ALeft: Integer; ATop: Integer; AWidth: Integer; AHeight: Integer);
begin
  // Das Bild im Panel ist immer bei (0, 0)
  // inherited SetBounds(0, 0, AWidth, AHeight);
  inherited SetBounds(ALeft, ATop, AWidth, AHeight);

  if Assigned(Panel) then
  begin
//    Panel.Left  := ALeft;
//    Panel.Top   := ATop;
    Panel.Width := AWidth;
    Panel.Height := AHeight;
  end;
end;

function TPaneledImage.GetParent: TWinControl;
begin
  result := Panel.Parent;
end;

procedure TPaneledImage.SetParent(AParent: TWinControl);
begin
  // TEST

  (* if not Assigned(Panel) then exit;

  if Panel.Parent <> AParent then
    Panel.Parent := AParent; *)

  Panel.Parent := AParent;
  if csDestroying in ComponentState then
    inherited SetParent(AParent)
  else
    inherited SetParent(Panel);
end;

procedure TPaneledImage.SetLeft(const Value: Integer);
begin
  inherited Left := 0;
  if Panel.Left <> Value then
    Panel.Left := Value;
end;

procedure TPaneledImage.SetTop(const Value: Integer);
begin
  inherited Top := 0;
  if Panel.Top <> Value then
    Panel.Top := Value;
end;

procedure TPaneledImage.SetHeight(Value: Integer);
begin
  // *** Es könnte passieren, dass wir aufgrund von AutoSize die größe nicht verändenr dürfen!
  inherited Align := alNone;
  inherited Height := Value;
  Value := inherited Height;
  inherited Align := alClient;

  if Panel.Height <> Value then
    Panel.Height := Value;
end;

procedure TPaneledImage.SetWidth(Value: Integer);
begin
  // *** Es könnte passieren, dass wir aufgrund von AutoSize die größe nicht verändenr dürfen!
  inherited Align := alNone;
  inherited Width := Value;
  Value := inherited Width;
  inherited Align := alClient;

  if Panel.Width <> Value then
    Panel.Width := Value;
end;

procedure TPaneledImage.SetAlign(Value: TAlign);
begin
  if Panel.Align <> Value then
  begin
    Panel.Align := Value;
    if (Value = alNone) and AutoSize then
    begin
      Width := Picture.Width;
      Height := Picture.Height;
    end;
  end;
end;

function TPaneledImage.GetLeft: Integer;
begin
  result := Panel.Left;
end;

function TPaneledImage.GetTop: Integer;
begin
  result := Panel.Top;
end;

function TPaneledImage.GetHeight: Integer;
begin
  result := Panel.Height;
end;

function TPaneledImage.GetWidth: Integer;
begin
  result := Panel.Width;
end;

function TPaneledImage.GetAlign: TAlign;
begin
  result := Panel.Align;
end;
Gruß
blackdrake


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:34 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz